From 2375b437bd2f4287e04b11050aae011b314bcd6b Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 23 Mar 2016 20:49:17 +0800 Subject: Fix a typo --- spec/lib/gitlab/email/receiver_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index 36267faeb93..f381a3907f3 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -24,7 +24,7 @@ describe Gitlab::Email::Receiver, lib: true do end end - context "when no sent notificiation for the reply key could be found" do + context "when no sent notification for the reply key could be found" do let(:email_raw) { fixture_file('emails/wrong_reply_key.eml') } it "raises a SentNotificationNotFoundError" do -- cgit v1.2.1 From 6cfd028278e7fe22c2776b9ce70a5b92223115f9 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 23 Mar 2016 22:20:22 +0800 Subject: Implement #3243 New Issue by email So we extend Gitlab::Email::Receiver for this new behaviour, however we might want to split it into another class for better testing it. Another issue is that, currently it's using this to parse project identifier: Gitlab::IncomingEmail.key_from_address Which is using: Gitlab.config.incoming_email.address for the receiver name. This is probably `reply` because it's used for replying to a specific issue. We might want to introduce another config for this, or just use `reply` instead of `incoming`. I'll prefer to introduce a new config for this, or just change `reply` to `incoming` because it would make sense for replying to there, too. The email template used in tests were copied and modified from: `emails/valid_reply.eml` which I hope is ok. --- lib/gitlab/email/receiver.rb | 87 +++++++++++++++++++++++++------- spec/fixtures/emails/valid_new_issue.eml | 23 +++++++++ spec/lib/gitlab/email/receiver_spec.rb | 66 +++++++++++++++++++----- 3 files changed, 147 insertions(+), 29 deletions(-) create mode 100644 spec/fixtures/emails/valid_new_issue.eml diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 97ef9851d71..2b57b3a20fc 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -12,6 +12,7 @@ module Gitlab class UserNotAuthorizedError < ProcessingError; end class NoteableNotFoundError < ProcessingError; end class InvalidNoteError < ProcessingError; end + class InvalidIssueError < ProcessingError; end def initialize(raw) @raw = raw @@ -20,29 +21,30 @@ module Gitlab def execute raise EmptyEmailError if @raw.blank? - raise SentNotificationNotFoundError unless sent_notification + if sent_notification + process_reply - raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/ - - author = sent_notification.recipient + elsif message_project + process_create_issue - raise UserNotFoundError unless author + else + # TODO: could also be project not found + raise SentNotificationNotFoundError + end + end - raise UserBlockedError if author.blocked? + private + def process_reply + raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/ + author = sent_notification.recipient project = sent_notification.project - raise UserNotAuthorizedError unless project && author.can?(:create_note, project) + check_input(author, project, :create_note) raise NoteableNotFoundError unless sent_notification.noteable - reply = ReplyParser.new(message).execute.strip - - raise EmptyEmailError if reply.blank? - - reply = add_attachments(reply) - - note = create_note(reply) + note = create_note(extract_reply(project)) unless note.persisted? msg = "The comment could not be created for the following reasons:" @@ -54,7 +56,58 @@ module Gitlab end end - private + def process_create_issue + check_input(message_sender, message_project, :create_issue) + + issue = Issues::CreateService.new(message_project, message_sender, + title: message.subject, + description: extract_reply(message_project)).execute + + unless issue.persisted? + msg = "The issue could not be created for the following reasons:" + issue.errors.full_messages.each do |error| + msg << "\n\n- #{error}" + end + + raise InvalidIssueError, msg + end + end + + def check_input(author, project, permission) + if author + if author.blocked? + raise UserBlockedError + elsif project.nil? || !author.can?(permission, project) + # TODO: Give project not found error if author cannot read project + raise UserNotAuthorizedError + end + else + raise UserNotFoundError + end + end + + # Find the first matched user in database from email From: section + def message_sender + @message_sender ||= message.from.find do |email| + user = User.find_by_any_email(email) + break user if user + end + end + + def message_project + @message_project ||= + Project.find_with_namespace(reply_key) if reply_key + end + + def extract_reply project + reply = ReplyParser.new(message).execute.strip + + raise EmptyEmailError if reply.blank? + + add_attachments(reply, project) + + reply + end def message @message ||= Mail::Message.new(@raw) @@ -93,8 +146,8 @@ module Gitlab SentNotification.for(reply_key) end - def add_attachments(reply) - attachments = Email::AttachmentUploader.new(message).execute(sent_notification.project) + def add_attachments(reply, project) + attachments = Email::AttachmentUploader.new(message).execute(project) attachments.each do |link| reply << "\n\n#{link[:markdown]}" diff --git a/spec/fixtures/emails/valid_new_issue.eml b/spec/fixtures/emails/valid_new_issue.eml new file mode 100644 index 00000000000..a7e34b9846d --- /dev/null +++ b/spec/fixtures/emails/valid_new_issue.eml @@ -0,0 +1,23 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo +Message-ID: +Subject: New Issue by email +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +The reply by email functionality should be extended to allow creating a new issue by email. + +* Allow an admin to specify which project the issue should be created under by checking the sender domain. +* Possibly allow the use of regular expression matches within the subject/body to specify which project the issue should be created under. diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index f381a3907f3..e7391a33751 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -15,6 +15,20 @@ describe Gitlab::Email::Receiver, lib: true do let!(:sent_notification) { SentNotification.record(noteable, user.id, reply_key) } let(:receiver) { described_class.new(email_raw) } + let(:markdown) { "![image](uploads/image.png)" } + + def setup_attachment + allow_any_instance_of(Gitlab::Email::AttachmentUploader).to receive(:execute).and_return( + [ + { + url: "uploads/image.png", + is_image: true, + alt: "image", + markdown: markdown + } + ] + ) + end context "when the recipient address doesn't include a reply key" do let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(reply_key, "") } @@ -108,19 +122,8 @@ describe Gitlab::Email::Receiver, lib: true do end context "when everything is fine" do - let(:markdown) { "![image](uploads/image.png)" } - before do - allow_any_instance_of(Gitlab::Email::AttachmentUploader).to receive(:execute).and_return( - [ - { - url: "uploads/image.png", - is_image: true, - alt: "image", - markdown: markdown - } - ] - ) + setup_attachment end it "creates a comment" do @@ -161,4 +164,43 @@ describe Gitlab::Email::Receiver, lib: true do end end end + + context "when it's trying to create a new issue" do + before do + setup_attachment + stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo") + end + + let(:sent_notification) {} + let!(:user) { create(:user, email: 'jake@adventuretime.ooo') } + let(:namespace) { create(:namespace, path: 'gitlabhq') } + let(:project) { create(:project, :public, namespace: namespace) } + let(:email_raw) { fixture_file('emails/valid_new_issue.eml') } + + context "when everything is fine" do + it "creates a new issue" do + expect { receiver.execute }.to change { project.issues.count }.by(1) + issue = project.issues.last + + expect(issue.author).to eq(user) + expect(issue.title).to eq('New Issue by email') + expect(issue.description).to include('reply by email') + expect(issue.description).to include(markdown) + end + end + + context "something is wrong" do + context "when the issue could not be saved" do + before do + project + + allow_any_instance_of(Issue).to receive(:persisted?).and_return(false) + end + + it "raises an InvalidIssueError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::InvalidIssueError) + end + end + end + end end -- cgit v1.2.1 From 4f5027042a944f2e688d010bc469b593e92d22bb Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 24 Mar 2016 18:28:23 +0800 Subject: Add another TODO that we need to verify identity better --- lib/gitlab/email/receiver.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 2b57b3a20fc..714f45d2d43 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -87,6 +87,8 @@ module Gitlab end # Find the first matched user in database from email From: section + # TODO: Since this address could be forged, we should have some kind of + # auth token attached somewhere to verify the identity better. def message_sender @message_sender ||= message.from.find do |email| user = User.find_by_any_email(email) -- cgit v1.2.1 From 0e4c2b6ec425adceefd90a87ffe98d687e1726d9 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 31 Mar 2016 21:23:33 +0800 Subject: enable Style/MethodDefParentheses and fix parentheses --- .rubocop.yml | 2 +- lib/gitlab/email/receiver.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 562197300b4..d14f8d6b53e 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -322,7 +322,7 @@ Style/MethodCallParentheses: # Checks if the method definitions have or don't have parentheses. Style/MethodDefParentheses: - Enabled: false + Enabled: true # Use the configured style when naming methods. Style/MethodName: diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 714f45d2d43..8fc39ee46f1 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -101,7 +101,7 @@ module Gitlab Project.find_with_namespace(reply_key) if reply_key end - def extract_reply project + def extract_reply(project) reply = ReplyParser.new(message).execute.strip raise EmptyEmailError if reply.blank? -- cgit v1.2.1 From a61bf17fce17b06741d47206d04b1137381fc639 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 31 Mar 2016 21:42:12 +0800 Subject: Try to give better names --- lib/gitlab/email/receiver.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 8fc39ee46f1..169312f0b90 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -40,11 +40,11 @@ module Gitlab author = sent_notification.recipient project = sent_notification.project - check_input(author, project, :create_note) + validate_permission(author, project, :create_note) raise NoteableNotFoundError unless sent_notification.noteable - note = create_note(extract_reply(project)) + note = create_note(handle_reply(project)) unless note.persisted? msg = "The comment could not be created for the following reasons:" @@ -57,11 +57,11 @@ module Gitlab end def process_create_issue - check_input(message_sender, message_project, :create_issue) + validate_permission(message_sender, message_project, :create_issue) issue = Issues::CreateService.new(message_project, message_sender, title: message.subject, - description: extract_reply(message_project)).execute + description: handle_reply(message_project)).execute unless issue.persisted? msg = "The issue could not be created for the following reasons:" @@ -73,7 +73,7 @@ module Gitlab end end - def check_input(author, project, permission) + def validate_permission(author, project, permission) if author if author.blocked? raise UserBlockedError @@ -101,7 +101,7 @@ module Gitlab Project.find_with_namespace(reply_key) if reply_key end - def extract_reply(project) + def handle_reply(project) reply = ReplyParser.new(message).execute.strip raise EmptyEmailError if reply.blank? -- cgit v1.2.1 From 347ee6cc912d49680d2f6b90134142c656868333 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 31 Mar 2016 21:56:37 +0800 Subject: Alloy empty reply for new issues, but not response --- lib/gitlab/email/receiver.rb | 6 +++--- spec/fixtures/emails/valid_new_issue_empty.eml | 18 ++++++++++++++++++ spec/lib/gitlab/email/receiver_spec.rb | 16 +++++++++++++++- 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 spec/fixtures/emails/valid_new_issue_empty.eml diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 169312f0b90..3dd3bd02707 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -44,7 +44,9 @@ module Gitlab raise NoteableNotFoundError unless sent_notification.noteable - note = create_note(handle_reply(project)) + reply = handle_reply(project) + raise EmptyEmailError if reply.blank? + note = create_note(reply) unless note.persisted? msg = "The comment could not be created for the following reasons:" @@ -104,8 +106,6 @@ module Gitlab def handle_reply(project) reply = ReplyParser.new(message).execute.strip - raise EmptyEmailError if reply.blank? - add_attachments(reply, project) reply diff --git a/spec/fixtures/emails/valid_new_issue_empty.eml b/spec/fixtures/emails/valid_new_issue_empty.eml new file mode 100644 index 00000000000..2173508d6f8 --- /dev/null +++ b/spec/fixtures/emails/valid_new_issue_empty.eml @@ -0,0 +1,18 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo +Message-ID: +Subject: New Issue by email +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index e7391a33751..4336f0f9e53 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -167,7 +167,6 @@ describe Gitlab::Email::Receiver, lib: true do context "when it's trying to create a new issue" do before do - setup_attachment stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo") end @@ -179,6 +178,8 @@ describe Gitlab::Email::Receiver, lib: true do context "when everything is fine" do it "creates a new issue" do + setup_attachment + expect { receiver.execute }.to change { project.issues.count }.by(1) issue = project.issues.last @@ -187,6 +188,19 @@ describe Gitlab::Email::Receiver, lib: true do expect(issue.description).to include('reply by email') expect(issue.description).to include(markdown) end + + context "when the reply is blank" do + let!(:email_raw) { fixture_file("emails/valid_new_issue_empty.eml") } + + it "creates a new issue" do + expect { receiver.execute }.to change { project.issues.count }.by(1) + issue = project.issues.last + + expect(issue.author).to eq(user) + expect(issue.title).to eq('New Issue by email') + expect(issue.description).to eq('') + end + end end context "something is wrong" do -- cgit v1.2.1 From 87ff0107c15c06dc6e48c7c304e3ffe7e2629684 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 31 Mar 2016 22:10:42 +0800 Subject: Raise one by one instead of if checks --- lib/gitlab/email/receiver.rb | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 3dd3bd02707..5b55a045cbe 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -76,16 +76,11 @@ module Gitlab end def validate_permission(author, project, permission) - if author - if author.blocked? - raise UserBlockedError - elsif project.nil? || !author.can?(permission, project) - # TODO: Give project not found error if author cannot read project - raise UserNotAuthorizedError - end - else - raise UserNotFoundError - end + raise UserNotFoundError unless author + raise UserBlockedError if author.blocked? + # TODO: Give project not found error if author cannot read project + raise UserNotAuthorizedError if project.nil? || + !author.can?(permission, project) end # Find the first matched user in database from email From: section -- cgit v1.2.1 From 034e752aa4dc31442437af08cc6b630f940b6802 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 31 Mar 2016 22:18:30 +0800 Subject: Handle InvalidIssueError as InvalidNoteError --- app/workers/email_receiver_worker.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index f2649e38eb3..95469f1c486 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -39,7 +39,8 @@ class EmailReceiverWorker reason = "You are not allowed to respond to the thread you are replying to. If you believe this is in error, contact a staff member." when Gitlab::Email::Receiver::NoteableNotFoundError reason = "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member." - when Gitlab::Email::Receiver::InvalidNoteError + when Gitlab::Email::Receiver::InvalidNoteError, + Gitlab::Email::Receiver::InvalidIssueError can_retry = true reason = e.message else -- cgit v1.2.1 From 68b5ded0565f3899753173cd294f77607c462dcf Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 31 Mar 2016 22:30:36 +0800 Subject: No need to check project because: sent_notification.project would never be nil, and we also have already checked message_project before entering process_create_issue. --- lib/gitlab/email/receiver.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 5b55a045cbe..0792116ed2d 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -79,8 +79,7 @@ module Gitlab raise UserNotFoundError unless author raise UserBlockedError if author.blocked? # TODO: Give project not found error if author cannot read project - raise UserNotAuthorizedError if project.nil? || - !author.can?(permission, project) + raise UserNotAuthorizedError unless author.can?(permission, project) end # Find the first matched user in database from email From: section -- cgit v1.2.1 From aac297adba87a8b4fecbc064d73f9b4577586f97 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 31 Mar 2016 22:52:17 +0800 Subject: Update style as create_note --- lib/gitlab/email/receiver.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 0792116ed2d..d67e96ff139 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -61,9 +61,12 @@ module Gitlab def process_create_issue validate_permission(message_sender, message_project, :create_issue) - issue = Issues::CreateService.new(message_project, message_sender, - title: message.subject, - description: handle_reply(message_project)).execute + issue = Issues::CreateService.new( + message_project, + message_sender, + title: message.subject, + description: handle_reply(message_project) + ).execute unless issue.persisted? msg = "The issue could not be created for the following reasons:" -- cgit v1.2.1 From 869de96eeaa462da3632441623fd203a2a1d3930 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 1 Apr 2016 18:55:53 +0800 Subject: bang to indicate that this method could raise an exception --- lib/gitlab/email/receiver.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index d67e96ff139..b8065580b54 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -40,7 +40,7 @@ module Gitlab author = sent_notification.recipient project = sent_notification.project - validate_permission(author, project, :create_note) + validate_permission!(author, project, :create_note) raise NoteableNotFoundError unless sent_notification.noteable @@ -59,7 +59,7 @@ module Gitlab end def process_create_issue - validate_permission(message_sender, message_project, :create_issue) + validate_permission!(message_sender, message_project, :create_issue) issue = Issues::CreateService.new( message_project, @@ -78,7 +78,7 @@ module Gitlab end end - def validate_permission(author, project, permission) + def validate_permission!(author, project, permission) raise UserNotFoundError unless author raise UserBlockedError if author.blocked? # TODO: Give project not found error if author cannot read project -- cgit v1.2.1 From eac358334048f55e750a5426de938915af30b12f Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 1 Apr 2016 19:02:40 +0800 Subject: Update wording because we're overloading UserNotAuthorizedError --- app/workers/email_receiver_worker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 95469f1c486..64105dbc01d 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -36,7 +36,7 @@ class EmailReceiverWorker when Gitlab::Email::Receiver::UserBlockedError reason = "Your account has been blocked. If you believe this is in error, contact a staff member." when Gitlab::Email::Receiver::UserNotAuthorizedError - reason = "You are not allowed to respond to the thread you are replying to. If you believe this is in error, contact a staff member." + reason = "You are not allowed to perform this action. If you believe this is in error, contact a staff member." when Gitlab::Email::Receiver::NoteableNotFoundError reason = "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member." when Gitlab::Email::Receiver::InvalidNoteError, -- cgit v1.2.1 From 634c9f403bca2c720ed81585b4fb760963ba4dbe Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 7 Apr 2016 03:18:01 +0800 Subject: process_reply -> process_create_note; handle_reply -> process_reply --- lib/gitlab/email/receiver.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index b8065580b54..e02554eb473 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -22,7 +22,7 @@ module Gitlab raise EmptyEmailError if @raw.blank? if sent_notification - process_reply + process_create_note elsif message_project process_create_issue @@ -34,7 +34,7 @@ module Gitlab end private - def process_reply + def process_create_note raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/ author = sent_notification.recipient @@ -44,7 +44,7 @@ module Gitlab raise NoteableNotFoundError unless sent_notification.noteable - reply = handle_reply(project) + reply = process_reply(project) raise EmptyEmailError if reply.blank? note = create_note(reply) @@ -65,7 +65,7 @@ module Gitlab message_project, message_sender, title: message.subject, - description: handle_reply(message_project) + description: process_reply(message_project) ).execute unless issue.persisted? @@ -100,7 +100,7 @@ module Gitlab Project.find_with_namespace(reply_key) if reply_key end - def handle_reply(project) + def process_reply(project) reply = ReplyParser.new(message).execute.strip add_attachments(reply, project) -- cgit v1.2.1 From a065c8d5d82d7db3a01b1c23571ea010c82f7a31 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 7 Apr 2016 04:39:45 +0800 Subject: Create a new issue via: incoming+group/project+AUTH_TOKEN@... --- lib/gitlab/email/receiver.rb | 16 +++++++++++++++- spec/fixtures/emails/valid_new_issue.eml | 2 +- spec/fixtures/emails/valid_new_issue_empty.eml | 2 +- spec/fixtures/emails/wrong_authentication_token.eml | 18 ++++++++++++++++++ spec/lib/gitlab/email/receiver_spec.rb | 20 +++++++++++++++++++- 5 files changed, 54 insertions(+), 4 deletions(-) create mode 100644 spec/fixtures/emails/wrong_authentication_token.eml diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index e02554eb473..17293b94b6b 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -60,6 +60,7 @@ module Gitlab def process_create_issue validate_permission!(message_sender, message_project, :create_issue) + validate_authentication_token!(message_sender) issue = Issues::CreateService.new( message_project, @@ -85,6 +86,11 @@ module Gitlab raise UserNotAuthorizedError unless author.can?(permission, project) end + def validate_authentication_token!(author) + raise UserNotAuthorizedError unless author.authentication_token == + authentication_token + end + # Find the first matched user in database from email From: section # TODO: Since this address could be forged, we should have some kind of # auth token attached somewhere to verify the identity better. @@ -97,7 +103,7 @@ module Gitlab def message_project @message_project ||= - Project.find_with_namespace(reply_key) if reply_key + Project.find_with_namespace(project_namespace) if reply_key end def process_reply(project) @@ -118,6 +124,14 @@ module Gitlab key_from_to_header || key_from_additional_headers end + def authentication_token + reply_key[/[^\+]+$/] + end + + def project_namespace + reply_key[/^[^\+]+/] + end + def key_from_to_header key = nil message.to.each do |address| diff --git a/spec/fixtures/emails/valid_new_issue.eml b/spec/fixtures/emails/valid_new_issue.eml index a7e34b9846d..3cf53a656a5 100644 --- a/spec/fixtures/emails/valid_new_issue.eml +++ b/spec/fixtures/emails/valid_new_issue.eml @@ -5,7 +5,7 @@ Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for -To: incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo +To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo Message-ID: Subject: New Issue by email Mime-Version: 1.0 diff --git a/spec/fixtures/emails/valid_new_issue_empty.eml b/spec/fixtures/emails/valid_new_issue_empty.eml index 2173508d6f8..fc1d52a3f42 100644 --- a/spec/fixtures/emails/valid_new_issue_empty.eml +++ b/spec/fixtures/emails/valid_new_issue_empty.eml @@ -5,7 +5,7 @@ Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for -To: incoming+gitlabhq/gitlabhq@appmail.adventuretime.ooo +To: incoming+gitlabhq/gitlabhq+auth_token@appmail.adventuretime.ooo Message-ID: Subject: New Issue by email Mime-Version: 1.0 diff --git a/spec/fixtures/emails/wrong_authentication_token.eml b/spec/fixtures/emails/wrong_authentication_token.eml new file mode 100644 index 00000000000..0994c2f7775 --- /dev/null +++ b/spec/fixtures/emails/wrong_authentication_token.eml @@ -0,0 +1,18 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: incoming+gitlabhq/gitlabhq+bad_token@appmail.adventuretime.ooo +Message-ID: +Subject: New Issue by email +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index 4336f0f9e53..d1b52b9d086 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -171,7 +171,13 @@ describe Gitlab::Email::Receiver, lib: true do end let(:sent_notification) {} - let!(:user) { create(:user, email: 'jake@adventuretime.ooo') } + let!(:user) do + create( + :user, + email: 'jake@adventuretime.ooo', + authentication_token: 'auth_token' + ) + end let(:namespace) { create(:namespace, path: 'gitlabhq') } let(:project) { create(:project, :public, namespace: namespace) } let(:email_raw) { fixture_file('emails/valid_new_issue.eml') } @@ -215,6 +221,18 @@ describe Gitlab::Email::Receiver, lib: true do expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::InvalidIssueError) end end + + context "when the authentication_token token didn't match" do + let!(:email_raw) { fixture_file("emails/wrong_authentication_token.eml") } + + before do + project + end + + it "raises an UserNotAuthorizedError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotAuthorizedError) + end + end end end end -- cgit v1.2.1 From 30b34437795eec6f55fb269cb6eff4a4f9da9cfc Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 7 Apr 2016 04:40:50 +0800 Subject: Now we would be validating authentication token --- lib/gitlab/email/receiver.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 17293b94b6b..471d10a11a6 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -91,9 +91,6 @@ module Gitlab authentication_token end - # Find the first matched user in database from email From: section - # TODO: Since this address could be forged, we should have some kind of - # auth token attached somewhere to verify the identity better. def message_sender @message_sender ||= message.from.find do |email| user = User.find_by_any_email(email) -- cgit v1.2.1 From 8156475ea501221c3ba1bf3280e8368b354839bc Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 7 Apr 2016 05:27:28 +0800 Subject: Report better errors. TODO: Enable skipped test --- app/workers/email_receiver_worker.rb | 2 ++ lib/gitlab/email/receiver.rb | 11 +++++++++-- spec/lib/gitlab/email/receiver_spec.rb | 25 ++++++++++++++++++++----- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 64105dbc01d..544b23da85e 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -26,6 +26,8 @@ class EmailReceiverWorker case e when Gitlab::Email::Receiver::SentNotificationNotFoundError reason = "We couldn't figure out what the email is in reply to. Please create your comment through the web interface." + when Gitlab::Email::Receiver::ProjectNotFound + reason = "We couldn't find the project. Please check if there's any typo." when Gitlab::Email::Receiver::EmptyEmailError can_retry = true reason = "It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies." diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 471d10a11a6..50c16da4ef6 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -5,6 +5,7 @@ module Gitlab class ProcessingError < StandardError; end class EmailUnparsableError < ProcessingError; end class SentNotificationNotFoundError < ProcessingError; end + class ProjectNotFound < ProcessingError; end class EmptyEmailError < ProcessingError; end class AutoGeneratedEmailError < ProcessingError; end class UserNotFoundError < ProcessingError; end @@ -25,10 +26,16 @@ module Gitlab process_create_note elsif message_project - process_create_issue + if message_sender.can?(:read_project, message_project) + process_create_issue + else # Must be private project without access + raise ProjectNotFound + end + elsif reply_key =~ %r{/|\+} + # Sent Notification reply_key would not have / or + + raise ProjectNotFound else - # TODO: could also be project not found raise SentNotificationNotFoundError end end diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index d1b52b9d086..c0fc18a9f83 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -210,10 +210,12 @@ describe Gitlab::Email::Receiver, lib: true do end context "something is wrong" do + before do + project + end + context "when the issue could not be saved" do before do - project - allow_any_instance_of(Issue).to receive(:persisted?).and_return(false) end @@ -225,11 +227,24 @@ describe Gitlab::Email::Receiver, lib: true do context "when the authentication_token token didn't match" do let!(:email_raw) { fixture_file("emails/wrong_authentication_token.eml") } - before do - project + it "raises an UserNotAuthorizedError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotAuthorizedError) end + end + + context "when project is private" do + let(:project) { create(:project, :private, namespace: namespace) } + + it "raises a ProjectNotFound if the user is not a member" do + expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::ProjectNotFound) + end + + it "raises a UserNotAuthorizedError if the user has no sufficient permission" do + skip("Find a role which can :read_project but can't :create_issue") + + project.update(group: create(:group)) + project.group.add_guest(user) - it "raises an UserNotAuthorizedError" do expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotAuthorizedError) end end -- cgit v1.2.1 From 3f4a6412dcc35c182d993cd1350459e8a4a1b8d1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 7 Apr 2016 05:46:26 +0800 Subject: We should totally cache it --- lib/gitlab/email/receiver.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 50c16da4ef6..5f85f7ad03f 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -158,9 +158,7 @@ module Gitlab end def sent_notification - return nil unless reply_key - - SentNotification.for(reply_key) + @sent_notification ||= SentNotification.for(reply_key) if reply_key end def add_attachments(reply, project) -- cgit v1.2.1 From c337e748d385fea5c768a8e7de55975dca7fa484 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 18 May 2016 17:19:25 -0500 Subject: so we use separate classes to handle different tasks --- app/workers/email_receiver_worker.rb | 20 +-- lib/gitlab/email/handler.rb | 55 +++++++++ lib/gitlab/email/handler/create_issue.rb | 62 ++++++++++ lib/gitlab/email/handler/create_note.rb | 55 +++++++++ lib/gitlab/email/receiver.rb | 191 ++++++----------------------- lib/gitlab/incoming_email.rb | 6 +- spec/lib/gitlab/email/receiver_spec.rb | 28 ++--- spec/workers/email_receiver_worker_spec.rb | 2 +- 8 files changed, 240 insertions(+), 179 deletions(-) create mode 100644 lib/gitlab/email/handler.rb create mode 100644 lib/gitlab/email/handler/create_issue.rb create mode 100644 lib/gitlab/email/handler/create_note.rb diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 544b23da85e..af9006d8dde 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -24,25 +24,25 @@ class EmailReceiverWorker reason = nil case e - when Gitlab::Email::Receiver::SentNotificationNotFoundError + when Gitlab::Email::SentNotificationNotFoundError reason = "We couldn't figure out what the email is in reply to. Please create your comment through the web interface." - when Gitlab::Email::Receiver::ProjectNotFound + when Gitlab::Email::ProjectNotFound reason = "We couldn't find the project. Please check if there's any typo." - when Gitlab::Email::Receiver::EmptyEmailError + when Gitlab::Email::EmptyEmailError can_retry = true reason = "It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies." - when Gitlab::Email::Receiver::AutoGeneratedEmailError + when Gitlab::Email::AutoGeneratedEmailError reason = "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface." - when Gitlab::Email::Receiver::UserNotFoundError + when Gitlab::Email::UserNotFoundError reason = "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface." - when Gitlab::Email::Receiver::UserBlockedError + when Gitlab::Email::UserBlockedError reason = "Your account has been blocked. If you believe this is in error, contact a staff member." - when Gitlab::Email::Receiver::UserNotAuthorizedError + when Gitlab::Email::UserNotAuthorizedError reason = "You are not allowed to perform this action. If you believe this is in error, contact a staff member." - when Gitlab::Email::Receiver::NoteableNotFoundError + when Gitlab::Email::NoteableNotFoundError reason = "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member." - when Gitlab::Email::Receiver::InvalidNoteError, - Gitlab::Email::Receiver::InvalidIssueError + when Gitlab::Email::InvalidNoteError, + Gitlab::Email::InvalidIssueError can_retry = true reason = e.message else diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb new file mode 100644 index 00000000000..55fbee276b8 --- /dev/null +++ b/lib/gitlab/email/handler.rb @@ -0,0 +1,55 @@ + +module Gitlab + module Email + class Handler + attr_reader :mail, :mail_key + + def initialize(mail, mail_key) + @mail = mail + @mail_key = mail_key + end + + def message + @message ||= process_message + end + + def author + raise NotImplementedError + end + + def project + raise NotImplementedError + end + + private + def validate_permission!(permission) + raise UserNotFoundError unless author + raise UserBlockedError if author.blocked? + # TODO: Give project not found error if author cannot read project + raise UserNotAuthorizedError unless author.can?(permission, project) + end + + def process_message + add_attachments(ReplyParser.new(mail).execute.strip) + end + + def add_attachments(reply) + attachments = Email::AttachmentUploader.new(mail).execute(project) + + reply + attachments.map do |link| + "\n\n#{link[:markdown]}" + end.join + end + + def verify_record(record, exception, error_title) + return if record.persisted? + + msg = error_title + record.errors.full_messages.map do |error| + "\n\n- #{error}" + end.join + + raise exception, msg + end + end + end +end diff --git a/lib/gitlab/email/handler/create_issue.rb b/lib/gitlab/email/handler/create_issue.rb new file mode 100644 index 00000000000..24f8f59900d --- /dev/null +++ b/lib/gitlab/email/handler/create_issue.rb @@ -0,0 +1,62 @@ + +require 'gitlab/email/handler' + +module Gitlab + module Email + class Handler + class CreateIssue < Handler + def can_handle? + !!project + end + + def execute + # Must be private project without access + raise ProjectNotFound unless author.can?(:read_project, project) + + validate_permission!(:create_issue) + validate_authentication_token! + + verify_record( + create_issue, + InvalidIssueError, + "The issue could not be created for the following reasons:" + ) + end + + def author + @author ||= mail.from.find do |email| + user = User.find_by_any_email(email) + break user if user + end + end + + def project + @project ||= Project.find_with_namespace(project_namespace) + end + + private + def authentication_token + mail_key[/[^\+]+$/] + end + + def project_namespace + mail_key[/^[^\+]+/] + end + + def create_issue + Issues::CreateService.new( + project, + author, + title: mail.subject, + description: message + ).execute + end + + def validate_authentication_token! + raise UserNotAuthorizedError unless author.authentication_token == + authentication_token + end + end + end + end +end diff --git a/lib/gitlab/email/handler/create_note.rb b/lib/gitlab/email/handler/create_note.rb new file mode 100644 index 00000000000..32deb5a311e --- /dev/null +++ b/lib/gitlab/email/handler/create_note.rb @@ -0,0 +1,55 @@ + +require 'gitlab/email/handler' + +module Gitlab + module Email + class Handler + class CreateNote < Handler + def can_handle? + !!sent_notification + end + + def execute + raise SentNotificationNotFoundError unless sent_notification + raise AutoGeneratedEmailError if mail.header.to_s =~ /auto-(generated|replied)/ + + validate_permission!(:create_note) + + raise NoteableNotFoundError unless sent_notification.noteable + raise EmptyEmailError if message.blank? + + verify_record( + create_note, + InvalidNoteError, + "The comment could not be created for the following reasons:" + ) + end + + def author + sent_notification.recipient + end + + def project + sent_notification.project + end + + def sent_notification + @sent_notification ||= SentNotification.for(mail_key) + end + + private + def create_note + Notes::CreateService.new( + project, + author, + note: message, + noteable_type: sent_notification.noteable_type, + noteable_id: sent_notification.noteable_id, + commit_id: sent_notification.commit_id, + line_code: sent_notification.line_code + ).execute + end + end + end + end +end diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 5f85f7ad03f..01a206666a4 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -1,186 +1,75 @@ + +require 'gitlab/email/handler/create_note' +require 'gitlab/email/handler/create_issue' + # Inspired in great part by Discourse's Email::Receiver module Gitlab module Email + class ProcessingError < StandardError; end + class EmailUnparsableError < ProcessingError; end + class SentNotificationNotFoundError < ProcessingError; end + class ProjectNotFound < ProcessingError; end + class EmptyEmailError < ProcessingError; end + class AutoGeneratedEmailError < ProcessingError; end + class UserNotFoundError < ProcessingError; end + class UserBlockedError < ProcessingError; end + class UserNotAuthorizedError < ProcessingError; end + class NoteableNotFoundError < ProcessingError; end + class InvalidNoteError < ProcessingError; end + class InvalidIssueError < ProcessingError; end + class Receiver - class ProcessingError < StandardError; end - class EmailUnparsableError < ProcessingError; end - class SentNotificationNotFoundError < ProcessingError; end - class ProjectNotFound < ProcessingError; end - class EmptyEmailError < ProcessingError; end - class AutoGeneratedEmailError < ProcessingError; end - class UserNotFoundError < ProcessingError; end - class UserBlockedError < ProcessingError; end - class UserNotAuthorizedError < ProcessingError; end - class NoteableNotFoundError < ProcessingError; end - class InvalidNoteError < ProcessingError; end - class InvalidIssueError < ProcessingError; end + attr_reader :mail def initialize(raw) - @raw = raw + raise EmptyEmailError if raw.blank? + @mail = build_mail(raw) end def execute - raise EmptyEmailError if @raw.blank? + mail_key = extract_mail_key + raise SentNotificationNotFoundError unless mail_key - if sent_notification - process_create_note - - elsif message_project - if message_sender.can?(:read_project, message_project) - process_create_issue - else # Must be private project without access - raise ProjectNotFound - end - - elsif reply_key =~ %r{/|\+} - # Sent Notification reply_key would not have / or + + if handler = find_handler(mail, mail_key) + handler.execute + elsif mail_key =~ %r{/|\+} + # Sent Notification mail_key would not have / or + raise ProjectNotFound else raise SentNotificationNotFoundError end end - private - def process_create_note - raise AutoGeneratedEmailError if message.header.to_s =~ /auto-(generated|replied)/ - - author = sent_notification.recipient - project = sent_notification.project - - validate_permission!(author, project, :create_note) - - raise NoteableNotFoundError unless sent_notification.noteable - - reply = process_reply(project) - raise EmptyEmailError if reply.blank? - note = create_note(reply) - - unless note.persisted? - msg = "The comment could not be created for the following reasons:" - note.errors.full_messages.each do |error| - msg << "\n\n- #{error}" - end - - raise InvalidNoteError, msg - end - end - - def process_create_issue - validate_permission!(message_sender, message_project, :create_issue) - validate_authentication_token!(message_sender) - - issue = Issues::CreateService.new( - message_project, - message_sender, - title: message.subject, - description: process_reply(message_project) - ).execute - - unless issue.persisted? - msg = "The issue could not be created for the following reasons:" - issue.errors.full_messages.each do |error| - msg << "\n\n- #{error}" - end - - raise InvalidIssueError, msg - end - end - - def validate_permission!(author, project, permission) - raise UserNotFoundError unless author - raise UserBlockedError if author.blocked? - # TODO: Give project not found error if author cannot read project - raise UserNotAuthorizedError unless author.can?(permission, project) - end - - def validate_authentication_token!(author) - raise UserNotAuthorizedError unless author.authentication_token == - authentication_token - end - - def message_sender - @message_sender ||= message.from.find do |email| - user = User.find_by_any_email(email) - break user if user - end - end - - def message_project - @message_project ||= - Project.find_with_namespace(project_namespace) if reply_key - end - - def process_reply(project) - reply = ReplyParser.new(message).execute.strip - - add_attachments(reply, project) - - reply - end - - def message - @message ||= Mail::Message.new(@raw) - rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e + def build_mail(raw) + Mail::Message.new(raw) + rescue Encoding::UndefinedConversionError, + Encoding::InvalidByteSequenceError => e raise EmailUnparsableError, e end - def reply_key + def extract_mail_key key_from_to_header || key_from_additional_headers end - def authentication_token - reply_key[/[^\+]+$/] - end - - def project_namespace - reply_key[/^[^\+]+/] - end - def key_from_to_header - key = nil - message.to.each do |address| + mail.to.find do |address| key = Gitlab::IncomingEmail.key_from_address(address) - break if key + break key if key end - - key end def key_from_additional_headers - reply_key = nil - - Array(message.references).each do |message_id| - reply_key = Gitlab::IncomingEmail.key_from_fallback_reply_message_id(message_id) - break if reply_key + Array(mail.references).find do |mail_id| + key = Gitlab::IncomingEmail.key_from_fallback_reply_mail_id(mail_id) + break key if key end - - reply_key end - def sent_notification - @sent_notification ||= SentNotification.for(reply_key) if reply_key - end - - def add_attachments(reply, project) - attachments = Email::AttachmentUploader.new(message).execute(project) - - attachments.each do |link| - reply << "\n\n#{link[:markdown]}" + def find_handler(mail, mail_key) + [Handler::CreateNote, Handler::CreateIssue].find do |klass| + handler = klass.new(mail, mail_key) + break handler if handler.can_handle? end - - reply - end - - def create_note(reply) - Notes::CreateService.new( - sent_notification.project, - sent_notification.recipient, - note: reply, - noteable_type: sent_notification.noteable_type, - noteable_id: sent_notification.noteable_id, - commit_id: sent_notification.commit_id, - line_code: sent_notification.line_code - ).execute end end end diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb index 8ce9d32abe0..f4ef1559716 100644 --- a/lib/gitlab/incoming_email.rb +++ b/lib/gitlab/incoming_email.rb @@ -1,7 +1,7 @@ module Gitlab module IncomingEmail class << self - FALLBACK_REPLY_MESSAGE_ID_REGEX = /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\Z/.freeze + FALLBACK_REPLY_MAIL_ID_REGEX = /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\Z/.freeze def enabled? config.enabled && config.address @@ -21,8 +21,8 @@ module Gitlab match[1] end - def key_from_fallback_reply_message_id(message_id) - match = message_id.match(FALLBACK_REPLY_MESSAGE_ID_REGEX) + def key_from_fallback_reply_mail_id(mail_id) + match = mail_id.match(FALLBACK_REPLY_MAIL_ID_REGEX) return unless match match[1] diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index c0fc18a9f83..58c525f4048 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -34,7 +34,7 @@ describe Gitlab::Email::Receiver, lib: true do let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(reply_key, "") } it "raises a SentNotificationNotFoundError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError) + expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError) end end @@ -42,7 +42,7 @@ describe Gitlab::Email::Receiver, lib: true do let(:email_raw) { fixture_file('emails/wrong_reply_key.eml') } it "raises a SentNotificationNotFoundError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::SentNotificationNotFoundError) + expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError) end end @@ -50,7 +50,7 @@ describe Gitlab::Email::Receiver, lib: true do let(:email_raw) { "" } it "raises an EmptyEmailError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError) + expect { receiver.execute }.to raise_error(Gitlab::Email::EmptyEmailError) end end @@ -59,7 +59,7 @@ describe Gitlab::Email::Receiver, lib: true do let!(:email_raw) { fixture_file("emails/auto_reply.eml") } it "raises an AutoGeneratedEmailError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::AutoGeneratedEmailError) + expect { receiver.execute }.to raise_error(Gitlab::Email::AutoGeneratedEmailError) end end @@ -69,7 +69,7 @@ describe Gitlab::Email::Receiver, lib: true do end it "raises a UserNotFoundError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotFoundError) + expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError) end end @@ -79,7 +79,7 @@ describe Gitlab::Email::Receiver, lib: true do end it "raises a UserBlockedError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserBlockedError) + expect { receiver.execute }.to raise_error(Gitlab::Email::UserBlockedError) end end @@ -89,7 +89,7 @@ describe Gitlab::Email::Receiver, lib: true do end it "raises a UserNotAuthorizedError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotAuthorizedError) + expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotAuthorizedError) end end @@ -99,7 +99,7 @@ describe Gitlab::Email::Receiver, lib: true do end it "raises a NoteableNotFoundError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::NoteableNotFoundError) + expect { receiver.execute }.to raise_error(Gitlab::Email::NoteableNotFoundError) end end @@ -107,7 +107,7 @@ describe Gitlab::Email::Receiver, lib: true do let!(:email_raw) { fixture_file("emails/no_content_reply.eml") } it "raises an EmptyEmailError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError) + expect { receiver.execute }.to raise_error(Gitlab::Email::EmptyEmailError) end end @@ -117,7 +117,7 @@ describe Gitlab::Email::Receiver, lib: true do end it "raises an InvalidNoteError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::InvalidNoteError) + expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError) end end @@ -220,7 +220,7 @@ describe Gitlab::Email::Receiver, lib: true do end it "raises an InvalidIssueError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::InvalidIssueError) + expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidIssueError) end end @@ -228,7 +228,7 @@ describe Gitlab::Email::Receiver, lib: true do let!(:email_raw) { fixture_file("emails/wrong_authentication_token.eml") } it "raises an UserNotAuthorizedError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotAuthorizedError) + expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotAuthorizedError) end end @@ -236,7 +236,7 @@ describe Gitlab::Email::Receiver, lib: true do let(:project) { create(:project, :private, namespace: namespace) } it "raises a ProjectNotFound if the user is not a member" do - expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::ProjectNotFound) + expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound) end it "raises a UserNotAuthorizedError if the user has no sufficient permission" do @@ -245,7 +245,7 @@ describe Gitlab::Email::Receiver, lib: true do project.update(group: create(:group)) project.group.add_guest(user) - expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::UserNotAuthorizedError) + expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotAuthorizedError) end end end diff --git a/spec/workers/email_receiver_worker_spec.rb b/spec/workers/email_receiver_worker_spec.rb index de40a6f78af..fe70501eeac 100644 --- a/spec/workers/email_receiver_worker_spec.rb +++ b/spec/workers/email_receiver_worker_spec.rb @@ -17,7 +17,7 @@ describe EmailReceiverWorker do context "when an error occurs" do before do - allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(Gitlab::Email::Receiver::EmptyEmailError) + allow_any_instance_of(Gitlab::Email::Receiver).to receive(:execute).and_raise(Gitlab::Email::EmptyEmailError) end it "sends out a rejection email" do -- cgit v1.2.1 From e7a6d17b2b9a3a1b1cae0733da46ddedfe2dc3b1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 18 May 2016 17:43:00 -0500 Subject: fix some styling offender --- app/controllers/omniauth_callbacks_controller.rb | 2 +- app/models/ci/build.rb | 4 ++-- app/models/project.rb | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index df98f56a1cd..f35d631df0c 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -97,7 +97,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController handle_signup_error end - def handle_service_ticket provider, ticket + def handle_service_ticket(provider, ticket) Gitlab::OAuth::Session.create provider, ticket session[:service_tickets] ||= {} session[:service_tickets][provider] = ticket diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 92327bdb08d..e8de22ddaf7 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -285,8 +285,8 @@ module Ci project.runners_token end - def valid_token? token - project.valid_runners_token? token + def valid_token?(token) + project.valid_runners_token?(token) end def can_be_served?(runner) diff --git a/app/models/project.rb b/app/models/project.rb index a3c4f1d8e9b..b32a30142c8 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -947,13 +947,13 @@ class Project < ActiveRecord::Base shared_runners_enabled? && Ci::Runner.shared.active.any?(&block) end - def valid_runners_token? token + def valid_runners_token?(token) self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token) end # TODO (ayufan): For now we use runners_token (backward compatibility) # In 8.4 every build will have its own individual token valid for time of build - def valid_build_token? token + def valid_build_token?(token) self.builds_enabled? && self.runners_token && ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.runners_token) end -- cgit v1.2.1 From a7c823a5730fade9d8cc2c992c0f80cc12b1c0a7 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 18 May 2016 17:57:14 -0500 Subject: Give ProjectNotFound when the project is not readable --- lib/gitlab/email/handler.rb | 2 +- lib/gitlab/email/handler/create_issue.rb | 3 --- spec/lib/gitlab/email/receiver_spec.rb | 4 ++-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb index 55fbee276b8..56d848cdd7b 100644 --- a/lib/gitlab/email/handler.rb +++ b/lib/gitlab/email/handler.rb @@ -25,7 +25,7 @@ module Gitlab def validate_permission!(permission) raise UserNotFoundError unless author raise UserBlockedError if author.blocked? - # TODO: Give project not found error if author cannot read project + raise ProjectNotFound unless author.can?(:read_project, project) raise UserNotAuthorizedError unless author.can?(permission, project) end diff --git a/lib/gitlab/email/handler/create_issue.rb b/lib/gitlab/email/handler/create_issue.rb index 24f8f59900d..8829fbb0e49 100644 --- a/lib/gitlab/email/handler/create_issue.rb +++ b/lib/gitlab/email/handler/create_issue.rb @@ -10,9 +10,6 @@ module Gitlab end def execute - # Must be private project without access - raise ProjectNotFound unless author.can?(:read_project, project) - validate_permission!(:create_issue) validate_authentication_token! diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index 58c525f4048..a9b93044a08 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -88,8 +88,8 @@ describe Gitlab::Email::Receiver, lib: true do project.update_attribute(:visibility_level, Project::PRIVATE) end - it "raises a UserNotAuthorizedError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotAuthorizedError) + it "raises a ProjectNotFound" do + expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound) end end -- cgit v1.2.1 From 4b341dea5547889fd8fe6239d28c453de194c7db Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 20 May 2016 12:52:53 -0500 Subject: Actually there's no such case --- spec/lib/gitlab/email/receiver_spec.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index a9b93044a08..c08e184c4c3 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -238,15 +238,6 @@ describe Gitlab::Email::Receiver, lib: true do it "raises a ProjectNotFound if the user is not a member" do expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound) end - - it "raises a UserNotAuthorizedError if the user has no sufficient permission" do - skip("Find a role which can :read_project but can't :create_issue") - - project.update(group: create(:group)) - project.group.add_guest(user) - - expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotAuthorizedError) - end end end end -- cgit v1.2.1 From 9436a444f3e890ad48b64c26ec95ffde4ed9e87c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 20 May 2016 13:05:57 -0500 Subject: Rename reply_key to mail_key --- spec/fixtures/emails/wrong_mail_key.eml | 40 ++++++++++++++++++++++++++++++++ spec/fixtures/emails/wrong_reply_key.eml | 40 -------------------------------- spec/lib/gitlab/email/receiver_spec.rb | 22 +++++++++--------- 3 files changed, 51 insertions(+), 51 deletions(-) create mode 100644 spec/fixtures/emails/wrong_mail_key.eml delete mode 100644 spec/fixtures/emails/wrong_reply_key.eml diff --git a/spec/fixtures/emails/wrong_mail_key.eml b/spec/fixtures/emails/wrong_mail_key.eml new file mode 100644 index 00000000000..491e078fb5b --- /dev/null +++ b/spec/fixtures/emails/wrong_mail_key.eml @@ -0,0 +1,40 @@ +Return-Path: +Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 +Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 +Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 +Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 +Date: Thu, 13 Jun 2013 17:03:48 -0400 +From: Jake the Dog +To: reply+QQd8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo +Message-ID: +Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' +Mime-Version: 1.0 +Content-Type: text/plain; + charset=ISO-8859-1 +Content-Transfer-Encoding: 7bit +X-Sieve: CMU Sieve 2.2 +X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, + 13 Jun 2013 14:03:48 -0700 (PDT) +X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 + +I could not disagree more. I am obviously biased but adventure time is the +greatest show ever created. Everyone should watch it. + +- Jake out + + +On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta + wrote: +> +> +> +> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: +> +> --- +> hey guys everyone knows adventure time sucks! +> +> --- +> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 +> +> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). +> \ No newline at end of file diff --git a/spec/fixtures/emails/wrong_reply_key.eml b/spec/fixtures/emails/wrong_reply_key.eml deleted file mode 100644 index 491e078fb5b..00000000000 --- a/spec/fixtures/emails/wrong_reply_key.eml +++ /dev/null @@ -1,40 +0,0 @@ -Return-Path: -Received: from iceking.adventuretime.ooo ([unix socket]) by iceking (Cyrus v2.2.13-Debian-2.2.13-19+squeeze3) with LMTPA; Thu, 13 Jun 2013 17:03:50 -0400 -Received: from mail-ie0-x234.google.com (mail-ie0-x234.google.com [IPv6:2607:f8b0:4001:c03::234]) by iceking.adventuretime.ooo (8.14.3/8.14.3/Debian-9.4) with ESMTP id r5DL3nFJ016967 (version=TLSv1/SSLv3 cipher=RC4-SHA bits=128 verify=NOT) for ; Thu, 13 Jun 2013 17:03:50 -0400 -Received: by mail-ie0-f180.google.com with SMTP id f4so21977375iea.25 for ; Thu, 13 Jun 2013 14:03:48 -0700 -Received: by 10.0.0.1 with HTTP; Thu, 13 Jun 2013 14:03:48 -0700 -Date: Thu, 13 Jun 2013 17:03:48 -0400 -From: Jake the Dog -To: reply+QQd8df8370b7e95c5a49fbf86aeb2c93@appmail.adventuretime.ooo -Message-ID: -Subject: re: [Discourse Meta] eviltrout posted in 'Adventure Time Sux' -Mime-Version: 1.0 -Content-Type: text/plain; - charset=ISO-8859-1 -Content-Transfer-Encoding: 7bit -X-Sieve: CMU Sieve 2.2 -X-Received: by 10.0.0.1 with SMTP id n7mr11234144ipb.85.1371157428600; Thu, - 13 Jun 2013 14:03:48 -0700 (PDT) -X-Scanned-By: MIMEDefang 2.69 on IPv6:2001:470:1d:165::1 - -I could not disagree more. I am obviously biased but adventure time is the -greatest show ever created. Everyone should watch it. - -- Jake out - - -On Sun, Jun 9, 2013 at 1:39 PM, eviltrout via Discourse Meta - wrote: -> -> -> -> eviltrout posted in 'Adventure Time Sux' on Discourse Meta: -> -> --- -> hey guys everyone knows adventure time sucks! -> -> --- -> Please visit this link to respond: http://localhost:3000/t/adventure-time-sux/1234/3 -> -> To unsubscribe from these emails, visit your [user preferences](http://localhost:3000/user_preferences). -> \ No newline at end of file diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index c08e184c4c3..e892da7bb46 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -6,13 +6,13 @@ describe Gitlab::Email::Receiver, lib: true do stub_config_setting(host: 'localhost') end - let(:reply_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } + let(:mail_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } let(:email_raw) { fixture_file('emails/valid_reply.eml') } let(:project) { create(:project, :public) } let(:noteable) { create(:issue, project: project) } let(:user) { create(:user) } - let!(:sent_notification) { SentNotification.record(noteable, user.id, reply_key) } + let!(:sent_notification) { SentNotification.record(noteable, user.id, mail_key) } let(:receiver) { described_class.new(email_raw) } let(:markdown) { "![image](uploads/image.png)" } @@ -30,16 +30,16 @@ describe Gitlab::Email::Receiver, lib: true do ) end - context "when the recipient address doesn't include a reply key" do - let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(reply_key, "") } + context "when the recipient address doesn't include a mail key" do + let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "") } it "raises a SentNotificationNotFoundError" do expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError) end end - context "when no sent notification for the reply key could be found" do - let(:email_raw) { fixture_file('emails/wrong_reply_key.eml') } + context "when no sent notification for the mail key could be found" do + let(:email_raw) { fixture_file('emails/wrong_mail_key.eml') } it "raises a SentNotificationNotFoundError" do expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError) @@ -55,7 +55,7 @@ describe Gitlab::Email::Receiver, lib: true do end context "when the email was auto generated" do - let!(:reply_key) { '636ca428858779856c226bb145ef4fad' } + let!(:mail_key) { '636ca428858779856c226bb145ef4fad' } let!(:email_raw) { fixture_file("emails/auto_reply.eml") } it "raises an AutoGeneratedEmailError" do @@ -147,8 +147,8 @@ describe Gitlab::Email::Receiver, lib: true do stub_incoming_email_setting(enabled: true, address: nil) end - shared_examples 'an email that contains a reply key' do |header| - it "fetches the reply key from the #{header} header and creates a comment" do + shared_examples 'an email that contains a mail key' do |header| + it "fetches the mail key from the #{header} header and creates a comment" do expect { receiver.execute }.to change { noteable.notes.count }.by(1) note = noteable.notes.last @@ -157,10 +157,10 @@ describe Gitlab::Email::Receiver, lib: true do end end - context 'reply key is in the References header' do + context 'mail key is in the References header' do let(:email_raw) { fixture_file('emails/reply_without_subaddressing_and_key_inside_references.eml') } - it_behaves_like 'an email that contains a reply key', 'References' + it_behaves_like 'an email that contains a mail key', 'References' end end end -- cgit v1.2.1 From a7f6b75e7fba69964e84a0ae96c77650c66bb031 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 20 May 2016 15:36:25 -0500 Subject: Fix a missed rename --- spec/lib/gitlab/incoming_email_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb index afb3e26f8fb..bb1fe089206 100644 --- a/spec/lib/gitlab/incoming_email_spec.rb +++ b/spec/lib/gitlab/incoming_email_spec.rb @@ -43,9 +43,9 @@ describe Gitlab::IncomingEmail, lib: true do end end - context 'self.key_from_fallback_reply_message_id' do + context 'self.key_from_fallback_reply_mail_id' do it 'returns reply key' do - expect(described_class.key_from_fallback_reply_message_id('reply-key@localhost')).to eq('key') + expect(described_class.key_from_fallback_reply_mail_id('reply-key@localhost')).to eq('key') end end end -- cgit v1.2.1 From c2bc15a7669b8f21b12314f8607a02cf7d8b4828 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 20 May 2016 17:38:08 -0500 Subject: Use the authentication_token for finding the user --- lib/gitlab/email/handler/create_issue.rb | 11 +---------- spec/lib/gitlab/email/receiver_spec.rb | 6 +++--- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/gitlab/email/handler/create_issue.rb b/lib/gitlab/email/handler/create_issue.rb index 8829fbb0e49..72d49ec6c96 100644 --- a/lib/gitlab/email/handler/create_issue.rb +++ b/lib/gitlab/email/handler/create_issue.rb @@ -11,7 +11,6 @@ module Gitlab def execute validate_permission!(:create_issue) - validate_authentication_token! verify_record( create_issue, @@ -21,10 +20,7 @@ module Gitlab end def author - @author ||= mail.from.find do |email| - user = User.find_by_any_email(email) - break user if user - end + @author ||= User.find_by(authentication_token: authentication_token) end def project @@ -48,11 +44,6 @@ module Gitlab description: message ).execute end - - def validate_authentication_token! - raise UserNotAuthorizedError unless author.authentication_token == - authentication_token - end end end end diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index e892da7bb46..a9e2be0ad47 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -224,11 +224,11 @@ describe Gitlab::Email::Receiver, lib: true do end end - context "when the authentication_token token didn't match" do + context "when we can't find the authentication_token" do let!(:email_raw) { fixture_file("emails/wrong_authentication_token.eml") } - it "raises an UserNotAuthorizedError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotAuthorizedError) + it "raises an UserNotFoundError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError) end end -- cgit v1.2.1 From 32eae15f2f7ada5b5008f0fe9fb46f349cd4f367 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 20 May 2016 18:21:58 -0500 Subject: It's for Message-ID so it should be message_id --- lib/gitlab/email/receiver.rb | 2 +- lib/gitlab/incoming_email.rb | 6 +++--- spec/lib/gitlab/incoming_email_spec.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 01a206666a4..daa1177a23a 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -60,7 +60,7 @@ module Gitlab def key_from_additional_headers Array(mail.references).find do |mail_id| - key = Gitlab::IncomingEmail.key_from_fallback_reply_mail_id(mail_id) + key = Gitlab::IncomingEmail.key_from_fallback_message_id(mail_id) break key if key end end diff --git a/lib/gitlab/incoming_email.rb b/lib/gitlab/incoming_email.rb index f4ef1559716..d7be50bd437 100644 --- a/lib/gitlab/incoming_email.rb +++ b/lib/gitlab/incoming_email.rb @@ -1,7 +1,7 @@ module Gitlab module IncomingEmail class << self - FALLBACK_REPLY_MAIL_ID_REGEX = /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\Z/.freeze + FALLBACK_MESSAGE_ID_REGEX = /\Areply\-(.+)@#{Gitlab.config.gitlab.host}\Z/.freeze def enabled? config.enabled && config.address @@ -21,8 +21,8 @@ module Gitlab match[1] end - def key_from_fallback_reply_mail_id(mail_id) - match = mail_id.match(FALLBACK_REPLY_MAIL_ID_REGEX) + def key_from_fallback_message_id(mail_id) + match = mail_id.match(FALLBACK_MESSAGE_ID_REGEX) return unless match match[1] diff --git a/spec/lib/gitlab/incoming_email_spec.rb b/spec/lib/gitlab/incoming_email_spec.rb index bb1fe089206..1dcf2c0668b 100644 --- a/spec/lib/gitlab/incoming_email_spec.rb +++ b/spec/lib/gitlab/incoming_email_spec.rb @@ -43,9 +43,9 @@ describe Gitlab::IncomingEmail, lib: true do end end - context 'self.key_from_fallback_reply_mail_id' do + context 'self.key_from_fallback_message_id' do it 'returns reply key' do - expect(described_class.key_from_fallback_reply_mail_id('reply-key@localhost')).to eq('key') + expect(described_class.key_from_fallback_message_id('reply-key@localhost')).to eq('key') end end end -- cgit v1.2.1 From ee548b6ed0e8f885cdd7dfcc104ea1471ad62bd0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 20 May 2016 19:03:39 -0500 Subject: Only set @raw for receiver, and handle the rest in execute --- lib/gitlab/email/receiver.rb | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index daa1177a23a..da4299ebcb3 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -19,15 +19,16 @@ module Gitlab class InvalidIssueError < ProcessingError; end class Receiver - attr_reader :mail - def initialize(raw) - raise EmptyEmailError if raw.blank? - @mail = build_mail(raw) + @raw = raw end def execute - mail_key = extract_mail_key + raise EmptyEmailError if @raw.blank? + + mail = build_mail + mail_key = extract_mail_key(mail) + raise SentNotificationNotFoundError unless mail_key if handler = find_handler(mail, mail_key) @@ -40,25 +41,25 @@ module Gitlab end end - def build_mail(raw) - Mail::Message.new(raw) + def build_mail + Mail::Message.new(@raw) rescue Encoding::UndefinedConversionError, Encoding::InvalidByteSequenceError => e raise EmailUnparsableError, e end - def extract_mail_key - key_from_to_header || key_from_additional_headers + def extract_mail_key(mail) + key_from_to_header(mail) || key_from_additional_headers(mail) end - def key_from_to_header + def key_from_to_header(mail) mail.to.find do |address| key = Gitlab::IncomingEmail.key_from_address(address) break key if key end end - def key_from_additional_headers + def key_from_additional_headers(mail) Array(mail.references).find do |mail_id| key = Gitlab::IncomingEmail.key_from_fallback_message_id(mail_id) break key if key -- cgit v1.2.1 From 75415663f84ac006c5a4d5a6896ece50c299c03e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 21 May 2016 09:40:08 -0700 Subject: Rename handlers and introduce Handler.for --- lib/gitlab/email/handler.rb | 57 ++++-------------------- lib/gitlab/email/handler/base_handler.rb | 57 ++++++++++++++++++++++++ lib/gitlab/email/handler/create_issue.rb | 50 --------------------- lib/gitlab/email/handler/create_issue_handler.rb | 50 +++++++++++++++++++++ lib/gitlab/email/handler/create_note.rb | 55 ----------------------- lib/gitlab/email/handler/create_note_handler.rb | 55 +++++++++++++++++++++++ lib/gitlab/email/receiver.rb | 12 +---- 7 files changed, 173 insertions(+), 163 deletions(-) create mode 100644 lib/gitlab/email/handler/base_handler.rb delete mode 100644 lib/gitlab/email/handler/create_issue.rb create mode 100644 lib/gitlab/email/handler/create_issue_handler.rb delete mode 100644 lib/gitlab/email/handler/create_note.rb create mode 100644 lib/gitlab/email/handler/create_note_handler.rb diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb index 56d848cdd7b..b9221f1210c 100644 --- a/lib/gitlab/email/handler.rb +++ b/lib/gitlab/email/handler.rb @@ -1,54 +1,15 @@ +require 'gitlab/email/handler/create_note_handler' +require 'gitlab/email/handler/create_issue_handler' + module Gitlab module Email - class Handler - attr_reader :mail, :mail_key - - def initialize(mail, mail_key) - @mail = mail - @mail_key = mail_key - end - - def message - @message ||= process_message - end - - def author - raise NotImplementedError - end - - def project - raise NotImplementedError - end - - private - def validate_permission!(permission) - raise UserNotFoundError unless author - raise UserBlockedError if author.blocked? - raise ProjectNotFound unless author.can?(:read_project, project) - raise UserNotAuthorizedError unless author.can?(permission, project) - end - - def process_message - add_attachments(ReplyParser.new(mail).execute.strip) - end - - def add_attachments(reply) - attachments = Email::AttachmentUploader.new(mail).execute(project) - - reply + attachments.map do |link| - "\n\n#{link[:markdown]}" - end.join - end - - def verify_record(record, exception, error_title) - return if record.persisted? - - msg = error_title + record.errors.full_messages.map do |error| - "\n\n- #{error}" - end.join - - raise exception, msg + module Handler + def self.for(mail, mail_key) + [CreateNoteHandler, CreateIssueHandler].find do |klass| + handler = klass.new(mail, mail_key) + break handler if handler.can_handle? + end end end end diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb new file mode 100644 index 00000000000..230d13feea9 --- /dev/null +++ b/lib/gitlab/email/handler/base_handler.rb @@ -0,0 +1,57 @@ + +module Gitlab + module Email + module Handler + class BaseHandler + attr_reader :mail, :mail_key + + def initialize(mail, mail_key) + @mail = mail + @mail_key = mail_key + end + + def message + @message ||= process_message + end + + def author + raise NotImplementedError + end + + def project + raise NotImplementedError + end + + private + def validate_permission!(permission) + raise UserNotFoundError unless author + raise UserBlockedError if author.blocked? + raise ProjectNotFound unless author.can?(:read_project, project) + raise UserNotAuthorizedError unless author.can?(permission, project) + end + + def process_message + add_attachments(ReplyParser.new(mail).execute.strip) + end + + def add_attachments(reply) + attachments = Email::AttachmentUploader.new(mail).execute(project) + + reply + attachments.map do |link| + "\n\n#{link[:markdown]}" + end.join + end + + def verify_record(record, exception, error_title) + return if record.persisted? + + msg = error_title + record.errors.full_messages.map do |error| + "\n\n- #{error}" + end.join + + raise exception, msg + end + end + end + end +end diff --git a/lib/gitlab/email/handler/create_issue.rb b/lib/gitlab/email/handler/create_issue.rb deleted file mode 100644 index 72d49ec6c96..00000000000 --- a/lib/gitlab/email/handler/create_issue.rb +++ /dev/null @@ -1,50 +0,0 @@ - -require 'gitlab/email/handler' - -module Gitlab - module Email - class Handler - class CreateIssue < Handler - def can_handle? - !!project - end - - def execute - validate_permission!(:create_issue) - - verify_record( - create_issue, - InvalidIssueError, - "The issue could not be created for the following reasons:" - ) - end - - def author - @author ||= User.find_by(authentication_token: authentication_token) - end - - def project - @project ||= Project.find_with_namespace(project_namespace) - end - - private - def authentication_token - mail_key[/[^\+]+$/] - end - - def project_namespace - mail_key[/^[^\+]+/] - end - - def create_issue - Issues::CreateService.new( - project, - author, - title: mail.subject, - description: message - ).execute - end - end - end - end -end diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb new file mode 100644 index 00000000000..259d74a83bf --- /dev/null +++ b/lib/gitlab/email/handler/create_issue_handler.rb @@ -0,0 +1,50 @@ + +require 'gitlab/email/handler/base_handler' + +module Gitlab + module Email + module Handler + class CreateIssueHandler < BaseHandler + def can_handle? + !!project + end + + def execute + validate_permission!(:create_issue) + + verify_record( + create_issue, + InvalidIssueError, + "The issue could not be created for the following reasons:" + ) + end + + def author + @author ||= User.find_by(authentication_token: authentication_token) + end + + def project + @project ||= Project.find_with_namespace(project_namespace) + end + + private + def authentication_token + mail_key[/[^\+]+$/] + end + + def project_namespace + mail_key[/^[^\+]+/] + end + + def create_issue + Issues::CreateService.new( + project, + author, + title: mail.subject, + description: message + ).execute + end + end + end + end +end diff --git a/lib/gitlab/email/handler/create_note.rb b/lib/gitlab/email/handler/create_note.rb deleted file mode 100644 index 32deb5a311e..00000000000 --- a/lib/gitlab/email/handler/create_note.rb +++ /dev/null @@ -1,55 +0,0 @@ - -require 'gitlab/email/handler' - -module Gitlab - module Email - class Handler - class CreateNote < Handler - def can_handle? - !!sent_notification - end - - def execute - raise SentNotificationNotFoundError unless sent_notification - raise AutoGeneratedEmailError if mail.header.to_s =~ /auto-(generated|replied)/ - - validate_permission!(:create_note) - - raise NoteableNotFoundError unless sent_notification.noteable - raise EmptyEmailError if message.blank? - - verify_record( - create_note, - InvalidNoteError, - "The comment could not be created for the following reasons:" - ) - end - - def author - sent_notification.recipient - end - - def project - sent_notification.project - end - - def sent_notification - @sent_notification ||= SentNotification.for(mail_key) - end - - private - def create_note - Notes::CreateService.new( - project, - author, - note: message, - noteable_type: sent_notification.noteable_type, - noteable_id: sent_notification.noteable_id, - commit_id: sent_notification.commit_id, - line_code: sent_notification.line_code - ).execute - end - end - end - end -end diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb new file mode 100644 index 00000000000..7252906fd48 --- /dev/null +++ b/lib/gitlab/email/handler/create_note_handler.rb @@ -0,0 +1,55 @@ + +require 'gitlab/email/handler/base_handler' + +module Gitlab + module Email + module Handler + class CreateNoteHandler < BaseHandler + def can_handle? + !!sent_notification + end + + def execute + raise SentNotificationNotFoundError unless sent_notification + raise AutoGeneratedEmailError if mail.header.to_s =~ /auto-(generated|replied)/ + + validate_permission!(:create_note) + + raise NoteableNotFoundError unless sent_notification.noteable + raise EmptyEmailError if message.blank? + + verify_record( + create_note, + InvalidNoteError, + "The comment could not be created for the following reasons:" + ) + end + + def author + sent_notification.recipient + end + + def project + sent_notification.project + end + + def sent_notification + @sent_notification ||= SentNotification.for(mail_key) + end + + private + def create_note + Notes::CreateService.new( + project, + author, + note: message, + noteable_type: sent_notification.noteable_type, + noteable_id: sent_notification.noteable_id, + commit_id: sent_notification.commit_id, + line_code: sent_notification.line_code + ).execute + end + end + end + end +end diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index da4299ebcb3..7038346192b 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -1,6 +1,5 @@ -require 'gitlab/email/handler/create_note' -require 'gitlab/email/handler/create_issue' +require 'gitlab/email/handler' # Inspired in great part by Discourse's Email::Receiver module Gitlab @@ -31,7 +30,7 @@ module Gitlab raise SentNotificationNotFoundError unless mail_key - if handler = find_handler(mail, mail_key) + if handler = Handler.for(mail, mail_key) handler.execute elsif mail_key =~ %r{/|\+} # Sent Notification mail_key would not have / or + @@ -65,13 +64,6 @@ module Gitlab break key if key end end - - def find_handler(mail, mail_key) - [Handler::CreateNote, Handler::CreateIssue].find do |klass| - handler = klass.new(mail, mail_key) - break handler if handler.can_handle? - end - end end end end -- cgit v1.2.1 From c64cd113ccd16fa210f9ea2808deb044aadc8f61 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Sat, 21 May 2016 09:46:27 -0700 Subject: Add ! for verify_record! because it could raise --- lib/gitlab/email/handler/base_handler.rb | 2 +- lib/gitlab/email/handler/create_issue_handler.rb | 2 +- lib/gitlab/email/handler/create_note_handler.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb index 230d13feea9..dcbad5bdb29 100644 --- a/lib/gitlab/email/handler/base_handler.rb +++ b/lib/gitlab/email/handler/base_handler.rb @@ -42,7 +42,7 @@ module Gitlab end.join end - def verify_record(record, exception, error_title) + def verify_record!(record, exception, error_title) return if record.persisted? msg = error_title + record.errors.full_messages.map do |error| diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb index 259d74a83bf..cba8f2b61f6 100644 --- a/lib/gitlab/email/handler/create_issue_handler.rb +++ b/lib/gitlab/email/handler/create_issue_handler.rb @@ -12,7 +12,7 @@ module Gitlab def execute validate_permission!(:create_issue) - verify_record( + verify_record!( create_issue, InvalidIssueError, "The issue could not be created for the following reasons:" diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb index 7252906fd48..282f1844097 100644 --- a/lib/gitlab/email/handler/create_note_handler.rb +++ b/lib/gitlab/email/handler/create_note_handler.rb @@ -18,7 +18,7 @@ module Gitlab raise NoteableNotFoundError unless sent_notification.noteable raise EmptyEmailError if message.blank? - verify_record( + verify_record!( create_note, InvalidNoteError, "The comment could not be created for the following reasons:" -- cgit v1.2.1 From 863d8e5ae55debc08bf095354aee0a63417624fc Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 23 May 2016 22:16:40 +0800 Subject: use split and try to unify error raising --- lib/gitlab/email/handler/create_issue_handler.rb | 18 +++++++++--------- lib/gitlab/email/handler/create_note_handler.rb | 2 +- lib/gitlab/email/receiver.rb | 2 -- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb index cba8f2b61f6..7e3506bbd3f 100644 --- a/lib/gitlab/email/handler/create_issue_handler.rb +++ b/lib/gitlab/email/handler/create_issue_handler.rb @@ -5,8 +5,16 @@ module Gitlab module Email module Handler class CreateIssueHandler < BaseHandler + attr_reader :project_namespace, :authentication_token + + def initialize(mail, mail_key) + super(mail, mail_key) + @project_namespace, @authentication_token = + mail_key && mail_key.split('+', 2) + end + def can_handle? - !!project + !!(project_namespace && project) end def execute @@ -28,14 +36,6 @@ module Gitlab end private - def authentication_token - mail_key[/[^\+]+$/] - end - - def project_namespace - mail_key[/^[^\+]+/] - end - def create_issue Issues::CreateService.new( project, diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb index 282f1844097..b91dd623161 100644 --- a/lib/gitlab/email/handler/create_note_handler.rb +++ b/lib/gitlab/email/handler/create_note_handler.rb @@ -6,7 +6,7 @@ module Gitlab module Handler class CreateNoteHandler < BaseHandler def can_handle? - !!sent_notification + !!(mail_key && sent_notification) end def execute diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 7038346192b..a497d09b0a8 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -28,8 +28,6 @@ module Gitlab mail = build_mail mail_key = extract_mail_key(mail) - raise SentNotificationNotFoundError unless mail_key - if handler = Handler.for(mail, mail_key) handler.execute elsif mail_key =~ %r{/|\+} -- cgit v1.2.1 From 1f5d55907a05fefbda17f7a4f45f58f2b522aee2 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 24 May 2016 01:19:20 +0800 Subject: Merge the places where exceptions could be raised --- app/workers/email_receiver_worker.rb | 2 ++ lib/gitlab/email/handler/create_issue_handler.rb | 3 ++- lib/gitlab/email/handler/create_note_handler.rb | 3 ++- lib/gitlab/email/receiver.rb | 6 ++---- spec/lib/gitlab/email/receiver_spec.rb | 8 ++++++++ 5 files changed, 16 insertions(+), 6 deletions(-) diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index af9006d8dde..68081643113 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -24,6 +24,8 @@ class EmailReceiverWorker reason = nil case e + when Gitlab::Email::UnknownIncomingEmail + reason = "We couldn't figure out what the email is for." when Gitlab::Email::SentNotificationNotFoundError reason = "We couldn't figure out what the email is in reply to. Please create your comment through the web interface." when Gitlab::Email::ProjectNotFound diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb index 7e3506bbd3f..bfc56cb17ff 100644 --- a/lib/gitlab/email/handler/create_issue_handler.rb +++ b/lib/gitlab/email/handler/create_issue_handler.rb @@ -14,10 +14,11 @@ module Gitlab end def can_handle? - !!(project_namespace && project) + !!authentication_token end def execute + raise ProjectNotFound unless project validate_permission!(:create_issue) verify_record!( diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb index b91dd623161..2ae3bf23a74 100644 --- a/lib/gitlab/email/handler/create_note_handler.rb +++ b/lib/gitlab/email/handler/create_note_handler.rb @@ -6,7 +6,8 @@ module Gitlab module Handler class CreateNoteHandler < BaseHandler def can_handle? - !!(mail_key && sent_notification) + # We want to raise SentNotificationNotFoundError for missing key + !!(mail_key.nil? || mail_key =~ /\A\w+\z/) end def execute diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index a497d09b0a8..fef9ee8402b 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -16,6 +16,7 @@ module Gitlab class NoteableNotFoundError < ProcessingError; end class InvalidNoteError < ProcessingError; end class InvalidIssueError < ProcessingError; end + class UnknownIncomingEmail < ProcessingError; end class Receiver def initialize(raw) @@ -30,11 +31,8 @@ module Gitlab if handler = Handler.for(mail, mail_key) handler.execute - elsif mail_key =~ %r{/|\+} - # Sent Notification mail_key would not have / or + - raise ProjectNotFound else - raise SentNotificationNotFoundError + raise UnknownIncomingEmail end end diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index a9e2be0ad47..7291e478d90 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -30,6 +30,14 @@ describe Gitlab::Email::Receiver, lib: true do ) end + context "when we cannot find a capable handler" do + let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "!!!") } + + it "raises a UnknownIncomingEmail" do + expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail) + end + end + context "when the recipient address doesn't include a mail key" do let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "") } -- cgit v1.2.1 From cc69bd07e7b0f7b8f81fcff0f1e464e4aa36a583 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 24 May 2016 15:16:16 +0800 Subject: This is easier to read for me. No early return https://gitlab.com/gitlab-org/gitlab-ce/commit/76dbafba86dda96b7ba2f93fc7e07eea3ca48302#note_4575102 --- app/workers/email_receiver_worker.rb | 59 ++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 30 deletions(-) diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index 68081643113..aecc7ecd058 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -21,36 +21,35 @@ class EmailReceiverWorker return unless raw.present? can_retry = false - reason = nil - - case e - when Gitlab::Email::UnknownIncomingEmail - reason = "We couldn't figure out what the email is for." - when Gitlab::Email::SentNotificationNotFoundError - reason = "We couldn't figure out what the email is in reply to. Please create your comment through the web interface." - when Gitlab::Email::ProjectNotFound - reason = "We couldn't find the project. Please check if there's any typo." - when Gitlab::Email::EmptyEmailError - can_retry = true - reason = "It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies." - when Gitlab::Email::AutoGeneratedEmailError - reason = "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface." - when Gitlab::Email::UserNotFoundError - reason = "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface." - when Gitlab::Email::UserBlockedError - reason = "Your account has been blocked. If you believe this is in error, contact a staff member." - when Gitlab::Email::UserNotAuthorizedError - reason = "You are not allowed to perform this action. If you believe this is in error, contact a staff member." - when Gitlab::Email::NoteableNotFoundError - reason = "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member." - when Gitlab::Email::InvalidNoteError, - Gitlab::Email::InvalidIssueError - can_retry = true - reason = e.message - else - return + reason = + case e + when Gitlab::Email::UnknownIncomingEmail + "We couldn't figure out what the email is for." + when Gitlab::Email::SentNotificationNotFoundError + "We couldn't figure out what the email is in reply to. Please create your comment through the web interface." + when Gitlab::Email::ProjectNotFound + "We couldn't find the project. Please check if there's any typo." + when Gitlab::Email::EmptyEmailError + can_retry = true + "It appears that the email is blank. Make sure your reply is at the top of the email, we can't process inline replies." + when Gitlab::Email::AutoGeneratedEmailError + "The email was marked as 'auto generated', which we can't accept. Please create your comment through the web interface." + when Gitlab::Email::UserNotFoundError + "We couldn't figure out what user corresponds to the email. Please create your comment through the web interface." + when Gitlab::Email::UserBlockedError + "Your account has been blocked. If you believe this is in error, contact a staff member." + when Gitlab::Email::UserNotAuthorizedError + "You are not allowed to perform this action. If you believe this is in error, contact a staff member." + when Gitlab::Email::NoteableNotFoundError + "The thread you are replying to no longer exists, perhaps it was deleted? If you believe this is in error, contact a staff member." + when Gitlab::Email::InvalidNoteError, + Gitlab::Email::InvalidIssueError + can_retry = true + e.message + end + + if reason + EmailRejectionMailer.rejection(reason, raw, can_retry).deliver_later end - - EmailRejectionMailer.rejection(reason, raw, can_retry).deliver_later end end -- cgit v1.2.1 From 8c0b619d40e4d113ef592f4ae7a4b6afa320f225 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 24 May 2016 17:30:36 +0800 Subject: Split tests into their own classes --- spec/lib/gitlab/email/email_shared_blocks.rb | 41 ++++ .../email/handler/create_issue_handler_spec.rb | 79 +++++++ .../email/handler/create_note_handler_spec.rb | 116 ++++++++++ spec/lib/gitlab/email/receiver_spec.rb | 236 +-------------------- 4 files changed, 239 insertions(+), 233 deletions(-) create mode 100644 spec/lib/gitlab/email/email_shared_blocks.rb create mode 100644 spec/lib/gitlab/email/handler/create_issue_handler_spec.rb create mode 100644 spec/lib/gitlab/email/handler/create_note_handler_spec.rb diff --git a/spec/lib/gitlab/email/email_shared_blocks.rb b/spec/lib/gitlab/email/email_shared_blocks.rb new file mode 100644 index 00000000000..eeaf427d39d --- /dev/null +++ b/spec/lib/gitlab/email/email_shared_blocks.rb @@ -0,0 +1,41 @@ + +shared_context :email_shared_context do + let(:mail_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } + let(:receiver) { Gitlab::Email::Receiver.new(email_raw) } + let(:markdown) { "![image](uploads/image.png)" } + + def setup_attachment + allow_any_instance_of(Gitlab::Email::AttachmentUploader).to receive(:execute).and_return( + [ + { + url: "uploads/image.png", + is_image: true, + alt: "image", + markdown: markdown + } + ] + ) + end +end + +shared_examples :email_shared_examples do + context "when the user could not be found" do + before do + user.destroy + end + + it "raises a UserNotFoundError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError) + end + end + + context "when the user is not authorized to the project" do + before do + project.update_attribute(:visibility_level, Project::PRIVATE) + end + + it "raises a ProjectNotFound" do + expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound) + end + end +end diff --git a/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb new file mode 100644 index 00000000000..e1153154778 --- /dev/null +++ b/spec/lib/gitlab/email/handler/create_issue_handler_spec.rb @@ -0,0 +1,79 @@ +require 'spec_helper' +require_relative '../email_shared_blocks' + +describe Gitlab::Email::Handler::CreateIssueHandler, lib: true do + include_context :email_shared_context + it_behaves_like :email_shared_examples + + before do + stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo") + stub_config_setting(host: 'localhost') + end + + let(:email_raw) { fixture_file('emails/valid_new_issue.eml') } + let(:namespace) { create(:namespace, path: 'gitlabhq') } + + let!(:project) { create(:project, :public, namespace: namespace) } + let!(:user) do + create( + :user, + email: 'jake@adventuretime.ooo', + authentication_token: 'auth_token' + ) + end + + context "when everything is fine" do + it "creates a new issue" do + setup_attachment + + expect { receiver.execute }.to change { project.issues.count }.by(1) + issue = project.issues.last + + expect(issue.author).to eq(user) + expect(issue.title).to eq('New Issue by email') + expect(issue.description).to include('reply by email') + expect(issue.description).to include(markdown) + end + + context "when the reply is blank" do + let(:email_raw) { fixture_file("emails/valid_new_issue_empty.eml") } + + it "creates a new issue" do + expect { receiver.execute }.to change { project.issues.count }.by(1) + issue = project.issues.last + + expect(issue.author).to eq(user) + expect(issue.title).to eq('New Issue by email') + expect(issue.description).to eq('') + end + end + end + + context "something is wrong" do + context "when the issue could not be saved" do + before do + allow_any_instance_of(Issue).to receive(:persisted?).and_return(false) + end + + it "raises an InvalidIssueError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidIssueError) + end + end + + context "when we can't find the authentication_token" do + let(:email_raw) { fixture_file("emails/wrong_authentication_token.eml") } + + it "raises an UserNotFoundError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError) + end + end + + context "when project is private" do + let(:project) { create(:project, :private, namespace: namespace) } + + it "raises a ProjectNotFound if the user is not a member" do + expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound) + end + end + end +end diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb new file mode 100644 index 00000000000..9b7fb6a1a4b --- /dev/null +++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb @@ -0,0 +1,116 @@ +require 'spec_helper' +require_relative '../email_shared_blocks' + +describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do + include_context :email_shared_context + it_behaves_like :email_shared_examples + + before do + stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo") + stub_config_setting(host: 'localhost') + end + + let(:email_raw) { fixture_file('emails/valid_reply.eml') } + let(:project) { create(:project, :public) } + let(:noteable) { create(:issue, project: project) } + let(:user) { create(:user) } + + let!(:sent_notification) { SentNotification.record(noteable, user.id, mail_key) } + + context "when the recipient address doesn't include a mail key" do + let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "") } + + it "raises a SentNotificationNotFoundError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError) + end + end + + context "when no sent notification for the mail key could be found" do + let(:email_raw) { fixture_file('emails/wrong_mail_key.eml') } + + it "raises a SentNotificationNotFoundError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError) + end + end + + context "when the email was auto generated" do + let!(:mail_key) { '636ca428858779856c226bb145ef4fad' } + let!(:email_raw) { fixture_file("emails/auto_reply.eml") } + + it "raises an AutoGeneratedEmailError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::AutoGeneratedEmailError) + end + end + + context "when the noteable could not be found" do + before do + noteable.destroy + end + + it "raises a NoteableNotFoundError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::NoteableNotFoundError) + end + end + + context "when the note could not be saved" do + before do + allow_any_instance_of(Note).to receive(:persisted?).and_return(false) + end + + it "raises an InvalidNoteError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError) + end + end + + context "when the reply is blank" do + let!(:email_raw) { fixture_file("emails/no_content_reply.eml") } + + it "raises an EmptyEmailError" do + expect { receiver.execute }.to raise_error(Gitlab::Email::EmptyEmailError) + end + end + + context "when everything is fine" do + before do + setup_attachment + end + + it "creates a comment" do + expect { receiver.execute }.to change { noteable.notes.count }.by(1) + note = noteable.notes.last + + expect(note.author).to eq(sent_notification.recipient) + expect(note.note).to include("I could not disagree more.") + end + + it "adds all attachments" do + receiver.execute + + note = noteable.notes.last + + expect(note.note).to include(markdown) + end + + context 'when sub-addressing is not supported' do + before do + stub_incoming_email_setting(enabled: true, address: nil) + end + + shared_examples 'an email that contains a mail key' do |header| + it "fetches the mail key from the #{header} header and creates a comment" do + expect { receiver.execute }.to change { noteable.notes.count }.by(1) + note = noteable.notes.last + + expect(note.author).to eq(sent_notification.recipient) + expect(note.note).to include('I could not disagree more.') + end + end + + context 'mail key is in the References header' do + let(:email_raw) { fixture_file('emails/reply_without_subaddressing_and_key_inside_references.eml') } + + it_behaves_like 'an email that contains a mail key', 'References' + end + end + end +end diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb index 7291e478d90..2a86b427806 100644 --- a/spec/lib/gitlab/email/receiver_spec.rb +++ b/spec/lib/gitlab/email/receiver_spec.rb @@ -1,34 +1,8 @@ -require "spec_helper" +require 'spec_helper' +require_relative 'email_shared_blocks' describe Gitlab::Email::Receiver, lib: true do - before do - stub_incoming_email_setting(enabled: true, address: "reply+%{key}@appmail.adventuretime.ooo") - stub_config_setting(host: 'localhost') - end - - let(:mail_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } - let(:email_raw) { fixture_file('emails/valid_reply.eml') } - - let(:project) { create(:project, :public) } - let(:noteable) { create(:issue, project: project) } - let(:user) { create(:user) } - let!(:sent_notification) { SentNotification.record(noteable, user.id, mail_key) } - - let(:receiver) { described_class.new(email_raw) } - let(:markdown) { "![image](uploads/image.png)" } - - def setup_attachment - allow_any_instance_of(Gitlab::Email::AttachmentUploader).to receive(:execute).and_return( - [ - { - url: "uploads/image.png", - is_image: true, - alt: "image", - markdown: markdown - } - ] - ) - end + include_context :email_shared_context context "when we cannot find a capable handler" do let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "!!!") } @@ -38,22 +12,6 @@ describe Gitlab::Email::Receiver, lib: true do end end - context "when the recipient address doesn't include a mail key" do - let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "") } - - it "raises a SentNotificationNotFoundError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError) - end - end - - context "when no sent notification for the mail key could be found" do - let(:email_raw) { fixture_file('emails/wrong_mail_key.eml') } - - it "raises a SentNotificationNotFoundError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError) - end - end - context "when the email is blank" do let(:email_raw) { "" } @@ -61,192 +19,4 @@ describe Gitlab::Email::Receiver, lib: true do expect { receiver.execute }.to raise_error(Gitlab::Email::EmptyEmailError) end end - - context "when the email was auto generated" do - let!(:mail_key) { '636ca428858779856c226bb145ef4fad' } - let!(:email_raw) { fixture_file("emails/auto_reply.eml") } - - it "raises an AutoGeneratedEmailError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::AutoGeneratedEmailError) - end - end - - context "when the user could not be found" do - before do - user.destroy - end - - it "raises a UserNotFoundError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError) - end - end - - context "when the user has been blocked" do - before do - user.block - end - - it "raises a UserBlockedError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::UserBlockedError) - end - end - - context "when the user is not authorized to create a note" do - before do - project.update_attribute(:visibility_level, Project::PRIVATE) - end - - it "raises a ProjectNotFound" do - expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound) - end - end - - context "when the noteable could not be found" do - before do - noteable.destroy - end - - it "raises a NoteableNotFoundError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::NoteableNotFoundError) - end - end - - context "when the reply is blank" do - let!(:email_raw) { fixture_file("emails/no_content_reply.eml") } - - it "raises an EmptyEmailError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::EmptyEmailError) - end - end - - context "when the note could not be saved" do - before do - allow_any_instance_of(Note).to receive(:persisted?).and_return(false) - end - - it "raises an InvalidNoteError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidNoteError) - end - end - - context "when everything is fine" do - before do - setup_attachment - end - - it "creates a comment" do - expect { receiver.execute }.to change { noteable.notes.count }.by(1) - note = noteable.notes.last - - expect(note.author).to eq(sent_notification.recipient) - expect(note.note).to include("I could not disagree more.") - end - - it "adds all attachments" do - receiver.execute - - note = noteable.notes.last - - expect(note.note).to include(markdown) - end - - context 'when sub-addressing is not supported' do - before do - stub_incoming_email_setting(enabled: true, address: nil) - end - - shared_examples 'an email that contains a mail key' do |header| - it "fetches the mail key from the #{header} header and creates a comment" do - expect { receiver.execute }.to change { noteable.notes.count }.by(1) - note = noteable.notes.last - - expect(note.author).to eq(sent_notification.recipient) - expect(note.note).to include('I could not disagree more.') - end - end - - context 'mail key is in the References header' do - let(:email_raw) { fixture_file('emails/reply_without_subaddressing_and_key_inside_references.eml') } - - it_behaves_like 'an email that contains a mail key', 'References' - end - end - end - - context "when it's trying to create a new issue" do - before do - stub_incoming_email_setting(enabled: true, address: "incoming+%{key}@appmail.adventuretime.ooo") - end - - let(:sent_notification) {} - let!(:user) do - create( - :user, - email: 'jake@adventuretime.ooo', - authentication_token: 'auth_token' - ) - end - let(:namespace) { create(:namespace, path: 'gitlabhq') } - let(:project) { create(:project, :public, namespace: namespace) } - let(:email_raw) { fixture_file('emails/valid_new_issue.eml') } - - context "when everything is fine" do - it "creates a new issue" do - setup_attachment - - expect { receiver.execute }.to change { project.issues.count }.by(1) - issue = project.issues.last - - expect(issue.author).to eq(user) - expect(issue.title).to eq('New Issue by email') - expect(issue.description).to include('reply by email') - expect(issue.description).to include(markdown) - end - - context "when the reply is blank" do - let!(:email_raw) { fixture_file("emails/valid_new_issue_empty.eml") } - - it "creates a new issue" do - expect { receiver.execute }.to change { project.issues.count }.by(1) - issue = project.issues.last - - expect(issue.author).to eq(user) - expect(issue.title).to eq('New Issue by email') - expect(issue.description).to eq('') - end - end - end - - context "something is wrong" do - before do - project - end - - context "when the issue could not be saved" do - before do - allow_any_instance_of(Issue).to receive(:persisted?).and_return(false) - end - - it "raises an InvalidIssueError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::InvalidIssueError) - end - end - - context "when we can't find the authentication_token" do - let!(:email_raw) { fixture_file("emails/wrong_authentication_token.eml") } - - it "raises an UserNotFoundError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::UserNotFoundError) - end - end - - context "when project is private" do - let(:project) { create(:project, :private, namespace: namespace) } - - it "raises a ProjectNotFound if the user is not a member" do - expect { receiver.execute }.to raise_error(Gitlab::Email::ProjectNotFound) - end - end - end - end end -- cgit v1.2.1 From cb168c347a035573c8b8fa493be4d1f797d895f0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 15 Jun 2016 15:47:40 +0800 Subject: Remove empty lines at the beginning of files --- lib/gitlab/email/handler.rb | 1 - lib/gitlab/email/handler/base_handler.rb | 1 - 2 files changed, 2 deletions(-) diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb index b9221f1210c..0824d453fed 100644 --- a/lib/gitlab/email/handler.rb +++ b/lib/gitlab/email/handler.rb @@ -1,4 +1,3 @@ - require 'gitlab/email/handler/create_note_handler' require 'gitlab/email/handler/create_issue_handler' diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb index dcbad5bdb29..d37ff3d1a40 100644 --- a/lib/gitlab/email/handler/base_handler.rb +++ b/lib/gitlab/email/handler/base_handler.rb @@ -1,4 +1,3 @@ - module Gitlab module Email module Handler -- cgit v1.2.1 From c55eebb2835d15e618356d94f318eae0573ba95e Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 15 Jun 2016 15:49:01 +0800 Subject: An instruction for what to do --- app/workers/email_receiver_worker.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/workers/email_receiver_worker.rb b/app/workers/email_receiver_worker.rb index aecc7ecd058..842eebdea9e 100644 --- a/app/workers/email_receiver_worker.rb +++ b/app/workers/email_receiver_worker.rb @@ -24,7 +24,7 @@ class EmailReceiverWorker reason = case e when Gitlab::Email::UnknownIncomingEmail - "We couldn't figure out what the email is for." + "We couldn't figure out what the email is for. Please create your issue or comment through the web interface." when Gitlab::Email::SentNotificationNotFoundError "We couldn't figure out what the email is in reply to. Please create your comment through the web interface." when Gitlab::Email::ProjectNotFound -- cgit v1.2.1 From 72184c16ab3af3279f218d8b7c2b9aae5bc0b4a8 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 15 Jun 2016 15:54:04 +0800 Subject: Rename to project_path which is more accurate --- lib/gitlab/email/handler/create_issue_handler.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb index bfc56cb17ff..19c903e56f5 100644 --- a/lib/gitlab/email/handler/create_issue_handler.rb +++ b/lib/gitlab/email/handler/create_issue_handler.rb @@ -5,11 +5,11 @@ module Gitlab module Email module Handler class CreateIssueHandler < BaseHandler - attr_reader :project_namespace, :authentication_token + attr_reader :project_path, :authentication_token def initialize(mail, mail_key) super(mail, mail_key) - @project_namespace, @authentication_token = + @project_path, @authentication_token = mail_key && mail_key.split('+', 2) end @@ -33,7 +33,7 @@ module Gitlab end def project - @project ||= Project.find_with_namespace(project_namespace) + @project ||= Project.find_with_namespace(project_path) end private -- cgit v1.2.1 From 4c098714504b5cc5693adac49ca28370e89d0643 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 15 Jun 2016 16:15:49 +0800 Subject: Avoid using bang bang --- lib/gitlab/email/handler/create_issue_handler.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb index 19c903e56f5..638766cca2f 100644 --- a/lib/gitlab/email/handler/create_issue_handler.rb +++ b/lib/gitlab/email/handler/create_issue_handler.rb @@ -14,7 +14,7 @@ module Gitlab end def can_handle? - !!authentication_token + !authentication_token.nil? end def execute -- cgit v1.2.1 From 3b64c4f9d625d307ff5c0d10beb0b6b42c0a1e9c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 15 Jun 2016 16:16:44 +0800 Subject: A blank line to separate exception raising --- lib/gitlab/email/handler/create_issue_handler.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb index 638766cca2f..d2997e73455 100644 --- a/lib/gitlab/email/handler/create_issue_handler.rb +++ b/lib/gitlab/email/handler/create_issue_handler.rb @@ -19,6 +19,7 @@ module Gitlab def execute raise ProjectNotFound unless project + validate_permission!(:create_issue) verify_record!( -- cgit v1.2.1 From fcc6a59247f7bb8c3aa45fc2d3dd3eda66f2c0e0 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 15 Jun 2016 16:18:31 +0800 Subject: Blank line for private --- lib/gitlab/email/handler/base_handler.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb index d37ff3d1a40..e6c2705421d 100644 --- a/lib/gitlab/email/handler/base_handler.rb +++ b/lib/gitlab/email/handler/base_handler.rb @@ -22,6 +22,7 @@ module Gitlab end private + def validate_permission!(permission) raise UserNotFoundError unless author raise UserBlockedError if author.blocked? -- cgit v1.2.1 From a01623882ed8eb10f1b3d2b6431d7cfdefd68ae1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 15 Jun 2016 16:18:40 +0800 Subject: Avoid assignment in if --- lib/gitlab/email/receiver.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index fef9ee8402b..77ed6b9f7d9 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -28,8 +28,9 @@ module Gitlab mail = build_mail mail_key = extract_mail_key(mail) + handler = Handler.for(mail, mail_key) - if handler = Handler.for(mail, mail_key) + if handler handler.execute else raise UnknownIncomingEmail -- cgit v1.2.1 From 5608e1a159318892abc78ccbd8db1ac7090c6a80 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 15 Jun 2016 16:23:41 +0800 Subject: Raise first like an input check --- lib/gitlab/email/receiver.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb index 77ed6b9f7d9..9213cfb51e8 100644 --- a/lib/gitlab/email/receiver.rb +++ b/lib/gitlab/email/receiver.rb @@ -30,11 +30,9 @@ module Gitlab mail_key = extract_mail_key(mail) handler = Handler.for(mail, mail_key) - if handler - handler.execute - else - raise UnknownIncomingEmail - end + raise UnknownIncomingEmail unless handler + + handler.execute end def build_mail -- cgit v1.2.1 From 3ced5ae68e7575ee3d4cd5b4b69f1d65b95d3285 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 15 Jun 2016 16:31:28 +0800 Subject: Save the list of handlers in a constant --- lib/gitlab/email/handler.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/email/handler.rb b/lib/gitlab/email/handler.rb index 0824d453fed..bd3267e2a80 100644 --- a/lib/gitlab/email/handler.rb +++ b/lib/gitlab/email/handler.rb @@ -4,8 +4,10 @@ require 'gitlab/email/handler/create_issue_handler' module Gitlab module Email module Handler + HANDLERS = [CreateNoteHandler, CreateIssueHandler] + def self.for(mail, mail_key) - [CreateNoteHandler, CreateIssueHandler].find do |klass| + HANDLERS.find do |klass| handler = klass.new(mail, mail_key) break handler if handler.can_handle? end -- cgit v1.2.1 From 4befcc353dfca081ea35cedc5be7687f6e0b97e4 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 15 Jun 2016 17:59:44 +0800 Subject: Add missing require in tests --- spec/lib/gitlab/email/email_shared_blocks.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/lib/gitlab/email/email_shared_blocks.rb b/spec/lib/gitlab/email/email_shared_blocks.rb index eeaf427d39d..c1dc1003831 100644 --- a/spec/lib/gitlab/email/email_shared_blocks.rb +++ b/spec/lib/gitlab/email/email_shared_blocks.rb @@ -1,3 +1,4 @@ +require 'gitlab/email/receiver' shared_context :email_shared_context do let(:mail_key) { "59d8df8370b7e95c5a49fbf86aeb2c93" } -- cgit v1.2.1 From 94178149703f06a7fef958bb4dd964819a02fe13 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 15 Jun 2016 19:41:31 +0800 Subject: More space for private concern --- lib/gitlab/email/handler/create_issue_handler.rb | 1 + lib/gitlab/email/handler/create_note_handler.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb index d2997e73455..431bd9a8f4e 100644 --- a/lib/gitlab/email/handler/create_issue_handler.rb +++ b/lib/gitlab/email/handler/create_issue_handler.rb @@ -38,6 +38,7 @@ module Gitlab end private + def create_issue Issues::CreateService.new( project, diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb index 2ae3bf23a74..a9af2f44a81 100644 --- a/lib/gitlab/email/handler/create_note_handler.rb +++ b/lib/gitlab/email/handler/create_note_handler.rb @@ -39,6 +39,7 @@ module Gitlab end private + def create_note Notes::CreateService.new( project, -- cgit v1.2.1 From 09c38e46e5e2e3c3f8a1dcda572243966be48b10 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 15 Jun 2016 20:10:16 +0800 Subject: Only pass item name --- lib/gitlab/email/handler/base_handler.rb | 5 ++++- lib/gitlab/email/handler/create_issue_handler.rb | 6 +----- lib/gitlab/email/handler/create_note_handler.rb | 6 +----- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb index e6c2705421d..4d74e770b91 100644 --- a/lib/gitlab/email/handler/base_handler.rb +++ b/lib/gitlab/email/handler/base_handler.rb @@ -42,9 +42,12 @@ module Gitlab end.join end - def verify_record!(record, exception, error_title) + def verify_record!(record, exception, name) return if record.persisted? + error_title = + "The #{name} could not be created for the following reasons:" + msg = error_title + record.errors.full_messages.map do |error| "\n\n- #{error}" end.join diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb index 431bd9a8f4e..f8b23d07165 100644 --- a/lib/gitlab/email/handler/create_issue_handler.rb +++ b/lib/gitlab/email/handler/create_issue_handler.rb @@ -22,11 +22,7 @@ module Gitlab validate_permission!(:create_issue) - verify_record!( - create_issue, - InvalidIssueError, - "The issue could not be created for the following reasons:" - ) + verify_record!(create_issue, InvalidIssueError, 'issue') end def author diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb index a9af2f44a81..e4da6b590fb 100644 --- a/lib/gitlab/email/handler/create_note_handler.rb +++ b/lib/gitlab/email/handler/create_note_handler.rb @@ -19,11 +19,7 @@ module Gitlab raise NoteableNotFoundError unless sent_notification.noteable raise EmptyEmailError if message.blank? - verify_record!( - create_note, - InvalidNoteError, - "The comment could not be created for the following reasons:" - ) + verify_record!(create_note, InvalidNoteError, 'comment') end def author -- cgit v1.2.1 From c491f66952dd440c449c966727f9f08f3247e9d3 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 20 Jun 2016 17:58:05 +0800 Subject: Use a separate line for separate actions: Feedback from: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3363#note_12420487 --- lib/gitlab/email/handler/base_handler.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb index 4d74e770b91..1691fca6465 100644 --- a/lib/gitlab/email/handler/base_handler.rb +++ b/lib/gitlab/email/handler/base_handler.rb @@ -31,7 +31,8 @@ module Gitlab end def process_message - add_attachments(ReplyParser.new(mail).execute.strip) + message = ReplyParser.new(mail).execute.strip + add_attachments(message) end def add_attachments(reply) -- cgit v1.2.1 From 0671db52a8152ad6d0c4104fa5fc437b9fd6c69d Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 20 Jun 2016 19:11:42 +0800 Subject: Use keyword args to be more clear: Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3363#note_12566374 --- lib/gitlab/email/handler/base_handler.rb | 7 +++---- lib/gitlab/email/handler/create_issue_handler.rb | 5 ++++- lib/gitlab/email/handler/create_note_handler.rb | 5 ++++- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/email/handler/base_handler.rb b/lib/gitlab/email/handler/base_handler.rb index 1691fca6465..b7ed11cb638 100644 --- a/lib/gitlab/email/handler/base_handler.rb +++ b/lib/gitlab/email/handler/base_handler.rb @@ -43,17 +43,16 @@ module Gitlab end.join end - def verify_record!(record, exception, name) + def verify_record!(record:, invalid_exception:, record_name:) return if record.persisted? - error_title = - "The #{name} could not be created for the following reasons:" + error_title = "The #{record_name} could not be created for the following reasons:" msg = error_title + record.errors.full_messages.map do |error| "\n\n- #{error}" end.join - raise exception, msg + raise invalid_exception, msg end end end diff --git a/lib/gitlab/email/handler/create_issue_handler.rb b/lib/gitlab/email/handler/create_issue_handler.rb index f8b23d07165..4e6566af8ab 100644 --- a/lib/gitlab/email/handler/create_issue_handler.rb +++ b/lib/gitlab/email/handler/create_issue_handler.rb @@ -22,7 +22,10 @@ module Gitlab validate_permission!(:create_issue) - verify_record!(create_issue, InvalidIssueError, 'issue') + verify_record!( + record: create_issue, + invalid_exception: InvalidIssueError, + record_name: 'issue') end def author diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb index e4da6b590fb..69646651223 100644 --- a/lib/gitlab/email/handler/create_note_handler.rb +++ b/lib/gitlab/email/handler/create_note_handler.rb @@ -19,7 +19,10 @@ module Gitlab raise NoteableNotFoundError unless sent_notification.noteable raise EmptyEmailError if message.blank? - verify_record!(create_note, InvalidNoteError, 'comment') + verify_record!( + record: create_note, + invalid_exception: InvalidNoteError, + record_name: 'comment') end def author -- cgit v1.2.1 From 176fa21cb75f404b401e021f0cdcc6a138c207eb Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 20 Jun 2016 19:15:54 +0800 Subject: raise UnknownIncomingEmail when there's no mail_key: So that we don't have to pretend that CreateNoteHandler could handle a nil mail_key. Also, since NilClass#=~ would just return nil for any regular expression, we could just match it without checking nilness. Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3363#note_12566407 --- lib/gitlab/email/handler/create_note_handler.rb | 3 +-- spec/lib/gitlab/email/handler/create_note_handler_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/email/handler/create_note_handler.rb b/lib/gitlab/email/handler/create_note_handler.rb index 69646651223..06dae31cc27 100644 --- a/lib/gitlab/email/handler/create_note_handler.rb +++ b/lib/gitlab/email/handler/create_note_handler.rb @@ -6,8 +6,7 @@ module Gitlab module Handler class CreateNoteHandler < BaseHandler def can_handle? - # We want to raise SentNotificationNotFoundError for missing key - !!(mail_key.nil? || mail_key =~ /\A\w+\z/) + mail_key =~ /\A\w+\z/ end def execute diff --git a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb index 9b7fb6a1a4b..a2119b0dadf 100644 --- a/spec/lib/gitlab/email/handler/create_note_handler_spec.rb +++ b/spec/lib/gitlab/email/handler/create_note_handler_spec.rb @@ -20,8 +20,8 @@ describe Gitlab::Email::Handler::CreateNoteHandler, lib: true do context "when the recipient address doesn't include a mail key" do let(:email_raw) { fixture_file('emails/valid_reply.eml').gsub(mail_key, "") } - it "raises a SentNotificationNotFoundError" do - expect { receiver.execute }.to raise_error(Gitlab::Email::SentNotificationNotFoundError) + it "raises a UnknownIncomingEmail" do + expect { receiver.execute }.to raise_error(Gitlab::Email::UnknownIncomingEmail) end end -- cgit v1.2.1 From 918646f8e9daa954d25f122f866bd675c1278c42 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 23 Jun 2016 18:36:44 +0800 Subject: Add Project#new_issue_address(author): Which would return the email address for creating a new issue by sending an email to that particular address. --- app/models/project.rb | 5 +++++ spec/models/project_spec.rb | 16 ++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/app/models/project.rb b/app/models/project.rb index 0d2e612436a..822d6489112 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -528,6 +528,11 @@ class Project < ActiveRecord::Base web_url.split('://')[1] end + def new_issue_address(author) + Gitlab::IncomingEmail.reply_address( + "#{path_with_namespace}+#{author.authentication_token}") + end + def build_commit_note(commit) notes.new(commit_id: commit.id, noteable_type: 'Commit') end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 30aa2b70c8d..407efc9f966 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -129,6 +129,22 @@ describe Project, models: true do end end + describe "#new_issue_address" do + before do + stub_incoming_email_setting(address: "p+%{key}@gl.ab") + end + + let(:project) { create(:empty_project, path: "somewhere") } + let(:user) { create(:user) } + + it 'returns the address to create a new issue' do + token = user.authentication_token + address = "p+#{project.namespace.path}/#{project.path}+#{token}@gl.ab" + + expect(project.new_issue_address(user)).to eq(address) + end + end + describe 'last_activity methods' do let(:project) { create(:project) } let(:last_event) { double(created_at: Time.now) } -- cgit v1.2.1 From e616fbfd425f838ca83fe01c3adf10e6b8e5a934 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 24 Jun 2016 16:05:05 +0800 Subject: Only return the address if incoming emails is enabled: Feedback from: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3363#note_12669123 --- app/models/project.rb | 6 ++++-- spec/models/project_spec.rb | 28 ++++++++++++++++++++-------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 822d6489112..ad2a3ec6cf8 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -529,8 +529,10 @@ class Project < ActiveRecord::Base end def new_issue_address(author) - Gitlab::IncomingEmail.reply_address( - "#{path_with_namespace}+#{author.authentication_token}") + if Gitlab::IncomingEmail.enabled? + Gitlab::IncomingEmail.reply_address( + "#{path_with_namespace}+#{author.authentication_token}") + end end def build_commit_note(commit) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 407efc9f966..5d41bc2d38c 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -130,18 +130,30 @@ describe Project, models: true do end describe "#new_issue_address" do - before do - stub_incoming_email_setting(address: "p+%{key}@gl.ab") - end - let(:project) { create(:empty_project, path: "somewhere") } let(:user) { create(:user) } - it 'returns the address to create a new issue' do - token = user.authentication_token - address = "p+#{project.namespace.path}/#{project.path}+#{token}@gl.ab" + context 'incoming email enabled' do + before do + stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") + end + + it 'returns the address to create a new issue' do + token = user.authentication_token + address = "p+#{project.namespace.path}/#{project.path}+#{token}@gl.ab" - expect(project.new_issue_address(user)).to eq(address) + expect(project.new_issue_address(user)).to eq(address) + end + end + + context 'incoming email disabled' do + before do + stub_incoming_email_setting(enabled: false) + end + + it 'returns nil' do + expect(project.new_issue_address(user)).to be_nil + end end end -- cgit v1.2.1 From 7964ea1bad6607becce3d2028650ef01b167bc60 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 30 Jun 2016 10:08:14 +0100 Subject: Added new issue by email modal window --- app/assets/stylesheets/pages/issues.scss | 17 +++++++++++++++++ app/models/project.rb | 2 +- app/views/projects/issues/_issue_by_email.html.haml | 20 ++++++++++++++++++++ app/views/projects/issues/index.html.haml | 6 ++++++ 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 app/views/projects/issues/_issue_by_email.html.haml diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 4e35ca329e4..80f7ef16360 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -91,3 +91,20 @@ form.edit-issue { .issue-form .select2-container { width: 250px !important; } + +.issues-footer { + padding-top: $gl-padding; + padding-bottom: 37px; +} + +.issue-email-modal-btn { + padding: 0; + color: $gl-link-color; + background-color: transparent; + border: 0; + outline: 0; + + &:hover { + text-decoration: underline; + } +} diff --git a/app/models/project.rb b/app/models/project.rb index ad2a3ec6cf8..9fb6a462b6f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -529,7 +529,7 @@ class Project < ActiveRecord::Base end def new_issue_address(author) - if Gitlab::IncomingEmail.enabled? + if Gitlab::IncomingEmail.enabled? && author Gitlab::IncomingEmail.reply_address( "#{path_with_namespace}+#{author.authentication_token}") end diff --git a/app/views/projects/issues/_issue_by_email.html.haml b/app/views/projects/issues/_issue_by_email.html.haml new file mode 100644 index 00000000000..395d8b4b256 --- /dev/null +++ b/app/views/projects/issues/_issue_by_email.html.haml @@ -0,0 +1,20 @@ +#issue-email-modal.modal.fade{ tabindex: "-1", role: "dialog" } + .modal-dialog{ role: "document" } + .modal-content + .modal-header + %button.close{ type: "button", data: { dismiss: "modal" }, aria: { label: "close" } } + %span{ aria: { hidden: "true" } }= icon("times") + %h4.modal-title + Create new issue by email + .modal-body + %p + Write an email to + = succeed "." do + %a{ href: "mailto:#{email}" }= email + (This is a private email address, so keep it secret.) + %p + Send an email to this address to create an issue. + %p + Use the subject line as the title of your issue. + %p + Use the message as the body of your issue (feel free to include some nice markdown). diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index cd876b5ea62..68befa5196f 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -1,5 +1,6 @@ - @no_container = true - page_title "Issues" +- new_issue_email = @project.new_issue_address(current_user) = render "projects/issues/head" = content_for :meta_tags do @@ -24,3 +25,8 @@ .issues-holder = render "issues" + - if new_issue_email + .issues-footer.text-center + %button.issue-email-modal-btn{ type: "button", data: { toggle: "modal", target: "#issue-email-modal" } } + Email a new issue to this project + = render "issue_by_email", email: new_issue_email -- cgit v1.2.1 From d0e8ffbbe63e1b6836dddc1d6333ea59fb39c3a7 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 1 Jul 2016 09:07:29 +0100 Subject: Uses input field for email address --- app/assets/stylesheets/pages/issues.scss | 13 +++++++++++++ app/views/projects/issues/_issue_by_email.html.haml | 9 +++++---- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss index 80f7ef16360..c01dc734505 100644 --- a/app/assets/stylesheets/pages/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -108,3 +108,16 @@ form.edit-issue { text-decoration: underline; } } + +.email-modal-input-group { + margin-bottom: 10px; + + .form-control { + background-color: $white-light; + } + + .btn { + background-color: $background-color; + border: 1px solid $border-gray-light; + } +} diff --git a/app/views/projects/issues/_issue_by_email.html.haml b/app/views/projects/issues/_issue_by_email.html.haml index 395d8b4b256..0d1204c5e10 100644 --- a/app/views/projects/issues/_issue_by_email.html.haml +++ b/app/views/projects/issues/_issue_by_email.html.haml @@ -8,10 +8,11 @@ Create new issue by email .modal-body %p - Write an email to - = succeed "." do - %a{ href: "mailto:#{email}" }= email - (This is a private email address, so keep it secret.) + Write an email to the below email address. (This is a private email address, so keep it secret.) + .email-modal-input-group.input-group + = text_field_tag :issue_email, email, class: "monospace js-select-on-focus form-control", readonly: true + .input-group-btn + = clipboard_button(clipboard_target: '#issue_email') %p Send an email to this address to create an issue. %p -- cgit v1.2.1 From cf53d798736f7c86459c3e6635a83875e6101373 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 5 Jul 2016 13:07:03 +0200 Subject: Extract jobs config to separate key in config hash --- lib/gitlab/ci/config/node/global.rb | 16 ++++++++++++++++ spec/lib/gitlab/ci/config/node/global_spec.rb | 12 +++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index f92e1eccbcf..0a4315db047 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -36,6 +36,22 @@ module Gitlab helpers :before_script, :image, :services, :after_script, :variables, :stages, :types, :cache + def initialize(config) + return super(config) unless config.is_a?(Hash) + + jobs = config.except(*self.class.nodes.keys) + global = config.slice(*self.class.nodes.keys) + + super(global.merge(jobs: jobs)) + end + + ## + # Temporary refactoring stub + # + def jobs + @config[:jobs] + end + def stages stages_defined? ? stages_value : types_value end diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index c87c9e97bc8..88194bf9453 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -22,7 +22,9 @@ describe Gitlab::Ci::Config::Node::Global do variables: { VAR: 'value' }, after_script: ['make clean'], stages: ['build', 'pages'], - cache: { key: 'k', untracked: true, paths: ['public/'] } } + cache: { key: 'k', untracked: true, paths: ['public/'] }, + rspec: { script: 'rspec' }, + spinach: { script: 'spinach' } } end describe '#process!' do @@ -120,6 +122,14 @@ describe Gitlab::Ci::Config::Node::Global do .to eq(key: 'k', untracked: true, paths: ['public/']) end end + + describe '#jobs' do + it 'returns jobs configuration' do + expect(global.jobs) + .to eq(rspec: { script: 'rspec' }, + spinach: { script: 'spinach' }) + end + end end end -- cgit v1.2.1 From 9686a4f2b2fd010b5a15409ba65ac4eed8e11c7b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 5 Jul 2016 13:19:22 +0200 Subject: Remove code creating job hash from legacy CI config --- lib/ci/gitlab_ci_yaml_processor.rb | 5 +++-- lib/gitlab/ci/config.rb | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 01ef13df57a..8d0bbe1ae56 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -68,8 +68,9 @@ module Ci @jobs = {} - @config.except!(*ALLOWED_YAML_KEYS) - @config.each { |name, param| add_job(name, param) } + @ci_config.jobs.each do |name, param| + add_job(name, param) + end raise ValidationError, "Please define at least one job" if @jobs.none? end diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index e6cc1529760..ae82c0db3f1 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -8,7 +8,7 @@ module Gitlab # Temporary delegations that should be removed after refactoring # delegate :before_script, :image, :services, :after_script, :variables, - :stages, :cache, to: :@global + :stages, :cache, :jobs, to: :@global def initialize(config) @config = Loader.new(config).load! -- cgit v1.2.1 From 5b7f211cbba06f7c43b55b4a38704073564a099f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 5 Jul 2016 13:35:50 +0200 Subject: Add new CI config entry that holds jobs definition --- lib/gitlab/ci/config/node/global.rb | 14 ++++------- lib/gitlab/ci/config/node/jobs.rb | 18 ++++++++++++++ spec/lib/gitlab/ci/config/node/global_spec.rb | 4 +-- spec/lib/gitlab/ci/config/node/jobs_spec.rb | 36 +++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 11 deletions(-) create mode 100644 lib/gitlab/ci/config/node/jobs.rb create mode 100644 spec/lib/gitlab/ci/config/node/jobs_spec.rb diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index 0a4315db047..cb2db4e9757 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -33,11 +33,14 @@ module Gitlab node :cache, Node::Cache, description: 'Configure caching between build jobs.' + node :jobs, Node::Jobs, + description: 'Definition of jobs for this pipeline.' + helpers :before_script, :image, :services, :after_script, - :variables, :stages, :types, :cache + :variables, :stages, :types, :cache, :jobs def initialize(config) - return super(config) unless config.is_a?(Hash) + return super unless config.is_a?(Hash) jobs = config.except(*self.class.nodes.keys) global = config.slice(*self.class.nodes.keys) @@ -45,13 +48,6 @@ module Gitlab super(global.merge(jobs: jobs)) end - ## - # Temporary refactoring stub - # - def jobs - @config[:jobs] - end - def stages stages_defined? ? stages_value : types_value end diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb new file mode 100644 index 00000000000..2df2d55aca2 --- /dev/null +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -0,0 +1,18 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a set of jobs. + # + class Jobs < Entry + include Validatable + + validations do + validates :config, type: Hash + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 88194bf9453..8f2f9e171d1 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -35,7 +35,7 @@ describe Gitlab::Ci::Config::Node::Global do end it 'creates node object for each entry' do - expect(global.nodes.count).to eq 8 + expect(global.nodes.count).to eq 9 end it 'creates node object using valid class' do @@ -139,7 +139,7 @@ describe Gitlab::Ci::Config::Node::Global do describe '#nodes' do it 'instantizes all nodes' do - expect(global.nodes.count).to eq 8 + expect(global.nodes.count).to eq 9 end it 'contains undefined nodes' do diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb new file mode 100644 index 00000000000..3afa3de06c6 --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Jobs do + let(:entry) { described_class.new(config) } + + describe 'validations' do + context 'when entry config value is correct' do + let(:config) { { rspec: { script: 'rspec' } } } + + describe '#value' do + it 'returns key value' do + expect(entry.value).to eq(rspec: { script: 'rspec' }) + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not correct' do + context 'incorrect config value type' do + let(:config) { ['incorrect'] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'jobs config should be a hash' + end + end + end + end + end +end -- cgit v1.2.1 From e00ae9a87720bccda634dc85c018f5ec1d03a883 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 5 Jul 2016 14:04:22 +0200 Subject: Add new CI config entry for single job in pipeline --- lib/gitlab/ci/config/node/job.rb | 14 ++++++++++++ spec/lib/gitlab/ci/config/node/job_spec.rb | 36 ++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 lib/gitlab/ci/config/node/job.rb create mode 100644 spec/lib/gitlab/ci/config/node/job_spec.rb diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb new file mode 100644 index 00000000000..5be8cb39a87 --- /dev/null +++ b/lib/gitlab/ci/config/node/job.rb @@ -0,0 +1,14 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a concrete CI/CD job. + # + class Job < Entry + include Configurable + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb new file mode 100644 index 00000000000..7dd25a23c83 --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Job do + let(:entry) { described_class.new(config) } + + describe 'validations' do + context 'when entry config value is correct' do + let(:config) { { script: 'rspec' } } + + describe '#value' do + it 'returns key value' do + expect(entry.value).to eq(script: 'rspec') + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not correct' do + context 'incorrect config value type' do + let(:config) { ['incorrect'] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'job config should be a hash' + end + end + end + end + end +end -- cgit v1.2.1 From 6ae80732bb3b503e2d15acb2cab527c17e22e34b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 5 Jul 2016 14:17:09 +0200 Subject: Add ability to define nodes in new CI config entry --- lib/gitlab/ci/config/node/entry.rb | 16 ++++++++++------ spec/lib/gitlab/ci/config/node/global_spec.rb | 16 ++++++++-------- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 9e79e170a4f..7148f4c2a79 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -26,12 +26,16 @@ module Gitlab process_nodes! end + def leaf? + nodes.none? + end + def nodes - @nodes.values + self.class.nodes end - def leaf? - self.class.nodes.none? + def descendants + @nodes.values end def ancestors @@ -43,7 +47,7 @@ module Gitlab end def errors - @validator.messages + nodes.flat_map(&:errors) + @validator.messages + @nodes.values.flat_map(&:errors) end def value @@ -73,13 +77,13 @@ module Gitlab private def compose! - self.class.nodes.each do |key, essence| + nodes.each do |key, essence| @nodes[key] = create_node(key, essence) end end def process_nodes! - nodes.each(&:process!) + @nodes.each_value(&:process!) end def create_node(key, essence) diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 8f2f9e171d1..254cb28190c 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -31,24 +31,24 @@ describe Gitlab::Ci::Config::Node::Global do before { global.process! } it 'creates nodes hash' do - expect(global.nodes).to be_an Array + expect(global.descendants).to be_an Array end it 'creates node object for each entry' do - expect(global.nodes.count).to eq 9 + expect(global.descendants.count).to eq 9 end it 'creates node object using valid class' do - expect(global.nodes.first) + expect(global.descendants.first) .to be_an_instance_of Gitlab::Ci::Config::Node::Script - expect(global.nodes.second) + expect(global.descendants.second) .to be_an_instance_of Gitlab::Ci::Config::Node::Image end it 'sets correct description for nodes' do - expect(global.nodes.first.description) + expect(global.descendants.first.description) .to eq 'Script that will be executed before each job.' - expect(global.nodes.second.description) + expect(global.descendants.second.description) .to eq 'Docker image that will be used to execute jobs.' end end @@ -139,11 +139,11 @@ describe Gitlab::Ci::Config::Node::Global do describe '#nodes' do it 'instantizes all nodes' do - expect(global.nodes.count).to eq 9 + expect(global.descendants.count).to eq 9 end it 'contains undefined nodes' do - expect(global.nodes.first) + expect(global.descendants.first) .to be_an_instance_of Gitlab::Ci::Config::Node::Undefined end end -- cgit v1.2.1 From dbab56a9519039bb6a83974c31b90b1283b8479c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 5 Jul 2016 14:48:17 +0200 Subject: Create composite job entries in new CI config --- lib/gitlab/ci/config/node/jobs.rb | 14 ++++++++++++++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 4 ++-- spec/lib/gitlab/ci/config/node/jobs_spec.rb | 17 +++++++++++++++++ 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index 2df2d55aca2..915b46652f2 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -11,6 +11,20 @@ module Gitlab validations do validates :config, type: Hash end + + def nodes + @config + end + + private + + def create_node(key, essence) + Node::Job.new(essence).tap do |job| + job.key = key + job.parent = self + job.description = "#{key} job definition." + end + end end end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index bad439bc489..49a786191b8 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1043,11 +1043,11 @@ EOT end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: services should be an array of strings") end - it "returns errors if there are unknown parameters" do + it "returns error if job configuration is invalid" do config = YAML.dump({ extra: "bundle update" }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Unknown parameter: extra") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:extra config should be a hash") end it "returns errors if there are unknown parameters that are hashes, but doesn't have a script" do diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb index 3afa3de06c6..7f80e11cea3 100644 --- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -4,6 +4,8 @@ describe Gitlab::Ci::Config::Node::Jobs do let(:entry) { described_class.new(config) } describe 'validations' do + before { entry.process! } + context 'when entry config value is correct' do let(:config) { { rspec: { script: 'rspec' } } } @@ -33,4 +35,19 @@ describe Gitlab::Ci::Config::Node::Jobs do end end end + + describe '#descendants' do + before { entry.process! } + + let(:config) do + { rspec: { script: 'rspec' }, + spinach: { script: 'spinach' } } + end + + it 'creates two descendant nodes' do + expect(entry.descendants.count).to eq 2 + expect(entry.descendants) + .to all(be_an_instance_of(Gitlab::Ci::Config::Node::Job)) + end + end end -- cgit v1.2.1 From abd7943636715b0ea4c861135a02db97b6bcfb7e Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Jul 2016 08:40:21 +0100 Subject: Links markdown text to docs --- app/views/projects/issues/_issue_by_email.html.haml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/views/projects/issues/_issue_by_email.html.haml b/app/views/projects/issues/_issue_by_email.html.haml index 0d1204c5e10..72022881c09 100644 --- a/app/views/projects/issues/_issue_by_email.html.haml +++ b/app/views/projects/issues/_issue_by_email.html.haml @@ -18,4 +18,6 @@ %p Use the subject line as the title of your issue. %p - Use the message as the body of your issue (feel free to include some nice markdown). + Use the message as the body of your issue (feel free to include some nice + = succeed ")." do + = link_to "Markdown", help_page_path('markdown', 'markdown') -- cgit v1.2.1 From b1b0c18b8c66857964eaaa5704a0744aacb707dd Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 6 Jul 2016 12:58:43 +0200 Subject: Add hidden job in new CI config that is irrelevant --- lib/gitlab/ci/config/node/entry.rb | 11 +++++- lib/gitlab/ci/config/node/hidden_job.rb | 22 +++++++++++ lib/gitlab/ci/config/node/jobs.rb | 10 ++++- lib/gitlab/ci/config/node/validator.rb | 2 +- spec/lib/gitlab/ci/config/node/hidden_job_spec.rb | 48 +++++++++++++++++++++++ spec/lib/gitlab/ci/config/node/job_spec.rb | 6 +++ spec/lib/gitlab/ci/config/node/jobs_spec.rb | 23 ++++++++--- 7 files changed, 112 insertions(+), 10 deletions(-) create mode 100644 lib/gitlab/ci/config/node/hidden_job.rb create mode 100644 spec/lib/gitlab/ci/config/node/hidden_job_spec.rb diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 7148f4c2a79..fa6569e8bb2 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -54,8 +54,11 @@ module Gitlab if leaf? @config else - defined = @nodes.select { |_key, value| value.defined? } - Hash[defined.map { |key, node| [key, node.value] }] + meaningful = @nodes.select do |_key, value| + value.defined? && value.relevant? + end + + Hash[meaningful.map { |key, node| [key, node.value] }] end end @@ -63,6 +66,10 @@ module Gitlab true end + def relevant? + true + end + def self.default end diff --git a/lib/gitlab/ci/config/node/hidden_job.rb b/lib/gitlab/ci/config/node/hidden_job.rb new file mode 100644 index 00000000000..6a559ee8c04 --- /dev/null +++ b/lib/gitlab/ci/config/node/hidden_job.rb @@ -0,0 +1,22 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a hidden CI/CD job. + # + class HiddenJob < Entry + include Validatable + + validations do + validates :config, type: Hash + end + + def relevant? + false + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index 915b46652f2..a76b7a260c4 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -19,12 +19,20 @@ module Gitlab private def create_node(key, essence) - Node::Job.new(essence).tap do |job| + fabricate_job(key, essence).tap do |job| job.key = key job.parent = self job.description = "#{key} job definition." end end + + def fabricate_job(key, essence) + if key.to_s.start_with?('.') + Node::HiddenJob.new(essence) + else + Node::Job.new(essence) + end + end end end end diff --git a/lib/gitlab/ci/config/node/validator.rb b/lib/gitlab/ci/config/node/validator.rb index 758a6cf4356..dcfeb194374 100644 --- a/lib/gitlab/ci/config/node/validator.rb +++ b/lib/gitlab/ci/config/node/validator.rb @@ -31,7 +31,7 @@ module Gitlab def location predecessors = ancestors.map(&:key).compact - current = key || @node.class.name.demodulize.underscore + current = key || @node.class.name.demodulize.underscore.humanize predecessors.append(current).join(':') end end diff --git a/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb b/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb new file mode 100644 index 00000000000..ab865c3522e --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::HiddenJob do + let(:entry) { described_class.new(config) } + + describe 'validations' do + context 'when entry config value is correct' do + let(:config) { { image: 'ruby:2.2' } } + + describe '#value' do + it 'returns key value' do + expect(entry.value).to eq(image: 'ruby:2.2') + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not correct' do + context 'incorrect config value type' do + let(:config) { ['incorrect'] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'hidden job config should be a hash' + end + end + end + end + end + + describe '#leaf?' do + it 'is a leaf' do + expect(entry).to be_leaf + end + end + + describe '#relevant?' do + it 'is not a relevant entry' do + expect(entry).not_to be_relevant + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index 7dd25a23c83..15c7f9bc394 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -33,4 +33,10 @@ describe Gitlab::Ci::Config::Node::Job do end end end + + describe '#relevant?' do + it 'is a relevant entry' do + expect(entry).to be_relevant + end + end end diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb index 7f80e11cea3..b2d2a92d9e8 100644 --- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -36,18 +36,29 @@ describe Gitlab::Ci::Config::Node::Jobs do end end - describe '#descendants' do + context 'when valid job entries processed' do before { entry.process! } let(:config) do { rspec: { script: 'rspec' }, - spinach: { script: 'spinach' } } + spinach: { script: 'spinach' }, + '.hidden'.to_sym => {} } end - it 'creates two descendant nodes' do - expect(entry.descendants.count).to eq 2 - expect(entry.descendants) - .to all(be_an_instance_of(Gitlab::Ci::Config::Node::Job)) + describe '#descendants' do + it 'creates valid descendant nodes' do + expect(entry.descendants.count).to eq 3 + expect(entry.descendants.first(2)) + .to all(be_an_instance_of(Gitlab::Ci::Config::Node::Job)) + expect(entry.descendants.last) + .to be_an_instance_of(Gitlab::Ci::Config::Node::HiddenJob) + end + end + + describe '#value' do + it 'returns value of visible jobs only' do + expect(entry.value.keys).to eq [:rspec, :spinach] + end end end end -- cgit v1.2.1 From 580c4e1841cf4756e86c1ec9eddef56e2bfc9c97 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 6 Jul 2016 13:08:40 +0200 Subject: Move decision about relevant jobs to new CI config --- lib/ci/gitlab_ci_yaml_processor.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 8d0bbe1ae56..d226ebc3229 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -76,8 +76,6 @@ module Ci end def add_job(name, job) - return if name.to_s.start_with?('.') - raise ValidationError, "Unknown parameter: #{name}" unless job.is_a?(Hash) && job.has_key?(:script) stage = job[:stage] || job[:type] || DEFAULT_STAGE -- cgit v1.2.1 From 4491bf28e10da258701b316f397c5802f5f9974e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 6 Jul 2016 14:08:19 +0200 Subject: Move CI job config validations to new classes --- lib/ci/gitlab_ci_yaml_processor.rb | 2 -- lib/gitlab/ci/config/node/entry.rb | 1 + lib/gitlab/ci/config/node/jobs.rb | 15 ++++++++++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 9 +++++- spec/lib/gitlab/ci/config/node/global_spec.rb | 7 +++-- spec/lib/gitlab/ci/config/node/jobs_spec.rb | 42 +++++++++++++++++++-------- 6 files changed, 59 insertions(+), 17 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index d226ebc3229..ab77d4df841 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -71,8 +71,6 @@ module Ci @ci_config.jobs.each do |name, param| add_job(name, param) end - - raise ValidationError, "Please define at least one job" if @jobs.none? end def add_job(name, job) diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index fa6569e8bb2..033f9f0e3d1 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -24,6 +24,7 @@ module Gitlab compose! process_nodes! + @validator.validate(:processed) end def leaf? diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index a76b7a260c4..e8e78e4088d 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -10,12 +10,27 @@ module Gitlab validations do validates :config, type: Hash + validate :jobs_presence, on: :processed + + def jobs_presence + unless relevant? + errors.add(:config, 'should contain at least one visible job') + end + end end def nodes @config end + def relevant? + @nodes.values.any?(&:relevant?) + end + + def leaf? + false + end + private def create_node(key, essence) diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 49a786191b8..ac058ba1595 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1061,7 +1061,14 @@ EOT config = YAML.dump({ before_script: ["bundle update"] }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Please define at least one job") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs config should contain at least one visible job") + end + + it "returns errors if there are no visible jobs defined" do + config = YAML.dump({ before_script: ["bundle update"], '.hidden'.to_sym => {} }) + expect do + GitlabCiYamlProcessor.new(config, path) + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs config should contain at least one visible job") end it "returns errors if job allow_failure parameter is not an boolean" do diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 254cb28190c..a98de73c06c 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -108,7 +108,10 @@ describe Gitlab::Ci::Config::Node::Global do end context 'when deprecated types key defined' do - let(:hash) { { types: ['test', 'deploy'] } } + let(:hash) do + { types: ['test', 'deploy'], + rspec: { script: 'rspec' } } + end it 'returns array of types as stages' do expect(global.stages).to eq %w[test deploy] @@ -174,7 +177,7 @@ describe Gitlab::Ci::Config::Node::Global do # details. # context 'when entires specified but not defined' do - let(:hash) { { variables: nil } } + let(:hash) { { variables: nil, rspec: { script: 'rspec' } } } before { global.process! } describe '#variables' do diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb index b2d2a92d9e8..7ec28b642b4 100644 --- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -4,17 +4,9 @@ describe Gitlab::Ci::Config::Node::Jobs do let(:entry) { described_class.new(config) } describe 'validations' do - before { entry.process! } - context 'when entry config value is correct' do let(:config) { { rspec: { script: 'rspec' } } } - describe '#value' do - it 'returns key value' do - expect(entry.value).to eq(rspec: { script: 'rspec' }) - end - end - describe '#valid?' do it 'is valid' do expect(entry).to be_valid @@ -23,15 +15,34 @@ describe Gitlab::Ci::Config::Node::Jobs do end context 'when entry value is not correct' do - context 'incorrect config value type' do - let(:config) { ['incorrect'] } + describe '#errors' do + context 'incorrect config value type' do + let(:config) { ['incorrect'] } - describe '#errors' do - it 'saves errors' do + it 'returns error about incorrect type' do expect(entry.errors) .to include 'jobs config should be a hash' end end + + context 'when no visible jobs present' do + let(:config) { { '.hidden'.to_sym => {} } } + + context 'when not processed' do + it 'is valid' do + expect(entry.errors).to be_empty + end + end + + context 'when processed' do + before { entry.process! } + + it 'returns error about no visible jobs defined' do + expect(entry.errors) + .to include 'jobs config should contain at least one visible job' + end + end + end end end end @@ -45,6 +56,13 @@ describe Gitlab::Ci::Config::Node::Jobs do '.hidden'.to_sym => {} } end + describe '#value' do + it 'returns key value' do + expect(entry.value).to eq(rspec: { script: 'rspec' }, + spinach: { script: 'spinach' }) + end + end + describe '#descendants' do it 'creates valid descendant nodes' do expect(entry.descendants.count).to eq 3 -- cgit v1.2.1 From 6f02da2c4e069ef4ab550dc43176dc0563c95017 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 6 Jul 2016 14:24:31 +0200 Subject: Simplify legacy CI config processor a little --- lib/ci/gitlab_ci_yaml_processor.rb | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index ab77d4df841..f0710690985 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -26,7 +26,6 @@ module Ci end initial_parsing - validate! rescue Gitlab::Ci::Config::Loader::FormatError => e raise ValidationError, e.message end @@ -71,6 +70,10 @@ module Ci @ci_config.jobs.each do |name, param| add_job(name, param) end + + @jobs.each do |name, job| + validate_job!(name, job) + end end def add_job(name, job) @@ -108,14 +111,6 @@ module Ci } end - def validate! - @jobs.each do |name, job| - validate_job!(name, job) - end - - true - end - def validate_job!(name, job) validate_job_name!(name) validate_job_keys!(name, job) -- cgit v1.2.1 From b0ae0d730f4eda34cd712477a828dddd888ba474 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 7 Jul 2016 10:23:47 +0200 Subject: Use only node factory to create CI config entries --- lib/gitlab/ci/config/node/factory.rb | 28 +++++++++++++++----------- lib/gitlab/ci/config/node/global.rb | 4 ++-- lib/gitlab/ci/config/node/jobs.rb | 20 +++++++----------- spec/lib/gitlab/ci/config/node/factory_spec.rb | 12 +++++++++++ 4 files changed, 37 insertions(+), 27 deletions(-) diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb index 5919a283283..b1457b81a45 100644 --- a/lib/gitlab/ci/config/node/factory.rb +++ b/lib/gitlab/ci/config/node/factory.rb @@ -21,26 +21,30 @@ module Gitlab def create! raise InvalidFactory unless @attributes.has_key?(:value) - fabricate.tap do |entry| - entry.key = @attributes[:key] - entry.parent = @attributes[:parent] - entry.description = @attributes[:description] - end - end - - private - - def fabricate ## # We assume that unspecified entry is undefined. # See issue #18775. # if @attributes[:value].nil? - Node::Undefined.new(@node) + fabricate(Node::Undefined, @node) else - @node.new(@attributes[:value]) + fabricate(@node, @attributes[:value]) + end + end + + def self.fabricate(node, value, **attributes) + node.new(value).tap do |entry| + entry.key = attributes[:key] + entry.parent = attributes[:parent] + entry.description = attributes[:description] end end + + private + + def fabricate(node, value) + self.class.fabricate(node, value, @attributes) + end end end end diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index cb2db4e9757..64d8e39093d 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -42,8 +42,8 @@ module Gitlab def initialize(config) return super unless config.is_a?(Hash) - jobs = config.except(*self.class.nodes.keys) - global = config.slice(*self.class.nodes.keys) + jobs = config.except(*nodes.keys) + global = config.slice(*nodes.keys) super(global.merge(jobs: jobs)) end diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index e8e78e4088d..1cd2dc8f5b3 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -33,20 +33,14 @@ module Gitlab private - def create_node(key, essence) - fabricate_job(key, essence).tap do |job| - job.key = key - job.parent = self - job.description = "#{key} job definition." - end - end + def create_node(key, value) + node = key.to_s.start_with?('.') ? Node::HiddenJob : Node::Job - def fabricate_job(key, essence) - if key.to_s.start_with?('.') - Node::HiddenJob.new(essence) - else - Node::Job.new(essence) - end + attributes = { key: key, + parent: self, + description: "#{key} job definition." } + + Node::Factory.fabricate(node, value, attributes) end end end diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb index 91ddef7bfbf..5a26bb6df02 100644 --- a/spec/lib/gitlab/ci/config/node/factory_spec.rb +++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb @@ -5,6 +5,18 @@ describe Gitlab::Ci::Config::Node::Factory do let(:factory) { described_class.new(entry_class) } let(:entry_class) { Gitlab::Ci::Config::Node::Script } + describe '.fabricate' do + it 'fabricates entry with attributes set' do + fabricated = described_class + .fabricate(entry_class, ['ls'], + parent: factory, key: :test) + + expect(fabricated.parent).to be factory + expect(fabricated.key).to eq :test + expect(fabricated.value).to eq ['ls'] + end + end + context 'when setting up a value' do it 'creates entry with valid value' do entry = factory -- cgit v1.2.1 From fea7762485c75003381891bc892bc6049f8a2105 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 7 Jul 2016 12:41:31 +0200 Subject: Delegate methods to default CI entry if undefined --- lib/gitlab/ci/config/node/entry.rb | 5 ++ lib/gitlab/ci/config/node/undefined.rb | 29 ++++++++++-- spec/lib/gitlab/ci/config/node/factory_spec.rb | 6 ++- spec/lib/gitlab/ci/config/node/undefined_spec.rb | 59 ++++++++++++++++++------ 4 files changed, 79 insertions(+), 20 deletions(-) diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 033f9f0e3d1..8fda37a8922 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -14,6 +14,7 @@ module Gitlab def initialize(config) @config = config @nodes = {} + @validator = self.class.validator.new(self) @validator.validate end @@ -71,6 +72,10 @@ module Gitlab true end + def attributes + { key: @key, parent: @parent, description: @description } + end + def self.default end diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb index 699605e1e3a..f152c433c42 100644 --- a/lib/gitlab/ci/config/node/undefined.rb +++ b/lib/gitlab/ci/config/node/undefined.rb @@ -5,8 +5,9 @@ module Gitlab ## # This class represents an undefined entry node. # - # It takes original entry class as configuration and returns default - # value of original entry as self value. + # It takes original entry class as configuration and creates an object + # if original entry has a default value. If there is default value + # some methods are delegated to it. # # class Undefined < Entry @@ -16,13 +17,35 @@ module Gitlab validates :config, type: Class end + def initialize(node) + super + + unless node.default.nil? + @default = fabricate_default(node) + end + end + def value - @config.default + @default.value if @default + end + + def valid? + @default ? @default.valid? : true + end + + def errors + @default ? @default.errors : [] end def defined? false end + + private + + def fabricate_default(node) + Node::Factory.fabricate(node, node.default, attributes) + end end end end diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb index 5a26bb6df02..5b856d44989 100644 --- a/spec/lib/gitlab/ci/config/node/factory_spec.rb +++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb @@ -9,11 +9,13 @@ describe Gitlab::Ci::Config::Node::Factory do it 'fabricates entry with attributes set' do fabricated = described_class .fabricate(entry_class, ['ls'], - parent: factory, key: :test) + parent: true, key: :test) - expect(fabricated.parent).to be factory + expect(fabricated.parent).to be true expect(fabricated.key).to eq :test expect(fabricated.value).to eq ['ls'] + expect(fabricated.attributes) + .to eq(parent: true, key: :test, description: nil) end end diff --git a/spec/lib/gitlab/ci/config/node/undefined_spec.rb b/spec/lib/gitlab/ci/config/node/undefined_spec.rb index 0c6608d906d..417b4a0ad6f 100644 --- a/spec/lib/gitlab/ci/config/node/undefined_spec.rb +++ b/spec/lib/gitlab/ci/config/node/undefined_spec.rb @@ -2,33 +2,62 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Undefined do let(:undefined) { described_class.new(entry) } - let(:entry) { Class.new } + let(:entry) { spy('Entry') } - describe '#leaf?' do - it 'is leaf node' do - expect(undefined).to be_leaf + context 'when entry does not have a default value' do + before { allow(entry).to receive(:default).and_return(nil) } + + describe '#leaf?' do + it 'is leaf node' do + expect(undefined).to be_leaf + end end - end - describe '#valid?' do - it 'is always valid' do - expect(undefined).to be_valid + describe '#valid?' do + it 'is always valid' do + expect(undefined).to be_valid + end end - end - describe '#errors' do - it 'is does not contain errors' do - expect(undefined.errors).to be_empty + describe '#errors' do + it 'is does not contain errors' do + expect(undefined.errors).to be_empty + end + end + + describe '#value' do + it 'returns nil' do + expect(undefined.value).to eq nil + end end end - describe '#value' do + context 'when entry has a default value' do before do allow(entry).to receive(:default).and_return('some value') + allow(entry).to receive(:value).and_return('some value') end - it 'returns default value for entry' do - expect(undefined.value).to eq 'some value' + describe '#value' do + it 'returns default value for entry' do + expect(undefined.value).to eq 'some value' + end + end + + describe '#errors' do + it 'delegates errors to default entry' do + expect(entry).to receive(:errors) + + undefined.errors + end + end + + describe '#valid?' do + it 'delegates valid? to default entry' do + expect(entry).to receive(:valid?) + + undefined.valid? + end end end -- cgit v1.2.1 From de8b93bbff65844438d7dfbde178746c3585bd92 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 7 Jul 2016 12:56:00 +0200 Subject: Improve validation of CI config entry if composite --- lib/gitlab/ci/config/node/entry.rb | 12 ++++-------- lib/gitlab/ci/config/node/jobs.rb | 4 ---- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 8fda37a8922..97e17b89c40 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -20,12 +20,8 @@ module Gitlab end def process! - return if leaf? - return unless valid? - - compose! - process_nodes! - @validator.validate(:processed) + compose! unless leaf? + @validator.validate(:processed) if valid? end def leaf? @@ -90,12 +86,12 @@ module Gitlab private def compose! + return unless valid? + nodes.each do |key, essence| @nodes[key] = create_node(key, essence) end - end - def process_nodes! @nodes.each_value(&:process!) end diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index 1cd2dc8f5b3..71893ba1d88 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -27,10 +27,6 @@ module Gitlab @nodes.values.any?(&:relevant?) end - def leaf? - false - end - private def create_node(key, value) -- cgit v1.2.1 From ecdcf04e88f6313ae8773e7b9886bc983adab83d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 7 Jul 2016 13:09:36 +0200 Subject: Add undefined CI node strategies to handle defaults --- lib/gitlab/ci/config/node/undefined.rb | 51 +++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 16 deletions(-) diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb index f152c433c42..7b18e364675 100644 --- a/lib/gitlab/ci/config/node/undefined.rb +++ b/lib/gitlab/ci/config/node/undefined.rb @@ -13,38 +13,57 @@ module Gitlab class Undefined < Entry include Validatable + delegate :valid?, :errors, :value, to: :@strategy + validations do validates :config, type: Class end def initialize(node) super - - unless node.default.nil? - @default = fabricate_default(node) - end + @strategy = create_strategy(node, node.default) end - def value - @default.value if @default + def defined? + false end - def valid? - @default ? @default.valid? : true - end + private + + def create_strategy(node, default) + if default.nil? + Undefined::NullStrategy.new + else + entry = Node::Factory + .fabricate(node, default, attributes) - def errors - @default ? @default.errors : [] + Undefined::DefaultStrategy.new(entry) + end end - def defined? - false + class DefaultStrategy + delegate :valid?, :errors, :value, to: :@default + + def initialize(entry) + @default = entry + end end - private + class NullStrategy + def initialize(*) + end - def fabricate_default(node) - Node::Factory.fabricate(node, node.default, attributes) + def value + nil + end + + def valid? + true + end + + def errors + [] + end end end end -- cgit v1.2.1 From 9410aecca87a1c03f7e7fb1e5e1073460c71b6e5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 7 Jul 2016 13:39:13 +0200 Subject: Add scaffold of CI config for the job stage entry --- lib/gitlab/ci/config/node/stage.rb | 22 +++++++++++++ spec/lib/gitlab/ci/config/node/stage_spec.rb | 46 ++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 lib/gitlab/ci/config/node/stage.rb create mode 100644 spec/lib/gitlab/ci/config/node/stage_spec.rb diff --git a/lib/gitlab/ci/config/node/stage.rb b/lib/gitlab/ci/config/node/stage.rb new file mode 100644 index 00000000000..53ceafaa3f4 --- /dev/null +++ b/lib/gitlab/ci/config/node/stage.rb @@ -0,0 +1,22 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a stage for a job. + # + class Stage < Entry + include Validatable + + validations do + validates :config, key: true + end + + def self.default + :test + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/stage_spec.rb b/spec/lib/gitlab/ci/config/node/stage_spec.rb new file mode 100644 index 00000000000..653d613ba6e --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/stage_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Stage do + let(:entry) { described_class.new(config) } + + describe 'validations' do + context 'when entry config value is correct' do + let(:config) { :stage1 } + + describe '#value' do + it 'returns a stage key' do + expect(entry.value).to eq config + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + context 'when entry config is incorrect' do + let(:config) { { test: true } } + + describe '#errors' do + it 'reports errors' do + expect(entry.errors) + .to include 'stage config should be a string or symbol' + end + end + + describe '#valid?' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end + end + end + + describe '.default' do + it 'returns default stage' do + expect(described_class.default).to eq :test + end + end +end -- cgit v1.2.1 From a7ac2f74944430d75b090f78cd9c1cf1d24379f6 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 7 Jul 2016 15:00:35 +0200 Subject: Simplify CI config entry node factory, use attribs --- lib/gitlab/ci/config/node/configurable.rb | 4 +++- lib/gitlab/ci/config/node/entry.rb | 14 ++++++------- lib/gitlab/ci/config/node/factory.rb | 27 +++++++++----------------- lib/gitlab/ci/config/node/global.rb | 4 ++++ lib/gitlab/ci/config/node/jobs.rb | 10 +++++----- lib/gitlab/ci/config/node/undefined.rb | 6 ++---- spec/lib/gitlab/ci/config/node/factory_spec.rb | 26 +++++++------------------ 7 files changed, 37 insertions(+), 54 deletions(-) diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 37936fc8242..88403a9de1e 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -26,7 +26,9 @@ module Gitlab private def create_node(key, factory) - factory.with(value: @config[key], key: key, parent: self) + factory + .value(config[key]) + .with(key: key, parent: self, global: global) factory.create! end diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 97e17b89c40..e8b0160edc1 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -8,13 +8,17 @@ module Gitlab class Entry class InvalidError < StandardError; end - attr_reader :config - attr_accessor :key, :parent, :description + attr_reader :config, :attributes + attr_accessor :key, :parent, :global, :description - def initialize(config) + def initialize(config, **attributes) @config = config @nodes = {} + (@attributes = attributes).each do |attribute, value| + public_send("#{attribute}=", value) + end + @validator = self.class.validator.new(self) @validator.validate end @@ -68,10 +72,6 @@ module Gitlab true end - def attributes - { key: @key, parent: @parent, description: @description } - end - def self.default end diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb index b1457b81a45..3f2cdf436e3 100644 --- a/lib/gitlab/ci/config/node/factory.rb +++ b/lib/gitlab/ci/config/node/factory.rb @@ -13,38 +13,29 @@ module Gitlab @attributes = {} end + def value(value) + @value = value + self + end + def with(attributes) @attributes.merge!(attributes) self end def create! - raise InvalidFactory unless @attributes.has_key?(:value) + raise InvalidFactory unless defined?(@value) ## # We assume that unspecified entry is undefined. # See issue #18775. # - if @attributes[:value].nil? - fabricate(Node::Undefined, @node) + if @value.nil? + Node::Undefined.new(@node, @attributes) else - fabricate(@node, @attributes[:value]) + @node.new(@value, @attributes) end end - - def self.fabricate(node, value, **attributes) - node.new(value).tap do |entry| - entry.key = attributes[:key] - entry.parent = attributes[:parent] - entry.description = attributes[:description] - end - end - - private - - def fabricate(node, value) - self.class.fabricate(node, value, @attributes) - end end end end diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index 64d8e39093d..dffa3326630 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -51,6 +51,10 @@ module Gitlab def stages stages_defined? ? stages_value : types_value end + + def global + self + end end end end diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index 71893ba1d88..d7d61ade36d 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -30,13 +30,13 @@ module Gitlab private def create_node(key, value) - node = key.to_s.start_with?('.') ? Node::HiddenJob : Node::Job + job_node = key.to_s.start_with?('.') ? Node::HiddenJob : Node::Job - attributes = { key: key, - parent: self, - description: "#{key} job definition." } + job_attributes = { key: key, + parent: self, + description: "#{key} job definition." } - Node::Factory.fabricate(node, value, attributes) + job_node.new(value, attributes.merge(job_attributes)) end end end diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb index 7b18e364675..fedb9d020be 100644 --- a/lib/gitlab/ci/config/node/undefined.rb +++ b/lib/gitlab/ci/config/node/undefined.rb @@ -19,7 +19,7 @@ module Gitlab validates :config, type: Class end - def initialize(node) + def initialize(node, **attributes) super @strategy = create_strategy(node, node.default) end @@ -34,9 +34,7 @@ module Gitlab if default.nil? Undefined::NullStrategy.new else - entry = Node::Factory - .fabricate(node, default, attributes) - + entry = node.new(default, attributes) Undefined::DefaultStrategy.new(entry) end end diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb index 5b856d44989..c912b1b2044 100644 --- a/spec/lib/gitlab/ci/config/node/factory_spec.rb +++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb @@ -5,24 +5,10 @@ describe Gitlab::Ci::Config::Node::Factory do let(:factory) { described_class.new(entry_class) } let(:entry_class) { Gitlab::Ci::Config::Node::Script } - describe '.fabricate' do - it 'fabricates entry with attributes set' do - fabricated = described_class - .fabricate(entry_class, ['ls'], - parent: true, key: :test) - - expect(fabricated.parent).to be true - expect(fabricated.key).to eq :test - expect(fabricated.value).to eq ['ls'] - expect(fabricated.attributes) - .to eq(parent: true, key: :test, description: nil) - end - end - context 'when setting up a value' do it 'creates entry with valid value' do entry = factory - .with(value: ['ls', 'pwd']) + .value(['ls', 'pwd']) .create! expect(entry.value).to eq ['ls', 'pwd'] @@ -31,7 +17,7 @@ describe Gitlab::Ci::Config::Node::Factory do context 'when setting description' do it 'creates entry with description' do entry = factory - .with(value: ['ls', 'pwd']) + .value(['ls', 'pwd']) .with(description: 'test description') .create! @@ -43,7 +29,8 @@ describe Gitlab::Ci::Config::Node::Factory do context 'when setting key' do it 'creates entry with custom key' do entry = factory - .with(value: ['ls', 'pwd'], key: 'test key') + .value(['ls', 'pwd']) + .with(key: 'test key') .create! expect(entry.key).to eq 'test key' @@ -55,7 +42,8 @@ describe Gitlab::Ci::Config::Node::Factory do it 'creates entry with valid parent' do entry = factory - .with(value: 'ls', parent: parent) + .value('ls') + .with(parent: parent) .create! expect(entry.parent).to eq parent @@ -74,7 +62,7 @@ describe Gitlab::Ci::Config::Node::Factory do context 'when creating entry with nil value' do it 'creates an undefined entry' do entry = factory - .with(value: nil) + .value(nil) .create! expect(entry).to be_an_instance_of Gitlab::Ci::Config::Node::Undefined -- cgit v1.2.1 From f067202e9b7a4304ffb8d68408880f7eb7fc8b34 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 7 Jul 2016 15:06:05 +0200 Subject: Improve creating CI config entries for jobs config --- lib/gitlab/ci/config/node/jobs.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index d7d61ade36d..cba1fce4a4c 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -29,14 +29,18 @@ module Gitlab private - def create_node(key, value) - job_node = key.to_s.start_with?('.') ? Node::HiddenJob : Node::Job + def create_node(name, config) + job_node(name).new(config, job_attributes(name)) + end - job_attributes = { key: key, - parent: self, - description: "#{key} job definition." } + def job_node(name) + name.to_s.start_with?('.') ? Node::HiddenJob : Node::Job + end - job_node.new(value, attributes.merge(job_attributes)) + def job_attributes(name) + @attributes.merge(key: name, + parent: self, + description: "#{name} job definition.") end end end -- cgit v1.2.1 From 3da57c800bf0c4fe3c45dbea3cff4179f6aa124f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 7 Jul 2016 15:15:44 +0200 Subject: Require reference to CI config for some entries --- lib/gitlab/ci/config/node/stage.rb | 7 +++++++ spec/lib/gitlab/ci/config/node/stage_spec.rb | 31 ++++++++++++++++++++-------- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/lib/gitlab/ci/config/node/stage.rb b/lib/gitlab/ci/config/node/stage.rb index 53ceafaa3f4..c7e12f291d7 100644 --- a/lib/gitlab/ci/config/node/stage.rb +++ b/lib/gitlab/ci/config/node/stage.rb @@ -10,6 +10,13 @@ module Gitlab validations do validates :config, key: true + + validate do |entry| + unless entry.global + raise Entry::InvalidError, + 'This entry needs reference to global configuration' + end + end end def self.default diff --git a/spec/lib/gitlab/ci/config/node/stage_spec.rb b/spec/lib/gitlab/ci/config/node/stage_spec.rb index 653d613ba6e..92150ea5337 100644 --- a/spec/lib/gitlab/ci/config/node/stage_spec.rb +++ b/spec/lib/gitlab/ci/config/node/stage_spec.rb @@ -1,11 +1,12 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Stage do - let(:entry) { described_class.new(config) } + let(:entry) { described_class.new(config, global: global) } + let(:global) { spy('Global') } describe 'validations' do context 'when entry config value is correct' do - let(:config) { :stage1 } + let(:config) { :build } describe '#value' do it 'returns a stage key' do @@ -18,20 +19,32 @@ describe Gitlab::Ci::Config::Node::Stage do expect(entry).to be_valid end end + end + + context 'when entry config is incorrect' do + describe '#errors' do + context 'when reference to global node is not set' do + let(:entry) { described_class.new(config) } + + it 'raises error' do + expect { entry } + .to raise_error Gitlab::Ci::Config::Node::Entry::InvalidError + end + end - context 'when entry config is incorrect' do - let(:config) { { test: true } } + context 'when value has a wrong type' do + let(:config) { { test: true } } - describe '#errors' do - it 'reports errors' do + it 'reports errors about wrong type' do expect(entry.errors) .to include 'stage config should be a string or symbol' end end - describe '#valid?' do - it 'is not valid' do - expect(entry).not_to be_valid + context 'when stage is not present in global configuration' do + pending 'reports error about missing stage' do + expect(entry.errors) + .to include 'stage config should be one of test, stage' end end end -- cgit v1.2.1 From 8baee987beaea8197d28ee9715ef23f5813566e5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 8 Jul 2016 11:27:36 +0200 Subject: Extract internal attributes validator for CI entry --- lib/gitlab/ci/config/node/stage.rb | 8 +------- lib/gitlab/ci/config/node/validators.rb | 9 +++++++++ spec/lib/gitlab/ci/config/node/stage_spec.rb | 6 ++++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/gitlab/ci/config/node/stage.rb b/lib/gitlab/ci/config/node/stage.rb index c7e12f291d7..9c76cf7c0b7 100644 --- a/lib/gitlab/ci/config/node/stage.rb +++ b/lib/gitlab/ci/config/node/stage.rb @@ -10,13 +10,7 @@ module Gitlab validations do validates :config, key: true - - validate do |entry| - unless entry.global - raise Entry::InvalidError, - 'This entry needs reference to global configuration' - end - end + validates :global, required_attribute: true end def self.default diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb index 7b2f57990b5..6f0e14e2f0a 100644 --- a/lib/gitlab/ci/config/node/validators.rb +++ b/lib/gitlab/ci/config/node/validators.rb @@ -33,6 +33,15 @@ module Gitlab end end + class RequiredAttributeValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + if value.nil? + raise Entry::InvalidError, + "Entry needs #{attribute} attribute set internally." + end + end + end + class KeyValidator < ActiveModel::EachValidator include LegacyValidationHelpers diff --git a/spec/lib/gitlab/ci/config/node/stage_spec.rb b/spec/lib/gitlab/ci/config/node/stage_spec.rb index 92150ea5337..4047d46c80f 100644 --- a/spec/lib/gitlab/ci/config/node/stage_spec.rb +++ b/spec/lib/gitlab/ci/config/node/stage_spec.rb @@ -27,8 +27,10 @@ describe Gitlab::Ci::Config::Node::Stage do let(:entry) { described_class.new(config) } it 'raises error' do - expect { entry } - .to raise_error Gitlab::Ci::Config::Node::Entry::InvalidError + expect { entry }.to raise_error( + Gitlab::Ci::Config::Node::Entry::InvalidError, + /Entry needs global attribute set internally./ + ) end end -- cgit v1.2.1 From 159aed1c4220277ece9b4496d460f931cd65e228 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 8 Jul 2016 11:29:03 +0200 Subject: Extract global CI config entry configuration setup --- lib/gitlab/ci/config/node/global.rb | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index dffa3326630..110d982588b 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -39,21 +39,24 @@ module Gitlab helpers :before_script, :image, :services, :after_script, :variables, :stages, :types, :cache, :jobs - def initialize(config) - return super unless config.is_a?(Hash) - - jobs = config.except(*nodes.keys) - global = config.slice(*nodes.keys) - - super(global.merge(jobs: jobs)) + def initialize(config, **attributes) + super(setup(config), attributes) + @global = self end def stages stages_defined? ? stages_value : types_value end - def global - self + private + + def setup(config) + return config unless config.is_a?(Hash) + + jobs = config.except(*nodes.keys) + global = config.slice(*nodes.keys) + + global.merge(jobs: jobs) end end end -- cgit v1.2.1 From 1ac62de2c12a26e6f5158cdb4f008a71729b39fc Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 8 Jul 2016 12:51:47 +0200 Subject: Extract CI entry node validator and improve naming --- lib/gitlab/ci/config.rb | 1 + lib/gitlab/ci/config/node/configurable.rb | 6 ++--- lib/gitlab/ci/config/node/entry.rb | 36 ++++++++++++++--------------- lib/gitlab/ci/config/node/jobs.rb | 6 ++--- spec/lib/gitlab/ci/config/node/jobs_spec.rb | 5 +++- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index ae82c0db3f1..20f5f8e2ff8 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -15,6 +15,7 @@ module Gitlab @global = Node::Global.new(@config) @global.process! + @global.validate! end def valid? diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 88403a9de1e..e5780c60e70 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -25,7 +25,7 @@ module Gitlab private - def create_node(key, factory) + def create(key, factory) factory .value(config[key]) .with(key: key, parent: self, global: global) @@ -50,12 +50,12 @@ module Gitlab def helpers(*nodes) nodes.each do |symbol| define_method("#{symbol}_defined?") do - @nodes[symbol].try(:defined?) + @entries[symbol].try(:defined?) end define_method("#{symbol}_value") do raise Entry::InvalidError unless valid? - @nodes[symbol].try(:value) + @entries[symbol].try(:value) end alias_method symbol.to_sym, "#{symbol}_value".to_sym diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index e8b0160edc1..67e59ffb86e 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -13,7 +13,7 @@ module Gitlab def initialize(config, **attributes) @config = config - @nodes = {} + @entries = {} (@attributes = attributes).each do |attribute, value| public_send("#{attribute}=", value) @@ -24,8 +24,18 @@ module Gitlab end def process! - compose! unless leaf? - @validator.validate(:processed) if valid? + return unless valid? + + nodes.each do |key, essence| + @entries[key] = create(key, essence) + end + + @entries.each_value(&:process!) + end + + def validate! + @validator.validate(:after) + @entries.each_value(&:validate!) end def leaf? @@ -37,7 +47,7 @@ module Gitlab end def descendants - @nodes.values + @entries.values end def ancestors @@ -49,18 +59,18 @@ module Gitlab end def errors - @validator.messages + @nodes.values.flat_map(&:errors) + @validator.messages + @entries.values.flat_map(&:errors) end def value if leaf? @config else - meaningful = @nodes.select do |_key, value| + meaningful = @entries.select do |_key, value| value.defined? && value.relevant? end - Hash[meaningful.map { |key, node| [key, node.value] }] + Hash[meaningful.map { |key, entry| [key, entry.value] }] end end @@ -85,17 +95,7 @@ module Gitlab private - def compose! - return unless valid? - - nodes.each do |key, essence| - @nodes[key] = create_node(key, essence) - end - - @nodes.each_value(&:process!) - end - - def create_node(key, essence) + def create(entry, essence) raise NotImplementedError end end diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index cba1fce4a4c..6199749a508 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -10,7 +10,7 @@ module Gitlab validations do validates :config, type: Hash - validate :jobs_presence, on: :processed + validate :jobs_presence, on: :after def jobs_presence unless relevant? @@ -24,12 +24,12 @@ module Gitlab end def relevant? - @nodes.values.any?(&:relevant?) + @entries.values.any?(&:relevant?) end private - def create_node(name, config) + def create(name, config) job_node(name).new(config, job_attributes(name)) end diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb index 7ec28b642b4..1ecc3e18d4e 100644 --- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -35,7 +35,10 @@ describe Gitlab::Ci::Config::Node::Jobs do end context 'when processed' do - before { entry.process! } + before do + entry.process! + entry.validate! + end it 'returns error about no visible jobs defined' do expect(entry.errors) -- cgit v1.2.1 From d9142f2c97524fc2d5af7dda79b849d1e23f4910 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 8 Jul 2016 13:31:41 +0200 Subject: Add CI config known stage validation for job stage --- lib/gitlab/ci/config/node/stage.rb | 13 +++++++ spec/lib/gitlab/ci/config/node/stage_spec.rb | 54 ++++++++++++++++++++++------ 2 files changed, 56 insertions(+), 11 deletions(-) diff --git a/lib/gitlab/ci/config/node/stage.rb b/lib/gitlab/ci/config/node/stage.rb index 9c76cf7c0b7..457f6dfa3ba 100644 --- a/lib/gitlab/ci/config/node/stage.rb +++ b/lib/gitlab/ci/config/node/stage.rb @@ -11,6 +11,19 @@ module Gitlab validations do validates :config, key: true validates :global, required_attribute: true + validate :known_stage, on: :after + + def known_stage + unless known? + stages_list = global.stages.join(', ') + errors.add(:config, + "should be one of defined stages (#{stages_list})") + end + end + end + + def known? + @global.stages.include?(@config) end def self.default diff --git a/spec/lib/gitlab/ci/config/node/stage_spec.rb b/spec/lib/gitlab/ci/config/node/stage_spec.rb index 4047d46c80f..95b46d76adb 100644 --- a/spec/lib/gitlab/ci/config/node/stage_spec.rb +++ b/spec/lib/gitlab/ci/config/node/stage_spec.rb @@ -1,33 +1,33 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Stage do - let(:entry) { described_class.new(config, global: global) } + let(:stage) { described_class.new(config, global: global) } let(:global) { spy('Global') } describe 'validations' do - context 'when entry config value is correct' do + context 'when stage config value is correct' do let(:config) { :build } describe '#value' do it 'returns a stage key' do - expect(entry.value).to eq config + expect(stage.value).to eq config end end describe '#valid?' do it 'is valid' do - expect(entry).to be_valid + expect(stage).to be_valid end end end - context 'when entry config is incorrect' do + context 'when stage config is incorrect' do describe '#errors' do context 'when reference to global node is not set' do - let(:entry) { described_class.new(config) } + let(:stage) { described_class.new(config) } it 'raises error' do - expect { entry }.to raise_error( + expect { stage }.to raise_error( Gitlab::Ci::Config::Node::Entry::InvalidError, /Entry needs global attribute set internally./ ) @@ -38,21 +38,53 @@ describe Gitlab::Ci::Config::Node::Stage do let(:config) { { test: true } } it 'reports errors about wrong type' do - expect(entry.errors) + expect(stage.errors) .to include 'stage config should be a string or symbol' end end context 'when stage is not present in global configuration' do - pending 'reports error about missing stage' do - expect(entry.errors) - .to include 'stage config should be one of test, stage' + let(:config) { :unknown } + + before do + allow(global) + .to receive(:stages).and_return([:test, :deploy]) + end + + it 'reports error about missing stage' do + stage.validate! + + expect(stage.errors) + .to include 'stage config should be one of ' \ + 'defined stages (test, deploy)' end end end end end + describe '#known?' do + before do + allow(global).to receive(:stages).and_return([:test, :deploy]) + end + + context 'when stage is not known' do + let(:config) { :unknown } + + it 'returns false' do + expect(stage.known?).to be false + end + end + + context 'when stage is known' do + let(:config) { :test } + + it 'returns false' do + expect(stage.known?).to be true + end + end + end + describe '.default' do it 'returns default stage' do expect(described_class.default).to eq :test -- cgit v1.2.1 From 1b624ba4eb423c39671b9f0440018e9783aa844b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 8 Jul 2016 13:36:01 +0200 Subject: Improve name of CI entry validation context hook --- lib/gitlab/ci/config/node/entry.rb | 2 +- lib/gitlab/ci/config/node/jobs.rb | 2 +- lib/gitlab/ci/config/node/stage.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 67e59ffb86e..2b16a81f88d 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -34,7 +34,7 @@ module Gitlab end def validate! - @validator.validate(:after) + @validator.validate(:processed) @entries.each_value(&:validate!) end diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index 6199749a508..f6acc25e4fb 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -10,7 +10,7 @@ module Gitlab validations do validates :config, type: Hash - validate :jobs_presence, on: :after + validate :jobs_presence, on: :processed def jobs_presence unless relevant? diff --git a/lib/gitlab/ci/config/node/stage.rb b/lib/gitlab/ci/config/node/stage.rb index 457f6dfa3ba..c15f46bc7a5 100644 --- a/lib/gitlab/ci/config/node/stage.rb +++ b/lib/gitlab/ci/config/node/stage.rb @@ -11,7 +11,7 @@ module Gitlab validations do validates :config, key: true validates :global, required_attribute: true - validate :known_stage, on: :after + validate :known_stage, on: :processed def known_stage unless known? -- cgit v1.2.1 From ccbdb4022ac87f7c30e970922be64bcea0b406e9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 9 Jul 2016 14:56:41 +0200 Subject: Integrate CI job stage entry into CI configuration --- lib/gitlab/ci/config/node/entry.rb | 18 ++++++++++++------ lib/gitlab/ci/config/node/job.rb | 24 ++++++++++++++++++++++++ lib/gitlab/ci/config/node/stage.rb | 4 ++-- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 6 +++--- spec/lib/gitlab/ci/config/node/global_spec.rb | 4 ++-- spec/lib/gitlab/ci/config/node/job_spec.rb | 12 ++++++++++-- spec/lib/gitlab/ci/config/node/jobs_spec.rb | 7 ++++--- spec/lib/gitlab/ci/config/node/stage_spec.rb | 18 +++++++++++------- 8 files changed, 68 insertions(+), 25 deletions(-) diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 2b16a81f88d..1940c39087b 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -20,21 +20,21 @@ module Gitlab end @validator = self.class.validator.new(self) - @validator.validate + @validator.validate(:new) end def process! return unless valid? - nodes.each do |key, essence| - @entries[key] = create(key, essence) - end - + compose! @entries.each_value(&:process!) end def validate! - @validator.validate(:processed) + if @validator.valid?(:new) + @validator.validate(:processed) + end + @entries.each_value(&:validate!) end @@ -95,6 +95,12 @@ module Gitlab private + def compose! + nodes.each do |key, essence| + @entries[key] = create(key, essence) + end + end + def create(entry, essence) raise NotImplementedError end diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 5be8cb39a87..4a9cc28d763 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -7,6 +7,30 @@ module Gitlab # class Job < Entry include Configurable + + node :stage, Stage, + description: 'Pipeline stage this job will be executed into.' + + node :type, Stage, + description: 'Deprecated: stage this job will be executed into.' + + helpers :stage, :type + + def value + @config.merge(stage: stage_value) + end + + private + + def compose! + super + + if type_defined? && !stage_defined? + @entries[:stage] = @entries[:type] + end + + @entries.delete(:type) + end end end end diff --git a/lib/gitlab/ci/config/node/stage.rb b/lib/gitlab/ci/config/node/stage.rb index c15f46bc7a5..e8fae65a2a9 100644 --- a/lib/gitlab/ci/config/node/stage.rb +++ b/lib/gitlab/ci/config/node/stage.rb @@ -9,7 +9,7 @@ module Gitlab include Validatable validations do - validates :config, key: true + validates :config, type: String validates :global, required_attribute: true validate :known_stage, on: :processed @@ -27,7 +27,7 @@ module Gitlab end def self.default - :test + 'test' end end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index ac058ba1595..03477e1ca13 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1082,21 +1082,21 @@ EOT config = YAML.dump({ rspec: { script: "test", type: 1 } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:type config should be a string") end it "returns errors if job stage is not a pre-defined stage" do config = YAML.dump({ rspec: { script: "test", type: "acceptance" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:type config should be one of defined stages (build, test, deploy)") end it "returns errors if job stage is not a defined stage" do config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", type: "acceptance" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:type config should be one of defined stages (build, test)") end it "returns errors if stages is not an array" do diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index a98de73c06c..0c56b59db0c 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -129,8 +129,8 @@ describe Gitlab::Ci::Config::Node::Global do describe '#jobs' do it 'returns jobs configuration' do expect(global.jobs) - .to eq(rspec: { script: 'rspec' }, - spinach: { script: 'spinach' }) + .to eq(rspec: { script: 'rspec', stage: 'test' }, + spinach: { script: 'spinach', stage: 'test' }) end end end diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index 15c7f9bc394..2a4296448fb 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -1,15 +1,23 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Job do - let(:entry) { described_class.new(config) } + let(:entry) { described_class.new(config, global: global) } + let(:global) { spy('Global') } describe 'validations' do + before do + entry.process! + entry.validate! + end + context 'when entry config value is correct' do let(:config) { { script: 'rspec' } } describe '#value' do it 'returns key value' do - expect(entry.value).to eq(script: 'rspec') + expect(entry.value) + .to eq(script: 'rspec', + stage: 'test') end end diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb index 1ecc3e18d4e..52018958dcf 100644 --- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Jobs do - let(:entry) { described_class.new(config) } + let(:entry) { described_class.new(config, global: spy) } describe 'validations' do context 'when entry config value is correct' do @@ -61,8 +61,9 @@ describe Gitlab::Ci::Config::Node::Jobs do describe '#value' do it 'returns key value' do - expect(entry.value).to eq(rspec: { script: 'rspec' }, - spinach: { script: 'spinach' }) + expect(entry.value) + .to eq(rspec: { script: 'rspec', stage: 'test' }, + spinach: { script: 'spinach', stage: 'test' }) end end diff --git a/spec/lib/gitlab/ci/config/node/stage_spec.rb b/spec/lib/gitlab/ci/config/node/stage_spec.rb index 95b46d76adb..6deeca1a6c0 100644 --- a/spec/lib/gitlab/ci/config/node/stage_spec.rb +++ b/spec/lib/gitlab/ci/config/node/stage_spec.rb @@ -6,7 +6,11 @@ describe Gitlab::Ci::Config::Node::Stage do describe 'validations' do context 'when stage config value is correct' do - let(:config) { :build } + let(:config) { 'build' } + + before do + allow(global).to receive(:stages).and_return(%w[build]) + end describe '#value' do it 'returns a stage key' do @@ -39,16 +43,16 @@ describe Gitlab::Ci::Config::Node::Stage do it 'reports errors about wrong type' do expect(stage.errors) - .to include 'stage config should be a string or symbol' + .to include 'stage config should be a string' end end context 'when stage is not present in global configuration' do - let(:config) { :unknown } + let(:config) { 'unknown' } before do allow(global) - .to receive(:stages).and_return([:test, :deploy]) + .to receive(:stages).and_return(%w[test deploy]) end it 'reports error about missing stage' do @@ -65,7 +69,7 @@ describe Gitlab::Ci::Config::Node::Stage do describe '#known?' do before do - allow(global).to receive(:stages).and_return([:test, :deploy]) + allow(global).to receive(:stages).and_return(%w[test deploy]) end context 'when stage is not known' do @@ -77,7 +81,7 @@ describe Gitlab::Ci::Config::Node::Stage do end context 'when stage is known' do - let(:config) { :test } + let(:config) { 'test' } it 'returns false' do expect(stage.known?).to be true @@ -87,7 +91,7 @@ describe Gitlab::Ci::Config::Node::Stage do describe '.default' do it 'returns default stage' do - expect(described_class.default).to eq :test + expect(described_class.default).to eq 'test' end end end -- cgit v1.2.1 From 9edced40dd7398d1aa553a5454f95ae629da2276 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 9 Jul 2016 16:51:26 +0200 Subject: Use node factory to assemble global CI config entry --- lib/gitlab/ci/config/node/configurable.rb | 4 ++-- lib/gitlab/ci/config/node/global.rb | 34 +++++++++++++++++---------- spec/lib/gitlab/ci/config/node/global_spec.rb | 4 ++-- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index e5780c60e70..7a43d494d3d 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -27,8 +27,8 @@ module Gitlab def create(key, factory) factory - .value(config[key]) - .with(key: key, parent: self, global: global) + .value(@config[key]) + .with(key: key, parent: self, global: @global) factory.create! end diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index 110d982588b..a2649e2c905 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -33,30 +33,38 @@ module Gitlab node :cache, Node::Cache, description: 'Configure caching between build jobs.' - node :jobs, Node::Jobs, - description: 'Definition of jobs for this pipeline.' - helpers :before_script, :image, :services, :after_script, :variables, :stages, :types, :cache, :jobs - def initialize(config, **attributes) - super(setup(config), attributes) + def initialize(*) + super @global = self end - def stages - stages_defined? ? stages_value : types_value + private + + def compose! + super + + compose_stages! + compose_jobs! end - private + def compose_stages! + factory = Node::Factory.new(Node::Jobs) + factory.value(@config.except(*nodes.keys)) + factory.with(key: :jobs, parent: self, global: self) + factory.with(description: 'Jobs definition for this pipeline') - def setup(config) - return config unless config.is_a?(Hash) + @entries[:jobs] = factory.create! + end - jobs = config.except(*nodes.keys) - global = config.slice(*nodes.keys) + def compose_jobs! + if types_defined? && !stages_defined? + @entries[:stages] = @entries[:types] + end - global.merge(jobs: jobs) + @entries.delete(:types) end end end diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 0c56b59db0c..10e5f05a2d5 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -35,7 +35,7 @@ describe Gitlab::Ci::Config::Node::Global do end it 'creates node object for each entry' do - expect(global.descendants.count).to eq 9 + expect(global.descendants.count).to eq 8 end it 'creates node object using valid class' do @@ -142,7 +142,7 @@ describe Gitlab::Ci::Config::Node::Global do describe '#nodes' do it 'instantizes all nodes' do - expect(global.descendants.count).to eq 9 + expect(global.descendants.count).to eq 8 end it 'contains undefined nodes' do -- cgit v1.2.1 From e9b42067fd4065e57a68e4b37732dda91444e3f7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 9 Jul 2016 17:06:53 +0200 Subject: Remove CI job stage code from legacy config processor --- lib/ci/gitlab_ci_yaml_processor.rb | 28 +++++++++------------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index f0710690985..1ffbd0020bb 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -57,6 +57,9 @@ module Ci private def initial_parsing + ## + # Global config + # @before_script = @ci_config.before_script @image = @ci_config.image @after_script = @ci_config.after_script @@ -65,24 +68,16 @@ module Ci @stages = @ci_config.stages @cache = @ci_config.cache - @jobs = {} - - @ci_config.jobs.each do |name, param| - add_job(name, param) - end + ## + # Jobs + # + @jobs = @ci_config.jobs @jobs.each do |name, job| validate_job!(name, job) end end - def add_job(name, job) - raise ValidationError, "Unknown parameter: #{name}" unless job.is_a?(Hash) && job.has_key?(:script) - - stage = job[:stage] || job[:type] || DEFAULT_STAGE - @jobs[name] = { stage: stage }.merge(job) - end - def build_job(name, job) { stage_idx: @stages.index(job[:stage]), @@ -112,12 +107,13 @@ module Ci end def validate_job!(name, job) + raise ValidationError, "Unknown parameter: #{name}" unless job.is_a?(Hash) && job.has_key?(:script) + validate_job_name!(name) validate_job_keys!(name, job) validate_job_types!(name, job) validate_job_script!(name, job) - validate_job_stage!(name, job) if job[:stage] validate_job_variables!(name, job) if job[:variables] validate_job_cache!(name, job) if job[:cache] validate_job_artifacts!(name, job) if job[:artifacts] @@ -186,12 +182,6 @@ module Ci end end - def validate_job_stage!(name, job) - unless job[:stage].is_a?(String) && job[:stage].in?(@stages) - raise ValidationError, "#{name} job: stage parameter should be #{@stages.join(", ")}" - end - end - def validate_job_variables!(name, job) unless validate_variables(job[:variables]) raise ValidationError, -- cgit v1.2.1 From a80a01e8411cee9e0f7d24ddddb65dca0c7a7fdf Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 9 Jul 2016 17:38:03 +0200 Subject: Add comment for deprecated CI config `types` entry --- lib/ci/gitlab_ci_yaml_processor.rb | 4 +--- lib/gitlab/ci/config/node/global.rb | 11 +++++++++-- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 1ffbd0020bb..40d1b475013 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -17,9 +17,7 @@ module Ci def initialize(config, path = nil) @ci_config = Gitlab::Ci::Config.new(config) - @config = @ci_config.to_hash - - @path = path + @config, @path = @ci_config.to_hash, path unless @ci_config.valid? raise ValidationError, @ci_config.errors.first diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index a2649e2c905..f59a967b1ef 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -50,7 +50,7 @@ module Gitlab compose_jobs! end - def compose_stages! + def compose_jobs! factory = Node::Factory.new(Node::Jobs) factory.value(@config.except(*nodes.keys)) factory.with(key: :jobs, parent: self, global: self) @@ -59,7 +59,14 @@ module Gitlab @entries[:jobs] = factory.create! end - def compose_jobs! + def compose_stages! + ## + # Deprecated `:types` key workaround - if types are defined and + # stages are not defined we use types definition as stages. + # + # Otherwise we use stages in favor of types, and remove types from + # processing. + # if types_defined? && !stages_defined? @entries[:stages] = @entries[:types] end -- cgit v1.2.1 From 2480701436bf84281e4afd65eb0d4c2d642754b9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 9 Jul 2016 18:43:26 +0200 Subject: Extend CI job entries fabrication and validation --- lib/gitlab/ci/config/node/global.rb | 1 + lib/gitlab/ci/config/node/hidden_job.rb | 1 + lib/gitlab/ci/config/node/job.rb | 4 +++ lib/gitlab/ci/config/node/jobs.rb | 18 ++++++++------ spec/lib/gitlab/ci/config/node/global_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/hidden_job_spec.rb | 10 ++++++++ spec/lib/gitlab/ci/config/node/job_spec.rb | 10 ++++++++ spec/lib/gitlab/ci/config/node/jobs_spec.rb | 30 +++++++++++------------ 8 files changed, 52 insertions(+), 24 deletions(-) diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index f59a967b1ef..f5500e27439 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -38,6 +38,7 @@ module Gitlab def initialize(*) super + @global = self end diff --git a/lib/gitlab/ci/config/node/hidden_job.rb b/lib/gitlab/ci/config/node/hidden_job.rb index 6a559ee8c04..073044b66f8 100644 --- a/lib/gitlab/ci/config/node/hidden_job.rb +++ b/lib/gitlab/ci/config/node/hidden_job.rb @@ -10,6 +10,7 @@ module Gitlab validations do validates :config, type: Hash + validates :config, presence: true end def relevant? diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 4a9cc28d763..28a3f407605 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -8,6 +8,10 @@ module Gitlab class Job < Entry include Configurable + validations do + validates :config, presence: true + end + node :stage, Stage, description: 'Pipeline stage this job will be executed into.' diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index f6acc25e4fb..7a164b69aff 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -30,17 +30,19 @@ module Gitlab private def create(name, config) - job_node(name).new(config, job_attributes(name)) + Node::Factory.new(job_node(name)) + .value(config || {}) + .with(key: name, parent: self, global: @global) + .with(description: "#{name} job definition.") + .create! end def job_node(name) - name.to_s.start_with?('.') ? Node::HiddenJob : Node::Job - end - - def job_attributes(name) - @attributes.merge(key: name, - parent: self, - description: "#{name} job definition.") + if name.to_s.start_with?('.') + Node::HiddenJob + else + Node::Job + end end end end diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 10e5f05a2d5..3e1c197fe61 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -137,7 +137,7 @@ describe Gitlab::Ci::Config::Node::Global do end context 'when most of entires not defined' do - let(:hash) { { cache: { key: 'a' }, rspec: {} } } + let(:hash) { { cache: { key: 'a' }, rspec: { script: %w[ls] } } } before { global.process! } describe '#nodes' do diff --git a/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb b/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb index ab865c3522e..cc44e2cc054 100644 --- a/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/hidden_job_spec.rb @@ -31,6 +31,16 @@ describe Gitlab::Ci::Config::Node::HiddenJob do end end end + + context 'when config is empty' do + let(:config) { {} } + + describe '#valid' do + it 'is invalid' do + expect(entry).not_to be_valid + end + end + end end end diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index 2a4296448fb..f841936ee6b 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -39,6 +39,16 @@ describe Gitlab::Ci::Config::Node::Job do end end end + + context 'when config is empty' do + let(:config) { {} } + + describe '#valid' do + it 'is invalid' do + expect(entry).not_to be_valid + end + end + end end end diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb index 52018958dcf..b0171174157 100644 --- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -4,6 +4,11 @@ describe Gitlab::Ci::Config::Node::Jobs do let(:entry) { described_class.new(config, global: spy) } describe 'validations' do + before do + entry.process! + entry.validate! + end + context 'when entry config value is correct' do let(:config) { { rspec: { script: 'rspec' } } } @@ -25,25 +30,20 @@ describe Gitlab::Ci::Config::Node::Jobs do end end - context 'when no visible jobs present' do - let(:config) { { '.hidden'.to_sym => {} } } + context 'when job is unspecified' do + let(:config) { { rspec: nil } } - context 'when not processed' do - it 'is valid' do - expect(entry.errors).to be_empty - end + it 'is not valid' do + expect(entry).not_to be_valid end + end - context 'when processed' do - before do - entry.process! - entry.validate! - end + context 'when no visible jobs present' do + let(:config) { { '.hidden'.to_sym => { script: [] } } } - it 'returns error about no visible jobs defined' do - expect(entry.errors) - .to include 'jobs config should contain at least one visible job' - end + it 'returns error about no visible jobs defined' do + expect(entry.errors) + .to include 'jobs config should contain at least one visible job' end end end -- cgit v1.2.1 From 3c5b1da2a1f15be9e032ec23f56de0af8002ec6b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sun, 10 Jul 2016 13:54:39 +0200 Subject: Add before_script node to CI job entry config --- lib/gitlab/ci/config/node/job.rb | 18 +++++++++-- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/job_spec.rb | 46 +++++++++++++++++++--------- 3 files changed, 49 insertions(+), 17 deletions(-) diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 28a3f407605..2f62248bb00 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -12,20 +12,34 @@ module Gitlab validates :config, presence: true end + node :before_script, Script, + description: 'Global before script overridden in this job.' + node :stage, Stage, description: 'Pipeline stage this job will be executed into.' node :type, Stage, description: 'Deprecated: stage this job will be executed into.' - helpers :stage, :type + helpers :before_script, :stage, :type def value - @config.merge(stage: stage_value) + raise InvalidError unless valid? + + ## + # TODO, refactoring step: do not expose internal configuration, + # return only hash value without merging it to internal config. + # + @config.merge(to_hash.compact) end private + def to_hash + { before_script: before_script, + stage: stage } + end + def compose! super diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 03477e1ca13..230106b74ae 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -970,7 +970,7 @@ EOT config = YAML.dump({ rspec: { script: "test", before_script: [10, "test"] } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: before_script should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:before_script config should be an array of strings") end it "returns errors if after_script parameter is invalid" do diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index f841936ee6b..032fbb9c27f 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -4,23 +4,15 @@ describe Gitlab::Ci::Config::Node::Job do let(:entry) { described_class.new(config, global: global) } let(:global) { spy('Global') } - describe 'validations' do - before do - entry.process! - entry.validate! - end + before do + entry.process! + entry.validate! + end + describe 'validations' do context 'when entry config value is correct' do let(:config) { { script: 'rspec' } } - describe '#value' do - it 'returns key value' do - expect(entry.value) - .to eq(script: 'rspec', - stage: 'test') - end - end - describe '#valid?' do it 'is valid' do expect(entry).to be_valid @@ -33,7 +25,7 @@ describe Gitlab::Ci::Config::Node::Job do let(:config) { ['incorrect'] } describe '#errors' do - it 'saves errors' do + it 'reports error about a config type' do expect(entry.errors) .to include 'job config should be a hash' end @@ -52,6 +44,32 @@ describe Gitlab::Ci::Config::Node::Job do end end + describe '#value' do + context 'when entry is correct' do + let(:config) do + { before_script: %w[ls pwd], + script: 'rspec' } + end + + it 'returns correct value' do + expect(entry.value) + .to eq(before_script: %w[ls pwd], + script: 'rspec', + stage: 'test') + end + end + + context 'when entry is incorrect' do + let(:config) { {} } + + it 'raises error' do + expect { entry.value }.to raise_error( + Gitlab::Ci::Config::Node::Entry::InvalidError + ) + end + end + end + describe '#relevant?' do it 'is a relevant entry' do expect(entry).to be_relevant -- cgit v1.2.1 From 489e9be4e83621ae6f3db311398bf32e1065d1a8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sun, 10 Jul 2016 14:35:53 +0200 Subject: Add CI job script node in new config processor --- lib/gitlab/ci/config/node/job.rb | 10 +++-- lib/gitlab/ci/config/node/job_script.rb | 31 ++++++++++++++ spec/lib/gitlab/ci/config/node/global_spec.rb | 4 +- spec/lib/gitlab/ci/config/node/job_script_spec.rb | 49 +++++++++++++++++++++++ spec/lib/gitlab/ci/config/node/job_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/jobs_spec.rb | 4 +- 6 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 lib/gitlab/ci/config/node/job_script.rb create mode 100644 spec/lib/gitlab/ci/config/node/job_script_spec.rb diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 2f62248bb00..fc6fe8015d2 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -15,13 +15,16 @@ module Gitlab node :before_script, Script, description: 'Global before script overridden in this job.' + node :script, JobScript, + description: 'Commands that will be executed in this job.' + node :stage, Stage, description: 'Pipeline stage this job will be executed into.' node :type, Stage, description: 'Deprecated: stage this job will be executed into.' - helpers :before_script, :stage, :type + helpers :before_script, :script, :stage, :type def value raise InvalidError unless valid? @@ -36,8 +39,9 @@ module Gitlab private def to_hash - { before_script: before_script, - stage: stage } + { before_script: before_script_value, + script: script_value, + stage: stage_value } end def compose! diff --git a/lib/gitlab/ci/config/node/job_script.rb b/lib/gitlab/ci/config/node/job_script.rb new file mode 100644 index 00000000000..c3a05dffdd3 --- /dev/null +++ b/lib/gitlab/ci/config/node/job_script.rb @@ -0,0 +1,31 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a job script. + # + class JobScript < Entry + include Validatable + + validations do + include LegacyValidationHelpers + + validate :string_or_array_of_strings + + def string_or_array_of_strings + unless validate_string(config) || validate_array_of_strings(config) + errors.add(:config, + 'should be a string or an array of strings') + end + end + end + + def value + [@config].flatten + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 3e1c197fe61..786fc5bb6ce 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -129,8 +129,8 @@ describe Gitlab::Ci::Config::Node::Global do describe '#jobs' do it 'returns jobs configuration' do expect(global.jobs) - .to eq(rspec: { script: 'rspec', stage: 'test' }, - spinach: { script: 'spinach', stage: 'test' }) + .to eq(rspec: { script: %w[rspec], stage: 'test' }, + spinach: { script: %w[spinach], stage: 'test' }) end end end diff --git a/spec/lib/gitlab/ci/config/node/job_script_spec.rb b/spec/lib/gitlab/ci/config/node/job_script_spec.rb new file mode 100644 index 00000000000..6e2ecb6e0ad --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/job_script_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::JobScript do + let(:entry) { described_class.new(config) } + + context 'when entry config value is an array' do + let(:config) { ['ls', 'pwd'] } + + describe '#value' do + it 'returns array of strings' do + expect(entry.value).to eq config + end + end + + describe '#errors' do + it 'does not append errors' do + expect(entry.errors).to be_empty + end + end + end + + context 'when entry config value is a string' do + let(:config) { 'ls' } + + describe '#value' do + it 'returns array with single element' do + expect(entry.value).to eq ['ls'] + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not valid' do + let(:config) { 1 } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'job script config should be a ' \ + 'string or an array of strings' + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index 032fbb9c27f..3bcac73809d 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -54,7 +54,7 @@ describe Gitlab::Ci::Config::Node::Job do it 'returns correct value' do expect(entry.value) .to eq(before_script: %w[ls pwd], - script: 'rspec', + script: %w[rspec], stage: 'test') end end diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb index b0171174157..255646a001a 100644 --- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -62,8 +62,8 @@ describe Gitlab::Ci::Config::Node::Jobs do describe '#value' do it 'returns key value' do expect(entry.value) - .to eq(rspec: { script: 'rspec', stage: 'test' }, - spinach: { script: 'spinach', stage: 'test' }) + .to eq(rspec: { script: %w[rspec], stage: 'test' }, + spinach: { script: %w[spinach], stage: 'test' }) end end -- cgit v1.2.1 From 500b61e14f384eec545c207fa9324906daf2e148 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sun, 10 Jul 2016 14:41:14 +0200 Subject: Move after script CI job confg to new processor --- lib/gitlab/ci/config/node/job.rb | 8 ++++++-- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/job_spec.rb | 6 ++++-- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index fc6fe8015d2..050e00a5151 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -24,7 +24,10 @@ module Gitlab node :type, Stage, description: 'Deprecated: stage this job will be executed into.' - helpers :before_script, :script, :stage, :type + node :after_script, Script, + description: 'Commands that will be executed when finishing job.' + + helpers :before_script, :script, :stage, :type, :after_script def value raise InvalidError unless valid? @@ -41,7 +44,8 @@ module Gitlab def to_hash { before_script: before_script_value, script: script_value, - stage: stage_value } + stage: stage_value, + after_script: after_script_value } end def compose! diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 230106b74ae..a80b0ce5a57 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -984,7 +984,7 @@ EOT config = YAML.dump({ rspec: { script: "test", after_script: [10, "test"] } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: after_script should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:after_script config should be an array of strings") end it "returns errors if image parameter is invalid" do diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index 3bcac73809d..77efc73632d 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -48,14 +48,16 @@ describe Gitlab::Ci::Config::Node::Job do context 'when entry is correct' do let(:config) do { before_script: %w[ls pwd], - script: 'rspec' } + script: 'rspec', + after_script: %w[cleanup] } end it 'returns correct value' do expect(entry.value) .to eq(before_script: %w[ls pwd], script: %w[rspec], - stage: 'test') + stage: 'test', + after_script: %w[cleanup]) end end -- cgit v1.2.1 From 6aefb9e99983494b129a011ee0fce57c1398f612 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sun, 10 Jul 2016 14:43:01 +0200 Subject: Remove CI job script validation from legacy config --- lib/ci/gitlab_ci_yaml_processor.rb | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 40d1b475013..ed8dd0f9e47 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -110,7 +110,6 @@ module Ci validate_job_name!(name) validate_job_keys!(name, job) validate_job_types!(name, job) - validate_job_script!(name, job) validate_job_variables!(name, job) if job[:variables] validate_job_cache!(name, job) if job[:cache] @@ -166,20 +165,6 @@ module Ci end end - def validate_job_script!(name, job) - if !validate_string(job[:script]) && !validate_array_of_strings(job[:script]) - raise ValidationError, "#{name} job: script should be a string or an array of a strings" - end - - if job[:before_script] && !validate_array_of_strings(job[:before_script]) - raise ValidationError, "#{name} job: before_script should be an array of strings" - end - - if job[:after_script] && !validate_array_of_strings(job[:after_script]) - raise ValidationError, "#{name} job: after_script should be an array of strings" - end - end - def validate_job_variables!(name, job) unless validate_variables(job[:variables]) raise ValidationError, -- cgit v1.2.1 From 8f7c98ee2a34cb063428bea81f1420579549a1a5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sun, 10 Jul 2016 20:26:37 +0200 Subject: Rename CI config job script entry node to commands --- lib/gitlab/ci/config/node/commands.rb | 35 ++++++++++++++++ lib/gitlab/ci/config/node/job.rb | 2 +- lib/gitlab/ci/config/node/job_script.rb | 31 -------------- spec/lib/gitlab/ci/config/node/commands_spec.rb | 49 +++++++++++++++++++++++ spec/lib/gitlab/ci/config/node/job_script_spec.rb | 49 ----------------------- 5 files changed, 85 insertions(+), 81 deletions(-) create mode 100644 lib/gitlab/ci/config/node/commands.rb delete mode 100644 lib/gitlab/ci/config/node/job_script.rb create mode 100644 spec/lib/gitlab/ci/config/node/commands_spec.rb delete mode 100644 spec/lib/gitlab/ci/config/node/job_script_spec.rb diff --git a/lib/gitlab/ci/config/node/commands.rb b/lib/gitlab/ci/config/node/commands.rb new file mode 100644 index 00000000000..f7e6950001e --- /dev/null +++ b/lib/gitlab/ci/config/node/commands.rb @@ -0,0 +1,35 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a job script. + # + class Commands < Entry + include Validatable + + validations do + include LegacyValidationHelpers + + validate :string_or_array_of_strings + + def string_or_array_of_strings + unless config_valid? + errors.add(:config, + 'should be a string or an array of strings') + end + end + + def config_valid? + validate_string(config) || validate_array_of_strings(config) + end + end + + def value + [@config].flatten + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 050e00a5151..9a019216ca3 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -15,7 +15,7 @@ module Gitlab node :before_script, Script, description: 'Global before script overridden in this job.' - node :script, JobScript, + node :script, Commands, description: 'Commands that will be executed in this job.' node :stage, Stage, diff --git a/lib/gitlab/ci/config/node/job_script.rb b/lib/gitlab/ci/config/node/job_script.rb deleted file mode 100644 index c3a05dffdd3..00000000000 --- a/lib/gitlab/ci/config/node/job_script.rb +++ /dev/null @@ -1,31 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a job script. - # - class JobScript < Entry - include Validatable - - validations do - include LegacyValidationHelpers - - validate :string_or_array_of_strings - - def string_or_array_of_strings - unless validate_string(config) || validate_array_of_strings(config) - errors.add(:config, - 'should be a string or an array of strings') - end - end - end - - def value - [@config].flatten - end - end - end - end - end -end diff --git a/spec/lib/gitlab/ci/config/node/commands_spec.rb b/spec/lib/gitlab/ci/config/node/commands_spec.rb new file mode 100644 index 00000000000..e373c40706f --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/commands_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Commands do + let(:entry) { described_class.new(config) } + + context 'when entry config value is an array' do + let(:config) { ['ls', 'pwd'] } + + describe '#value' do + it 'returns array of strings' do + expect(entry.value).to eq config + end + end + + describe '#errors' do + it 'does not append errors' do + expect(entry.errors).to be_empty + end + end + end + + context 'when entry config value is a string' do + let(:config) { 'ls' } + + describe '#value' do + it 'returns array with single element' do + expect(entry.value).to eq ['ls'] + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not valid' do + let(:config) { 1 } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'commands config should be a ' \ + 'string or an array of strings' + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/job_script_spec.rb b/spec/lib/gitlab/ci/config/node/job_script_spec.rb deleted file mode 100644 index 6e2ecb6e0ad..00000000000 --- a/spec/lib/gitlab/ci/config/node/job_script_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Ci::Config::Node::JobScript do - let(:entry) { described_class.new(config) } - - context 'when entry config value is an array' do - let(:config) { ['ls', 'pwd'] } - - describe '#value' do - it 'returns array of strings' do - expect(entry.value).to eq config - end - end - - describe '#errors' do - it 'does not append errors' do - expect(entry.errors).to be_empty - end - end - end - - context 'when entry config value is a string' do - let(:config) { 'ls' } - - describe '#value' do - it 'returns array with single element' do - expect(entry.value).to eq ['ls'] - end - end - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - - context 'when entry value is not valid' do - let(:config) { 1 } - - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include 'job script config should be a ' \ - 'string or an array of strings' - end - end - end -end -- cgit v1.2.1 From 80587064eb798f5bf2b18dbb8e65e5a55d1db085 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sun, 10 Jul 2016 20:59:18 +0200 Subject: Require parent when using node factory in CI config --- lib/gitlab/ci/config/node/configurable.rb | 9 +++--- lib/gitlab/ci/config/node/factory.rb | 13 ++++++-- lib/gitlab/ci/config/node/global.rb | 7 +++-- lib/gitlab/ci/config/node/jobs.rb | 8 ++--- spec/lib/gitlab/ci/config/node/factory_spec.rb | 43 +++++++++++++++++++++----- 5 files changed, 60 insertions(+), 20 deletions(-) diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 7a43d494d3d..36d7d20c110 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -28,7 +28,8 @@ module Gitlab def create(key, factory) factory .value(@config[key]) - .with(key: key, parent: self, global: @global) + .parent(self) + .with(key: key) factory.create! end @@ -40,11 +41,11 @@ module Gitlab private - def node(symbol, entry_class, metadata) - factory = Node::Factory.new(entry_class) + def node(key, node, metadata) + factory = Node::Factory.new(node) .with(description: metadata[:description]) - (@nodes ||= {}).merge!(symbol.to_sym => factory) + (@nodes ||= {}).merge!(key.to_sym => factory) end def helpers(*nodes) diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb index 3f2cdf436e3..3488aec4a22 100644 --- a/lib/gitlab/ci/config/node/factory.rb +++ b/lib/gitlab/ci/config/node/factory.rb @@ -18,6 +18,11 @@ module Gitlab self end + def parent(parent) + @parent = parent + self + end + def with(attributes) @attributes.merge!(attributes) self @@ -25,15 +30,19 @@ module Gitlab def create! raise InvalidFactory unless defined?(@value) + raise InvalidFactory unless defined?(@parent) + + attributes = { parent: @parent, global: @parent.global } + attributes.merge!(@attributes) ## # We assume that unspecified entry is undefined. # See issue #18775. # if @value.nil? - Node::Undefined.new(@node, @attributes) + Node::Undefined.new(@node, attributes) else - @node.new(@value, @attributes) + @node.new(@value, attributes) end end end diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index f5500e27439..4a958735599 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -53,9 +53,10 @@ module Gitlab def compose_jobs! factory = Node::Factory.new(Node::Jobs) - factory.value(@config.except(*nodes.keys)) - factory.with(key: :jobs, parent: self, global: self) - factory.with(description: 'Jobs definition for this pipeline') + .value(@config.except(*nodes.keys)) + .parent(self) + .with(key: :jobs, global: self) + .with(description: 'Jobs definition for this pipeline') @entries[:jobs] = factory.create! end diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index 7a164b69aff..548441df37c 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -30,14 +30,14 @@ module Gitlab private def create(name, config) - Node::Factory.new(job_node(name)) + Node::Factory.new(node(name)) .value(config || {}) - .with(key: name, parent: self, global: @global) - .with(description: "#{name} job definition.") + .parent(self) + .with(key: name, description: "#{name} job definition.") .create! end - def job_node(name) + def node(name) if name.to_s.start_with?('.') Node::HiddenJob else diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb index c912b1b2044..bc6bf32ffbf 100644 --- a/spec/lib/gitlab/ci/config/node/factory_spec.rb +++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb @@ -2,22 +2,40 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Factory do describe '#create!' do - let(:factory) { described_class.new(entry_class) } - let(:entry_class) { Gitlab::Ci::Config::Node::Script } + let(:factory) { described_class.new(node) } + let(:node) { Gitlab::Ci::Config::Node::Script } + let(:parent) { double('parent') } + let(:global) { double('global') } - context 'when setting up a value' do + before do + allow(parent).to receive(:global).and_return(global) + end + + context 'when setting a concrete value' do it 'creates entry with valid value' do entry = factory .value(['ls', 'pwd']) + .parent(parent) .create! expect(entry.value).to eq ['ls', 'pwd'] end + it 'sets parent and global attributes' do + entry = factory + .value('ls') + .parent(parent) + .create! + + expect(entry.global).to eq global + expect(entry.parent).to eq parent + end + context 'when setting description' do it 'creates entry with description' do entry = factory .value(['ls', 'pwd']) + .parent(parent) .with(description: 'test description') .create! @@ -30,6 +48,7 @@ describe Gitlab::Ci::Config::Node::Factory do it 'creates entry with custom key' do entry = factory .value(['ls', 'pwd']) + .parent(parent) .with(key: 'test key') .create! @@ -38,20 +57,21 @@ describe Gitlab::Ci::Config::Node::Factory do end context 'when setting a parent' do - let(:parent) { Object.new } + let(:object) { Object.new } it 'creates entry with valid parent' do entry = factory .value('ls') - .with(parent: parent) + .parent(parent) + .with(parent: object) .create! - expect(entry.parent).to eq parent + expect(entry.parent).to eq object end end end - context 'when not setting up a value' do + context 'when not setting a value' do it 'raises error' do expect { factory.create! }.to raise_error( Gitlab::Ci::Config::Node::Factory::InvalidFactory @@ -59,10 +79,19 @@ describe Gitlab::Ci::Config::Node::Factory do end end + context 'when not setting parent object' do + it 'raises error' do + expect { factory.value('ls').create! }.to raise_error( + Gitlab::Ci::Config::Node::Factory::InvalidFactory + ) + end + end + context 'when creating entry with nil value' do it 'creates an undefined entry' do entry = factory .value(nil) + .parent(parent) .create! expect(entry).to be_an_instance_of Gitlab::Ci::Config::Node::Undefined -- cgit v1.2.1 From 8c9c3eda7a305c43d7cf3d50b868292d0b612b5b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 12 Jul 2016 12:56:21 +0200 Subject: Prevalidate CI entries recursively on processed --- lib/gitlab/ci/config/node/entry.rb | 5 ++--- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 1940c39087b..02e4f7693da 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -31,10 +31,9 @@ module Gitlab end def validate! - if @validator.valid?(:new) - @validator.validate(:processed) - end + return unless valid? + @validator.validate(:processed) @entries.each_value(&:validate!) end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index a80b0ce5a57..1017f79cc6e 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1065,7 +1065,7 @@ EOT end it "returns errors if there are no visible jobs defined" do - config = YAML.dump({ before_script: ["bundle update"], '.hidden'.to_sym => {} }) + config = YAML.dump({ before_script: ["bundle update"], '.hidden'.to_sym => { script: 'ls' } }) expect do GitlabCiYamlProcessor.new(config, path) end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs config should contain at least one visible job") -- cgit v1.2.1 From d41d3301474ffd7022e41daad4ddf67590ac9f95 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 12 Jul 2016 13:03:19 +0200 Subject: Add CI config node that is unspecified null entry --- lib/gitlab/ci/config/node/null.rb | 30 +++++++++++++++++++++++++++++ spec/lib/gitlab/ci/config/node/null_spec.rb | 29 ++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+) create mode 100644 lib/gitlab/ci/config/node/null.rb create mode 100644 spec/lib/gitlab/ci/config/node/null_spec.rb diff --git a/lib/gitlab/ci/config/node/null.rb b/lib/gitlab/ci/config/node/null.rb new file mode 100644 index 00000000000..c7bbc16939a --- /dev/null +++ b/lib/gitlab/ci/config/node/null.rb @@ -0,0 +1,30 @@ +module Gitlab + module Ci + class Config + module Node + ## + # This class represents an undefined and unspecified node. + # + # Implements the Null Object pattern. + # + class Null < Entry + def initialize(config = nil, **attributes) + super + end + + def value + nil + end + + def valid? + true + end + + def errors + [] + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/null_spec.rb b/spec/lib/gitlab/ci/config/node/null_spec.rb new file mode 100644 index 00000000000..63b23ed0bd1 --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/null_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Null do + let(:null) { described_class.new } + + describe '#leaf?' do + it 'is leaf node' do + expect(null).to be_leaf + end + end + + describe '#valid?' do + it 'is always valid' do + expect(null).to be_valid + end + end + + describe '#errors' do + it 'is does not contain errors' do + expect(null.errors).to be_empty + end + end + + describe '#value' do + it 'returns nil' do + expect(null.value).to eq nil + end + end +end -- cgit v1.2.1 From 06641a3fee4ebdaada3007a51866a7fb927d21de Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 12 Jul 2016 14:10:18 +0200 Subject: Simplify undefined node definition in CI config --- lib/gitlab/ci/config/node/configurable.rb | 2 +- lib/gitlab/ci/config/node/entry.rb | 4 +- lib/gitlab/ci/config/node/factory.rb | 12 ++++- lib/gitlab/ci/config/node/null.rb | 12 +++-- lib/gitlab/ci/config/node/undefined.rb | 59 +++------------------ spec/lib/gitlab/ci/config/node/global_spec.rb | 4 +- spec/lib/gitlab/ci/config/node/null_spec.rb | 14 ++++- spec/lib/gitlab/ci/config/node/undefined_spec.rb | 67 ++++++------------------ 8 files changed, 58 insertions(+), 116 deletions(-) diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 36d7d20c110..29de2d7d0b5 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -51,7 +51,7 @@ module Gitlab def helpers(*nodes) nodes.each do |symbol| define_method("#{symbol}_defined?") do - @entries[symbol].try(:defined?) + @entries[symbol].specified? end define_method("#{symbol}_value") do diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 02e4f7693da..c95e85200d2 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -66,14 +66,14 @@ module Gitlab @config else meaningful = @entries.select do |_key, value| - value.defined? && value.relevant? + value.specified? && value.relevant? end Hash[meaningful.map { |key, entry| [key, entry.value] }] end end - def defined? + def specified? true end diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb index 3488aec4a22..602660acb93 100644 --- a/lib/gitlab/ci/config/node/factory.rb +++ b/lib/gitlab/ci/config/node/factory.rb @@ -40,11 +40,21 @@ module Gitlab # See issue #18775. # if @value.nil? - Node::Undefined.new(@node, attributes) + Node::Undefined.new(fabricate_undefined(attributes)) else @node.new(@value, attributes) end end + + private + + def fabricate_undefined(attributes) + if @node.default.nil? + Node::Null.new(nil, attributes) + else + @node.new(@node.default, attributes) + end + end end end end diff --git a/lib/gitlab/ci/config/node/null.rb b/lib/gitlab/ci/config/node/null.rb index c7bbc16939a..880d29f663d 100644 --- a/lib/gitlab/ci/config/node/null.rb +++ b/lib/gitlab/ci/config/node/null.rb @@ -8,10 +8,6 @@ module Gitlab # Implements the Null Object pattern. # class Null < Entry - def initialize(config = nil, **attributes) - super - end - def value nil end @@ -23,6 +19,14 @@ module Gitlab def errors [] end + + def specified? + false + end + + def relevant? + false + end end end end diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb index fedb9d020be..384774c9b69 100644 --- a/lib/gitlab/ci/config/node/undefined.rb +++ b/lib/gitlab/ci/config/node/undefined.rb @@ -3,66 +3,19 @@ module Gitlab class Config module Node ## - # This class represents an undefined entry node. + # This class represents an undefined and unspecified entry node. # - # It takes original entry class as configuration and creates an object - # if original entry has a default value. If there is default value - # some methods are delegated to it. + # It decorates original entry adding method that idicates it is + # unspecified. # - # - class Undefined < Entry - include Validatable - - delegate :valid?, :errors, :value, to: :@strategy - - validations do - validates :config, type: Class - end - - def initialize(node, **attributes) + class Undefined < SimpleDelegator + def initialize(entry) super - @strategy = create_strategy(node, node.default) end - def defined? + def specified? false end - - private - - def create_strategy(node, default) - if default.nil? - Undefined::NullStrategy.new - else - entry = node.new(default, attributes) - Undefined::DefaultStrategy.new(entry) - end - end - - class DefaultStrategy - delegate :valid?, :errors, :value, to: :@default - - def initialize(entry) - @default = entry - end - end - - class NullStrategy - def initialize(*) - end - - def value - nil - end - - def valid? - true - end - - def errors - [] - end - end end end end diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 786fc5bb6ce..1945b0326cc 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -233,9 +233,9 @@ describe Gitlab::Ci::Config::Node::Global do end end - describe '#defined?' do + describe '#specified?' do it 'is concrete entry that is defined' do - expect(global.defined?).to be true + expect(global.specified?).to be true end end end diff --git a/spec/lib/gitlab/ci/config/node/null_spec.rb b/spec/lib/gitlab/ci/config/node/null_spec.rb index 63b23ed0bd1..1ab5478dcfa 100644 --- a/spec/lib/gitlab/ci/config/node/null_spec.rb +++ b/spec/lib/gitlab/ci/config/node/null_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Null do - let(:null) { described_class.new } + let(:null) { described_class.new(nil) } describe '#leaf?' do it 'is leaf node' do @@ -26,4 +26,16 @@ describe Gitlab::Ci::Config::Node::Null do expect(null.value).to eq nil end end + + describe '#relevant?' do + it 'is not relevant' do + expect(null.relevant?).to eq false + end + end + + describe '#specified?' do + it 'is not defined' do + expect(null.specified?).to eq false + end + end end diff --git a/spec/lib/gitlab/ci/config/node/undefined_spec.rb b/spec/lib/gitlab/ci/config/node/undefined_spec.rb index 417b4a0ad6f..2d43e1c1a9d 100644 --- a/spec/lib/gitlab/ci/config/node/undefined_spec.rb +++ b/spec/lib/gitlab/ci/config/node/undefined_spec.rb @@ -4,66 +4,29 @@ describe Gitlab::Ci::Config::Node::Undefined do let(:undefined) { described_class.new(entry) } let(:entry) { spy('Entry') } - context 'when entry does not have a default value' do - before { allow(entry).to receive(:default).and_return(nil) } - - describe '#leaf?' do - it 'is leaf node' do - expect(undefined).to be_leaf - end - end - - describe '#valid?' do - it 'is always valid' do - expect(undefined).to be_valid - end - end - - describe '#errors' do - it 'is does not contain errors' do - expect(undefined.errors).to be_empty - end - end - - describe '#value' do - it 'returns nil' do - expect(undefined.value).to eq nil - end + describe '#valid?' do + it 'delegates method to entry' do + expect(undefined.valid).to eq entry end end - context 'when entry has a default value' do - before do - allow(entry).to receive(:default).and_return('some value') - allow(entry).to receive(:value).and_return('some value') + describe '#errors' do + it 'delegates method to entry' do + expect(undefined.errors).to eq entry end + end - describe '#value' do - it 'returns default value for entry' do - expect(undefined.value).to eq 'some value' - end - end - - describe '#errors' do - it 'delegates errors to default entry' do - expect(entry).to receive(:errors) - - undefined.errors - end - end - - describe '#valid?' do - it 'delegates valid? to default entry' do - expect(entry).to receive(:valid?) - - undefined.valid? - end + describe '#value' do + it 'delegates method to entry' do + expect(undefined.value).to eq entry end end - describe '#undefined?' do - it 'is not a defined entry' do - expect(undefined.defined?).to be false + describe '#specified?' do + it 'is always false' do + allow(entry).to receive(:specified?).and_return(true) + + expect(undefined.specified?).to be false end end end -- cgit v1.2.1 From 61f7bede79a006c7b44e88a3385d175c5ad2a863 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 12 Jul 2016 14:40:51 +0200 Subject: Fix using `try` on delegators in CI config entries See: https://github.com/rails/rails/commit/af53280a4b5b3323ac87dc60deb2b1b781197b2b --- lib/gitlab/ci/config/node/configurable.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 29de2d7d0b5..da2ef4d5503 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -51,12 +51,12 @@ module Gitlab def helpers(*nodes) nodes.each do |symbol| define_method("#{symbol}_defined?") do - @entries[symbol].specified? + @entries[symbol].specified? if @entries[symbol] end define_method("#{symbol}_value") do raise Entry::InvalidError unless valid? - @entries[symbol].try(:value) + @entries[symbol].value if @entries[symbol] end alias_method symbol.to_sym, "#{symbol}_value".to_sym -- cgit v1.2.1 From b228787f5afa34b153e6b52d6b0d88248cc3e099 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 12 Jul 2016 14:58:48 +0200 Subject: Do not raise when getting value of invalid CI node --- lib/gitlab/ci/config/node/configurable.rb | 2 +- spec/lib/gitlab/ci/config/node/global_spec.rb | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index da2ef4d5503..8bd752b0e2a 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -55,7 +55,7 @@ module Gitlab end define_method("#{symbol}_value") do - raise Entry::InvalidError unless valid? + return unless valid? @entries[symbol].value if @entries[symbol] end diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 1945b0326cc..3ffbe9c2e97 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -209,10 +209,8 @@ describe Gitlab::Ci::Config::Node::Global do end describe '#before_script' do - it 'raises error' do - expect { global.before_script }.to raise_error( - Gitlab::Ci::Config::Node::Entry::InvalidError - ) + it 'returns nil' do + expect(global.before_script).to be_nil end end end -- cgit v1.2.1 From de4c9a273867864a9033dab0624e0cfd72201384 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 13 Jul 2016 12:22:33 +0200 Subject: Improve CI stage configuration entry validations --- lib/gitlab/ci/config/node/stage.rb | 16 +++++++++------- lib/gitlab/ci/config/node/validators.rb | 2 +- spec/lib/gitlab/ci/config/node/stage_spec.rb | 4 ++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/gitlab/ci/config/node/stage.rb b/lib/gitlab/ci/config/node/stage.rb index e8fae65a2a9..909358ea170 100644 --- a/lib/gitlab/ci/config/node/stage.rb +++ b/lib/gitlab/ci/config/node/stage.rb @@ -10,14 +10,16 @@ module Gitlab validations do validates :config, type: String - validates :global, required_attribute: true - validate :known_stage, on: :processed - def known_stage - unless known? - stages_list = global.stages.join(', ') - errors.add(:config, - "should be one of defined stages (#{stages_list})") + with_options on: :processed do + validates :global, required: true + + validate do + unless known? + errors.add(:config, + 'should be one of defined stages ' \ + "(#{global.stages.join(', ')})") + end end end end diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb index 6f0e14e2f0a..d33b407af68 100644 --- a/lib/gitlab/ci/config/node/validators.rb +++ b/lib/gitlab/ci/config/node/validators.rb @@ -33,7 +33,7 @@ module Gitlab end end - class RequiredAttributeValidator < ActiveModel::EachValidator + class RequiredValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) if value.nil? raise Entry::InvalidError, diff --git a/spec/lib/gitlab/ci/config/node/stage_spec.rb b/spec/lib/gitlab/ci/config/node/stage_spec.rb index 6deeca1a6c0..004012f8b38 100644 --- a/spec/lib/gitlab/ci/config/node/stage_spec.rb +++ b/spec/lib/gitlab/ci/config/node/stage_spec.rb @@ -28,10 +28,10 @@ describe Gitlab::Ci::Config::Node::Stage do context 'when stage config is incorrect' do describe '#errors' do context 'when reference to global node is not set' do - let(:stage) { described_class.new(config) } + let(:stage) { described_class.new('test') } it 'raises error' do - expect { stage }.to raise_error( + expect { stage.validate! }.to raise_error( Gitlab::Ci::Config::Node::Entry::InvalidError, /Entry needs global attribute set internally./ ) -- cgit v1.2.1 From 097550f08a2a92dd1efff5da97cff0228afde57b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 13 Jul 2016 13:42:57 +0200 Subject: Fabricate CI entry with value, set attributes later --- lib/gitlab/ci/config/node/factory.rb | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb index 602660acb93..339548e0feb 100644 --- a/lib/gitlab/ci/config/node/factory.rb +++ b/lib/gitlab/ci/config/node/factory.rb @@ -32,27 +32,39 @@ module Gitlab raise InvalidFactory unless defined?(@value) raise InvalidFactory unless defined?(@parent) - attributes = { parent: @parent, global: @parent.global } - attributes.merge!(@attributes) - ## # We assume that unspecified entry is undefined. # See issue #18775. # if @value.nil? - Node::Undefined.new(fabricate_undefined(attributes)) + Node::Undefined.new( + fabricate_undefined + ) else - @node.new(@value, attributes) + fabricate(@node, @value) end end private - def fabricate_undefined(attributes) + def fabricate_undefined + ## + # If node has a default value we fabricate concrete node + # with default value. + # if @node.default.nil? - Node::Null.new(nil, attributes) + fabricate(Node::Null) else - @node.new(@node.default, attributes) + fabricate(@node, @node.default) + end + end + + def fabricate(node, value = nil) + node.new(value).tap do |entry| + entry.key = @attributes[:key] + entry.parent = @attributes[:parent] || @parent + entry.global = @attributes[:global] || @parent.global + entry.description = @attributes[:description] end end end -- cgit v1.2.1 From 6920390aad683dcc73109be5a23b647c918f9309 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 13 Jul 2016 14:38:10 +0200 Subject: Add before script and commands to CI job entry --- lib/gitlab/ci/config/node/job.rb | 15 +++- spec/lib/gitlab/ci/config/node/job_spec.rb | 117 +++++++++++++++++++++++++++-- 2 files changed, 124 insertions(+), 8 deletions(-) diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 9a019216ca3..5ee91ebcf0b 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -10,6 +10,7 @@ module Gitlab validations do validates :config, presence: true + validates :global, required: true, on: :processed end node :before_script, Script, @@ -30,8 +31,6 @@ module Gitlab helpers :before_script, :script, :stage, :type, :after_script def value - raise InvalidError unless valid? - ## # TODO, refactoring step: do not expose internal configuration, # return only hash value without merging it to internal config. @@ -39,6 +38,18 @@ module Gitlab @config.merge(to_hash.compact) end + def before_script + if before_script_defined? + before_script_value.to_a + else + @global.before_script.to_a + end + end + + def commands + (before_script + script).join("\n") + end + private def to_hash diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index 77efc73632d..635362611a0 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -60,14 +60,62 @@ describe Gitlab::Ci::Config::Node::Job do after_script: %w[cleanup]) end end + end + + describe '#before_script' do + context 'when global entry has before script' do + before do + allow(global).to receive(:before_script) + .and_return(%w[ls pwd]) + end - context 'when entry is incorrect' do - let(:config) { {} } + context 'when before script is overridden' do + let(:config) do + { before_script: %w[whoami], + script: 'rspec' } + end - it 'raises error' do - expect { entry.value }.to raise_error( - Gitlab::Ci::Config::Node::Entry::InvalidError - ) + it 'returns correct script' do + expect(entry.before_script).to eq %w[whoami] + end + end + + context 'when before script is not overriden' do + let(:config) do + { script: %w[spinach] } + end + + it 'returns correct script' do + expect(entry.before_script).to eq %w[ls pwd] + end + end + end + + context 'when global entry does not have before script' do + before do + allow(global).to receive(:before_script) + .and_return(nil) + end + + context 'when job has before script' do + let(:config) do + { before_script: %w[whoami], + script: 'rspec' } + end + + it 'returns correct script' do + expect(entry.before_script).to eq %w[whoami] + end + end + + context 'when job does not have before script' do + let(:config) do + { script: %w[ls test] } + end + + it 'returns correct script' do + expect(entry.before_script).to eq [] + end end end end @@ -77,4 +125,61 @@ describe Gitlab::Ci::Config::Node::Job do expect(entry).to be_relevant end end + + describe '#commands' do + context 'when global entry has before script' do + before do + allow(global).to receive(:before_script) + .and_return(%w[ls pwd]) + end + + context 'when before script is overridden' do + let(:config) do + { before_script: %w[whoami], + script: 'rspec' } + end + + it 'returns correct commands' do + expect(entry.commands).to eq "whoami\nrspec" + end + end + + context 'when before script is not overriden' do + let(:config) do + { script: %w[rspec spinach] } + end + + it 'returns correct commands' do + expect(entry.commands).to eq "ls\npwd\nrspec\nspinach" + end + end + end + + context 'when global entry does not have before script' do + before do + allow(global).to receive(:before_script) + .and_return(nil) + end + context 'when job has before script' do + let(:config) do + { before_script: %w[whoami], + script: 'rspec' } + end + + it 'returns correct commands' do + expect(entry.commands).to eq "whoami\nrspec" + end + end + + context 'when job does not have before script' do + let(:config) do + { script: %w[ls test] } + end + + it 'returns correct commands' do + expect(entry.commands).to eq "ls\ntest" + end + end + end + end end -- cgit v1.2.1 From 036e297ca3c39f90aebc76d5acb2e01f32364d0d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 13 Jul 2016 15:04:12 +0200 Subject: Expose CI job commands and use in legacy processor --- lib/ci/gitlab_ci_yaml_processor.rb | 13 ++++++------- lib/gitlab/ci/config/node/job.rb | 15 ++++++++------- spec/lib/gitlab/ci/config/node/global_spec.rb | 12 +++++++++--- spec/lib/gitlab/ci/config/node/job_spec.rb | 3 ++- spec/lib/gitlab/ci/config/node/jobs_spec.rb | 11 ++++++++--- 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index ed8dd0f9e47..61075d3b923 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -80,12 +80,7 @@ module Ci { stage_idx: @stages.index(job[:stage]), stage: job[:stage], - ## - # Refactoring note: - # - before script behaves differently than after script - # - after script returns an array of commands - # - before script should be a concatenated command - commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"), + commands: job[:commands], tag_list: job[:tags] || [], name: name, only: job[:only], @@ -124,8 +119,12 @@ module Ci end def validate_job_keys!(name, job) + ## + # TODO, remove refactoring keys + # + refactoring_keys = [:commands] job.keys.each do |key| - unless ALLOWED_JOB_KEYS.include? key + unless (ALLOWED_JOB_KEYS + refactoring_keys).include? key raise ValidationError, "#{name} job: unknown parameter #{key}" end end diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 5ee91ebcf0b..bb1c3386bd4 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -40,23 +40,24 @@ module Gitlab def before_script if before_script_defined? - before_script_value.to_a + before_script_value else - @global.before_script.to_a + @global.before_script end end def commands - (before_script + script).join("\n") + [before_script, script].compact.join("\n") end private def to_hash - { before_script: before_script_value, - script: script_value, - stage: stage_value, - after_script: after_script_value } + { before_script: before_script, + script: script, + commands: commands, + stage: stage, + after_script: after_script } end def compose! diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 3ffbe9c2e97..f46359f7ee6 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -23,7 +23,7 @@ describe Gitlab::Ci::Config::Node::Global do after_script: ['make clean'], stages: ['build', 'pages'], cache: { key: 'k', untracked: true, paths: ['public/'] }, - rspec: { script: 'rspec' }, + rspec: { script: %w[rspec ls] }, spinach: { script: 'spinach' } } end @@ -129,8 +129,14 @@ describe Gitlab::Ci::Config::Node::Global do describe '#jobs' do it 'returns jobs configuration' do expect(global.jobs) - .to eq(rspec: { script: %w[rspec], stage: 'test' }, - spinach: { script: %w[spinach], stage: 'test' }) + .to eq(rspec: { before_script: %w[ls pwd], + script: %w[rspec ls], + commands: "ls\npwd\nrspec\nls", + stage: 'test' }, + spinach: { before_script: %w[ls pwd], + script: %w[spinach], + commands: "ls\npwd\nspinach", + stage: 'test' }) end end end diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index 635362611a0..816c0f275d6 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -56,6 +56,7 @@ describe Gitlab::Ci::Config::Node::Job do expect(entry.value) .to eq(before_script: %w[ls pwd], script: %w[rspec], + commands: "ls\npwd\nrspec", stage: 'test', after_script: %w[cleanup]) end @@ -114,7 +115,7 @@ describe Gitlab::Ci::Config::Node::Job do end it 'returns correct script' do - expect(entry.before_script).to eq [] + expect(entry.before_script).to be_nil end end end diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb index 255646a001a..60ab1d2150d 100644 --- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -1,7 +1,8 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Jobs do - let(:entry) { described_class.new(config, global: spy) } + let(:entry) { described_class.new(config, global: global) } + let(:global) { double('global', before_script: nil, stages: %w[test]) } describe 'validations' do before do @@ -62,8 +63,12 @@ describe Gitlab::Ci::Config::Node::Jobs do describe '#value' do it 'returns key value' do expect(entry.value) - .to eq(rspec: { script: %w[rspec], stage: 'test' }, - spinach: { script: %w[spinach], stage: 'test' }) + .to eq(rspec: { script: %w[rspec], + commands: 'rspec', + stage: 'test' }, + spinach: { script: %w[spinach], + commands: 'spinach', + stage: 'test' }) end end -- cgit v1.2.1 From 56ae9f6ba933939ced7c8e0eea5abbb34a0a68be Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 14 Jul 2016 13:14:09 +0200 Subject: Improve CI job entry validations in new config --- lib/ci/gitlab_ci_yaml_processor.rb | 7 ------- lib/gitlab/ci/config/node/configurable.rb | 6 ++++-- lib/gitlab/ci/config/node/job.rb | 20 ++++++++++++-------- lib/gitlab/ci/config/node/jobs.rb | 11 ++++++----- lib/gitlab/ci/config/node/validator.rb | 11 +++++++++-- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 4 ++-- spec/lib/gitlab/ci/config/node/global_spec.rb | 14 ++++++-------- spec/lib/gitlab/ci/config/node/job_spec.rb | 16 +++++++++++++--- spec/lib/gitlab/ci/config/node/jobs_spec.rb | 4 ++-- 9 files changed, 54 insertions(+), 39 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 61075d3b923..e18d5b907b4 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -102,7 +102,6 @@ module Ci def validate_job!(name, job) raise ValidationError, "Unknown parameter: #{name}" unless job.is_a?(Hash) && job.has_key?(:script) - validate_job_name!(name) validate_job_keys!(name, job) validate_job_types!(name, job) @@ -112,12 +111,6 @@ module Ci validate_job_dependencies!(name, job) if job[:dependencies] end - def validate_job_name!(name) - if name.blank? || !validate_string(name) - raise ValidationError, "job name should be non-empty string" - end - end - def validate_job_keys!(name, job) ## # TODO, remove refactoring keys diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 8bd752b0e2a..b33d743e2c3 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -55,8 +55,10 @@ module Gitlab end define_method("#{symbol}_value") do - return unless valid? - @entries[symbol].value if @entries[symbol] + if @entries[symbol] + return unless @entries[symbol].valid? + @entries[symbol].value + end end alias_method symbol.to_sym, "#{symbol}_value".to_sym diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index bb1c3386bd4..822b428926a 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -10,7 +10,12 @@ module Gitlab validations do validates :config, presence: true - validates :global, required: true, on: :processed + + with_options on: :processed do + validates :global, required: true + validates :name, presence: true + validates :name, type: Symbol + end end node :before_script, Script, @@ -30,11 +35,11 @@ module Gitlab helpers :before_script, :script, :stage, :type, :after_script + def name + @key + end + def value - ## - # TODO, refactoring step: do not expose internal configuration, - # return only hash value without merging it to internal config. - # @config.merge(to_hash.compact) end @@ -53,10 +58,9 @@ module Gitlab private def to_hash - { before_script: before_script, - script: script, - commands: commands, + { script: script, stage: stage, + commands: commands, after_script: after_script } end diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index 548441df37c..3c1851b9fea 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -10,11 +10,12 @@ module Gitlab validations do validates :config, type: Hash - validate :jobs_presence, on: :processed - def jobs_presence - unless relevant? - errors.add(:config, 'should contain at least one visible job') + with_options on: :processed do + validate do + unless has_visible_job? + errors.add(:config, 'should contain at least one visible job') + end end end end @@ -23,7 +24,7 @@ module Gitlab @config end - def relevant? + def has_visible_job? @entries.values.any?(&:relevant?) end diff --git a/lib/gitlab/ci/config/node/validator.rb b/lib/gitlab/ci/config/node/validator.rb index dcfeb194374..ca000f245aa 100644 --- a/lib/gitlab/ci/config/node/validator.rb +++ b/lib/gitlab/ci/config/node/validator.rb @@ -31,8 +31,15 @@ module Gitlab def location predecessors = ancestors.map(&:key).compact - current = key || @node.class.name.demodulize.underscore.humanize - predecessors.append(current).join(':') + predecessors.append(key_name).join(':') + end + + def key_name + if key.blank? || key.nil? + @node.class.name.demodulize.underscore.humanize + else + key + end end end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 1017f79cc6e..e88f5cfc6dd 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -998,14 +998,14 @@ EOT config = YAML.dump({ '' => { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "job name should be non-empty string") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:job name can't be blank") end it "returns errors if job name is non-string" do config = YAML.dump({ 10 => { script: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "job name should be non-empty string") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:10 name should be a symbol") end it "returns errors if job image parameter is invalid" do diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index f46359f7ee6..fa5ff016995 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -129,14 +129,12 @@ describe Gitlab::Ci::Config::Node::Global do describe '#jobs' do it 'returns jobs configuration' do expect(global.jobs) - .to eq(rspec: { before_script: %w[ls pwd], - script: %w[rspec ls], - commands: "ls\npwd\nrspec\nls", - stage: 'test' }, - spinach: { before_script: %w[ls pwd], - script: %w[spinach], - commands: "ls\npwd\nspinach", - stage: 'test' }) + .to eq(rspec: { script: %w[rspec ls], + stage: 'test', + commands: "ls\npwd\nrspec\nls" }, + spinach: { script: %w[spinach], + stage: 'test', + commands: "ls\npwd\nspinach" }) end end end diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index 816c0f275d6..2ac7509cb6d 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -1,8 +1,9 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Job do - let(:entry) { described_class.new(config, global: global) } - let(:global) { spy('Global') } + let(:entry) { described_class.new(config, attributes) } + let(:attributes) { { key: :rspec, global: global } } + let(:global) { double('global', stages: %w[test]) } before do entry.process! @@ -18,6 +19,15 @@ describe Gitlab::Ci::Config::Node::Job do expect(entry).to be_valid end end + + context 'when job name is empty' do + let(:attributes) { { key: '', global: global } } + + it 'reports error' do + expect(entry.errors) + .to include "job name can't be blank" + end + end end context 'when entry value is not correct' do @@ -27,7 +37,7 @@ describe Gitlab::Ci::Config::Node::Job do describe '#errors' do it 'reports error about a config type' do expect(entry.errors) - .to include 'job config should be a hash' + .to include 'rspec config should be a hash' end end end diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb index 60ab1d2150d..fe7ae61ed81 100644 --- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -34,8 +34,8 @@ describe Gitlab::Ci::Config::Node::Jobs do context 'when job is unspecified' do let(:config) { { rspec: nil } } - it 'is not valid' do - expect(entry).not_to be_valid + it 'reports error' do + expect(entry.errors).to include "rspec config can't be blank" end end -- cgit v1.2.1 From f7c80e9f31944c0001c9bef23d1a8efe33e4adce Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 14 Jul 2016 15:05:46 +0200 Subject: Revert references to global node in CI job entry --- lib/ci/gitlab_ci_yaml_processor.rb | 8 +- lib/gitlab/ci/config/node/job.rb | 20 +---- spec/lib/gitlab/ci/config/node/global_spec.rb | 6 +- spec/lib/gitlab/ci/config/node/job_spec.rb | 116 -------------------------- spec/lib/gitlab/ci/config/node/jobs_spec.rb | 2 - 5 files changed, 8 insertions(+), 144 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index e18d5b907b4..144f9cd7b74 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -80,7 +80,7 @@ module Ci { stage_idx: @stages.index(job[:stage]), stage: job[:stage], - commands: job[:commands], + commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"), tag_list: job[:tags] || [], name: name, only: job[:only], @@ -112,12 +112,8 @@ module Ci end def validate_job_keys!(name, job) - ## - # TODO, remove refactoring keys - # - refactoring_keys = [:commands] job.keys.each do |key| - unless (ALLOWED_JOB_KEYS + refactoring_keys).include? key + unless ALLOWED_JOB_KEYS.include? key raise ValidationError, "#{name} job: unknown parameter #{key}" end end diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 822b428926a..f01a46a8ddc 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -43,25 +43,13 @@ module Gitlab @config.merge(to_hash.compact) end - def before_script - if before_script_defined? - before_script_value - else - @global.before_script - end - end - - def commands - [before_script, script].compact.join("\n") - end - private def to_hash - { script: script, - stage: stage, - commands: commands, - after_script: after_script } + { before_script: before_script_value, + script: script_value, + stage: stage_value, + after_script: after_script_value } end def compose! diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index fa5ff016995..dfcaebe35be 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -130,11 +130,9 @@ describe Gitlab::Ci::Config::Node::Global do it 'returns jobs configuration' do expect(global.jobs) .to eq(rspec: { script: %w[rspec ls], - stage: 'test', - commands: "ls\npwd\nrspec\nls" }, + stage: 'test' }, spinach: { script: %w[spinach], - stage: 'test', - commands: "ls\npwd\nspinach" }) + stage: 'test' }) end end end diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index 2ac7509cb6d..ee3eea9c27a 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -66,131 +66,15 @@ describe Gitlab::Ci::Config::Node::Job do expect(entry.value) .to eq(before_script: %w[ls pwd], script: %w[rspec], - commands: "ls\npwd\nrspec", stage: 'test', after_script: %w[cleanup]) end end end - describe '#before_script' do - context 'when global entry has before script' do - before do - allow(global).to receive(:before_script) - .and_return(%w[ls pwd]) - end - - context 'when before script is overridden' do - let(:config) do - { before_script: %w[whoami], - script: 'rspec' } - end - - it 'returns correct script' do - expect(entry.before_script).to eq %w[whoami] - end - end - - context 'when before script is not overriden' do - let(:config) do - { script: %w[spinach] } - end - - it 'returns correct script' do - expect(entry.before_script).to eq %w[ls pwd] - end - end - end - - context 'when global entry does not have before script' do - before do - allow(global).to receive(:before_script) - .and_return(nil) - end - - context 'when job has before script' do - let(:config) do - { before_script: %w[whoami], - script: 'rspec' } - end - - it 'returns correct script' do - expect(entry.before_script).to eq %w[whoami] - end - end - - context 'when job does not have before script' do - let(:config) do - { script: %w[ls test] } - end - - it 'returns correct script' do - expect(entry.before_script).to be_nil - end - end - end - end - describe '#relevant?' do it 'is a relevant entry' do expect(entry).to be_relevant end end - - describe '#commands' do - context 'when global entry has before script' do - before do - allow(global).to receive(:before_script) - .and_return(%w[ls pwd]) - end - - context 'when before script is overridden' do - let(:config) do - { before_script: %w[whoami], - script: 'rspec' } - end - - it 'returns correct commands' do - expect(entry.commands).to eq "whoami\nrspec" - end - end - - context 'when before script is not overriden' do - let(:config) do - { script: %w[rspec spinach] } - end - - it 'returns correct commands' do - expect(entry.commands).to eq "ls\npwd\nrspec\nspinach" - end - end - end - - context 'when global entry does not have before script' do - before do - allow(global).to receive(:before_script) - .and_return(nil) - end - context 'when job has before script' do - let(:config) do - { before_script: %w[whoami], - script: 'rspec' } - end - - it 'returns correct commands' do - expect(entry.commands).to eq "whoami\nrspec" - end - end - - context 'when job does not have before script' do - let(:config) do - { script: %w[ls test] } - end - - it 'returns correct commands' do - expect(entry.commands).to eq "ls\ntest" - end - end - end - end end diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb index fe7ae61ed81..40837b5f857 100644 --- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -64,10 +64,8 @@ describe Gitlab::Ci::Config::Node::Jobs do it 'returns key value' do expect(entry.value) .to eq(rspec: { script: %w[rspec], - commands: 'rspec', stage: 'test' }, spinach: { script: %w[spinach], - commands: 'spinach', stage: 'test' }) end end -- cgit v1.2.1 From 3e16b015b969a4d5d28240e76bffd382b0772f49 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 14 Jul 2016 15:23:52 +0200 Subject: Revert logical validation in CI job stage entry --- lib/ci/gitlab_ci_yaml_processor.rb | 7 +++ lib/gitlab/ci/config/node/job.rb | 1 - lib/gitlab/ci/config/node/stage.rb | 16 ------- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 4 +- spec/lib/gitlab/ci/config/node/stage_spec.rb | 71 +++------------------------- 5 files changed, 15 insertions(+), 84 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 144f9cd7b74..0217a905eac 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -105,6 +105,7 @@ module Ci validate_job_keys!(name, job) validate_job_types!(name, job) + validate_job_stage!(name, job) if job[:stage] validate_job_variables!(name, job) if job[:variables] validate_job_cache!(name, job) if job[:cache] validate_job_artifacts!(name, job) if job[:artifacts] @@ -153,6 +154,12 @@ module Ci end end + def validate_job_stage!(name, job) + unless job[:stage].is_a?(String) && job[:stage].in?(@stages) + raise ValidationError, "#{name} job: stage parameter should be #{@stages.join(", ")}" + end + end + def validate_job_variables!(name, job) unless validate_variables(job[:variables]) raise ValidationError, diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index f01a46a8ddc..cca9791fc8e 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -12,7 +12,6 @@ module Gitlab validates :config, presence: true with_options on: :processed do - validates :global, required: true validates :name, presence: true validates :name, type: Symbol end diff --git a/lib/gitlab/ci/config/node/stage.rb b/lib/gitlab/ci/config/node/stage.rb index 909358ea170..cbc97641f5a 100644 --- a/lib/gitlab/ci/config/node/stage.rb +++ b/lib/gitlab/ci/config/node/stage.rb @@ -10,22 +10,6 @@ module Gitlab validations do validates :config, type: String - - with_options on: :processed do - validates :global, required: true - - validate do - unless known? - errors.add(:config, - 'should be one of defined stages ' \ - "(#{global.stages.join(', ')})") - end - end - end - end - - def known? - @global.stages.include?(@config) end def self.default diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index e88f5cfc6dd..daa02faf6fb 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1089,14 +1089,14 @@ EOT config = YAML.dump({ rspec: { script: "test", type: "acceptance" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:type config should be one of defined stages (build, test, deploy)") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test, deploy") end it "returns errors if job stage is not a defined stage" do config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", type: "acceptance" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:type config should be one of defined stages (build, test)") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: stage parameter should be build, test") end it "returns errors if stages is not an array" do diff --git a/spec/lib/gitlab/ci/config/node/stage_spec.rb b/spec/lib/gitlab/ci/config/node/stage_spec.rb index 004012f8b38..fb9ec70762a 100644 --- a/spec/lib/gitlab/ci/config/node/stage_spec.rb +++ b/spec/lib/gitlab/ci/config/node/stage_spec.rb @@ -1,17 +1,12 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Stage do - let(:stage) { described_class.new(config, global: global) } - let(:global) { spy('Global') } + let(:stage) { described_class.new(config) } describe 'validations' do context 'when stage config value is correct' do let(:config) { 'build' } - before do - allow(global).to receive(:stages).and_return(%w[build]) - end - describe '#value' do it 'returns a stage key' do expect(stage.value).to eq config @@ -25,66 +20,12 @@ describe Gitlab::Ci::Config::Node::Stage do end end - context 'when stage config is incorrect' do - describe '#errors' do - context 'when reference to global node is not set' do - let(:stage) { described_class.new('test') } - - it 'raises error' do - expect { stage.validate! }.to raise_error( - Gitlab::Ci::Config::Node::Entry::InvalidError, - /Entry needs global attribute set internally./ - ) - end - end - - context 'when value has a wrong type' do - let(:config) { { test: true } } - - it 'reports errors about wrong type' do - expect(stage.errors) - .to include 'stage config should be a string' - end - end - - context 'when stage is not present in global configuration' do - let(:config) { 'unknown' } - - before do - allow(global) - .to receive(:stages).and_return(%w[test deploy]) - end - - it 'reports error about missing stage' do - stage.validate! - - expect(stage.errors) - .to include 'stage config should be one of ' \ - 'defined stages (test, deploy)' - end - end - end - end - end - - describe '#known?' do - before do - allow(global).to receive(:stages).and_return(%w[test deploy]) - end - - context 'when stage is not known' do - let(:config) { :unknown } - - it 'returns false' do - expect(stage.known?).to be false - end - end - - context 'when stage is known' do - let(:config) { 'test' } + context 'when value has a wrong type' do + let(:config) { { test: true } } - it 'returns false' do - expect(stage.known?).to be true + it 'reports errors about wrong type' do + expect(stage.errors) + .to include 'stage config should be a string' end end end -- cgit v1.2.1 From 5923741fe690a688591ad36da894b3103954a437 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 14 Jul 2016 15:45:29 +0200 Subject: Remove references to global entry in new CI config --- lib/gitlab/ci/config/node/entry.rb | 2 +- lib/gitlab/ci/config/node/factory.rb | 1 - spec/lib/gitlab/ci/config/node/factory_spec.rb | 8 +------- spec/lib/gitlab/ci/config/node/job_spec.rb | 6 ++---- spec/lib/gitlab/ci/config/node/jobs_spec.rb | 3 +-- 5 files changed, 5 insertions(+), 15 deletions(-) diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index c95e85200d2..9640103ea22 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -9,7 +9,7 @@ module Gitlab class InvalidError < StandardError; end attr_reader :config, :attributes - attr_accessor :key, :parent, :global, :description + attr_accessor :key, :parent, :description def initialize(config, **attributes) @config = config diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb index 339548e0feb..b509c5edf59 100644 --- a/lib/gitlab/ci/config/node/factory.rb +++ b/lib/gitlab/ci/config/node/factory.rb @@ -63,7 +63,6 @@ module Gitlab node.new(value).tap do |entry| entry.key = @attributes[:key] entry.parent = @attributes[:parent] || @parent - entry.global = @attributes[:global] || @parent.global entry.description = @attributes[:description] end end diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb index bc6bf32ffbf..4e6f2419e13 100644 --- a/spec/lib/gitlab/ci/config/node/factory_spec.rb +++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb @@ -5,11 +5,6 @@ describe Gitlab::Ci::Config::Node::Factory do let(:factory) { described_class.new(node) } let(:node) { Gitlab::Ci::Config::Node::Script } let(:parent) { double('parent') } - let(:global) { double('global') } - - before do - allow(parent).to receive(:global).and_return(global) - end context 'when setting a concrete value' do it 'creates entry with valid value' do @@ -21,13 +16,12 @@ describe Gitlab::Ci::Config::Node::Factory do expect(entry.value).to eq ['ls', 'pwd'] end - it 'sets parent and global attributes' do + it 'sets parent attributes' do entry = factory .value('ls') .parent(parent) .create! - expect(entry.global).to eq global expect(entry.parent).to eq parent end diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index ee3eea9c27a..4c7ac9949cc 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -1,9 +1,7 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Job do - let(:entry) { described_class.new(config, attributes) } - let(:attributes) { { key: :rspec, global: global } } - let(:global) { double('global', stages: %w[test]) } + let(:entry) { described_class.new(config, key: :rspec) } before do entry.process! @@ -21,7 +19,7 @@ describe Gitlab::Ci::Config::Node::Job do end context 'when job name is empty' do - let(:attributes) { { key: '', global: global } } + let(:entry) { described_class.new(config, key: ''.to_sym) } it 'reports error' do expect(entry.errors) diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb index 40837b5f857..c4c130abb6d 100644 --- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -1,8 +1,7 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Jobs do - let(:entry) { described_class.new(config, global: global) } - let(:global) { double('global', before_script: nil, stages: %w[test]) } + let(:entry) { described_class.new(config) } describe 'validations' do before do -- cgit v1.2.1 From 615c9730e7783e82287d2b65f58da6336d3d2410 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 14 Jul 2016 16:01:18 +0200 Subject: Remove job cache configfrom legacy yaml processor --- lib/ci/gitlab_ci_yaml_processor.rb | 21 --------------------- lib/gitlab/ci/config/node/job.rb | 6 +++++- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 6 +++--- 3 files changed, 8 insertions(+), 25 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 0217a905eac..3e4767cc9f6 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -107,7 +107,6 @@ module Ci validate_job_stage!(name, job) if job[:stage] validate_job_variables!(name, job) if job[:variables] - validate_job_cache!(name, job) if job[:cache] validate_job_artifacts!(name, job) if job[:artifacts] validate_job_dependencies!(name, job) if job[:dependencies] end @@ -167,26 +166,6 @@ module Ci end end - def validate_job_cache!(name, job) - job[:cache].keys.each do |key| - unless ALLOWED_CACHE_KEYS.include? key - raise ValidationError, "#{name} job: cache unknown parameter #{key}" - end - end - - if job[:cache][:key] && !validate_string(job[:cache][:key]) - raise ValidationError, "#{name} job: cache:key parameter should be a string" - end - - if job[:cache][:untracked] && !validate_boolean(job[:cache][:untracked]) - raise ValidationError, "#{name} job: cache:untracked parameter should be an boolean" - end - - if job[:cache][:paths] && !validate_array_of_strings(job[:cache][:paths]) - raise ValidationError, "#{name} job: cache:paths parameter should be an array of strings" - end - end - def validate_job_artifacts!(name, job) job[:artifacts].keys.each do |key| unless ALLOWED_ARTIFACTS_KEYS.include? key diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index cca9791fc8e..483be2a21cc 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -32,7 +32,10 @@ module Gitlab node :after_script, Script, description: 'Commands that will be executed when finishing job.' - helpers :before_script, :script, :stage, :type, :after_script + node :cache, Cache, + description: 'Cache definition for this job.' + + helpers :before_script, :script, :stage, :type, :after_script, :cache def name @key @@ -48,6 +51,7 @@ module Gitlab { before_script: before_script_value, script: script_value, stage: stage_value, + cache: cache_value, after_script: after_script_value } end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index daa02faf6fb..c9602bcca22 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1201,21 +1201,21 @@ EOT config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { key: 1 } } }) expect do GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:key parameter should be a string") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:cache:key config should be a string or symbol") end it "returns errors if job cache:untracked is not an array of strings" do config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { untracked: "string" } } }) expect do GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:untracked parameter should be an boolean") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:cache:untracked config should be a boolean value") end it "returns errors if job cache:paths is not an array of strings" do config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", cache: { paths: "string" } } }) expect do GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: cache:paths parameter should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:cache:paths config should be an array of strings") end it "returns errors if job dependencies is not an array of strings" do -- cgit v1.2.1 From 41bcbdd8c2412769a376cd37541ad6e65a1af1f2 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 15 Jul 2016 21:07:51 +0200 Subject: Add metadata to new CI config and expose job name --- lib/ci/gitlab_ci_yaml_processor.rb | 4 +-- lib/gitlab/ci/config/node/configurable.rb | 3 +-- lib/gitlab/ci/config/node/entry.rb | 7 ++--- lib/gitlab/ci/config/node/factory.rb | 10 +++---- lib/gitlab/ci/config/node/global.rb | 5 ++-- lib/gitlab/ci/config/node/job.rb | 20 +++++++------- lib/gitlab/ci/config/node/jobs.rb | 9 ++++--- spec/lib/gitlab/ci/config/node/factory_spec.rb | 37 ++++++++++---------------- spec/lib/gitlab/ci/config/node/global_spec.rb | 13 +++++---- spec/lib/gitlab/ci/config/node/job_spec.rb | 9 ++++--- spec/lib/gitlab/ci/config/node/jobs_spec.rb | 12 +++++---- 11 files changed, 60 insertions(+), 69 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 3e4767cc9f6..0704e8f1683 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -82,7 +82,7 @@ module Ci stage: job[:stage], commands: [job[:before_script] || @before_script, job[:script]].flatten.compact.join("\n"), tag_list: job[:tags] || [], - name: name, + name: job[:name], only: job[:only], except: job[:except], allow_failure: job[:allow_failure] || false, @@ -113,7 +113,7 @@ module Ci def validate_job_keys!(name, job) job.keys.each do |key| - unless ALLOWED_JOB_KEYS.include? key + unless (ALLOWED_JOB_KEYS + %i[name]).include? key raise ValidationError, "#{name} job: unknown parameter #{key}" end end diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index b33d743e2c3..10b2db86d24 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -28,8 +28,7 @@ module Gitlab def create(key, factory) factory .value(@config[key]) - .parent(self) - .with(key: key) + .with(key: key, parent: self) factory.create! end diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 9640103ea22..011c3be849e 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -11,13 +11,10 @@ module Gitlab attr_reader :config, :attributes attr_accessor :key, :parent, :description - def initialize(config, **attributes) + def initialize(config, **metadata) @config = config @entries = {} - - (@attributes = attributes).each do |attribute, value| - public_send("#{attribute}=", value) - end + @metadata = metadata @validator = self.class.validator.new(self) @validator.validate(:new) diff --git a/lib/gitlab/ci/config/node/factory.rb b/lib/gitlab/ci/config/node/factory.rb index b509c5edf59..707b052e6a8 100644 --- a/lib/gitlab/ci/config/node/factory.rb +++ b/lib/gitlab/ci/config/node/factory.rb @@ -10,6 +10,7 @@ module Gitlab def initialize(node) @node = node + @metadata = {} @attributes = {} end @@ -18,8 +19,8 @@ module Gitlab self end - def parent(parent) - @parent = parent + def metadata(metadata) + @metadata.merge!(metadata) self end @@ -30,7 +31,6 @@ module Gitlab def create! raise InvalidFactory unless defined?(@value) - raise InvalidFactory unless defined?(@parent) ## # We assume that unspecified entry is undefined. @@ -60,9 +60,9 @@ module Gitlab end def fabricate(node, value = nil) - node.new(value).tap do |entry| + node.new(value, @metadata).tap do |entry| entry.key = @attributes[:key] - entry.parent = @attributes[:parent] || @parent + entry.parent = @attributes[:parent] entry.description = @attributes[:description] end end diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index 4a958735599..bedacd904cc 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -54,9 +54,8 @@ module Gitlab def compose_jobs! factory = Node::Factory.new(Node::Jobs) .value(@config.except(*nodes.keys)) - .parent(self) - .with(key: :jobs, global: self) - .with(description: 'Jobs definition for this pipeline') + .with(key: :jobs, parent: self, + description: 'Jobs definition for this pipeline') @entries[:jobs] = factory.create! end diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 483be2a21cc..9280412a638 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -10,11 +10,8 @@ module Gitlab validations do validates :config, presence: true - - with_options on: :processed do - validates :name, presence: true - validates :name, type: Symbol - end + validates :name, presence: true + validates :name, type: Symbol end node :before_script, Script, @@ -38,7 +35,7 @@ module Gitlab helpers :before_script, :script, :stage, :type, :after_script, :cache def name - @key + @metadata[:name] end def value @@ -48,11 +45,12 @@ module Gitlab private def to_hash - { before_script: before_script_value, - script: script_value, - stage: stage_value, - cache: cache_value, - after_script: after_script_value } + { name: name, + before_script: before_script, + script: script, + stage: stage, + cache: cache, + after_script: after_script } end def compose! diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index 3c1851b9fea..3cabcd6b763 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -31,14 +31,15 @@ module Gitlab private def create(name, config) - Node::Factory.new(node(name)) + Node::Factory.new(job_class(name)) .value(config || {}) - .parent(self) - .with(key: name, description: "#{name} job definition.") + .metadata(name: name) + .with(key: name, parent: self, + description: "#{name} job definition.") .create! end - def node(name) + def job_class(name) if name.to_s.start_with?('.') Node::HiddenJob else diff --git a/spec/lib/gitlab/ci/config/node/factory_spec.rb b/spec/lib/gitlab/ci/config/node/factory_spec.rb index 4e6f2419e13..d26185ba585 100644 --- a/spec/lib/gitlab/ci/config/node/factory_spec.rb +++ b/spec/lib/gitlab/ci/config/node/factory_spec.rb @@ -4,32 +4,20 @@ describe Gitlab::Ci::Config::Node::Factory do describe '#create!' do let(:factory) { described_class.new(node) } let(:node) { Gitlab::Ci::Config::Node::Script } - let(:parent) { double('parent') } context 'when setting a concrete value' do it 'creates entry with valid value' do entry = factory .value(['ls', 'pwd']) - .parent(parent) .create! expect(entry.value).to eq ['ls', 'pwd'] end - it 'sets parent attributes' do - entry = factory - .value('ls') - .parent(parent) - .create! - - expect(entry.parent).to eq parent - end - context 'when setting description' do it 'creates entry with description' do entry = factory .value(['ls', 'pwd']) - .parent(parent) .with(description: 'test description') .create! @@ -42,7 +30,6 @@ describe Gitlab::Ci::Config::Node::Factory do it 'creates entry with custom key' do entry = factory .value(['ls', 'pwd']) - .parent(parent) .with(key: 'test key') .create! @@ -56,7 +43,6 @@ describe Gitlab::Ci::Config::Node::Factory do it 'creates entry with valid parent' do entry = factory .value('ls') - .parent(parent) .with(parent: object) .create! @@ -73,23 +59,28 @@ describe Gitlab::Ci::Config::Node::Factory do end end - context 'when not setting parent object' do - it 'raises error' do - expect { factory.value('ls').create! }.to raise_error( - Gitlab::Ci::Config::Node::Factory::InvalidFactory - ) - end - end - context 'when creating entry with nil value' do it 'creates an undefined entry' do entry = factory .value(nil) - .parent(parent) .create! expect(entry).to be_an_instance_of Gitlab::Ci::Config::Node::Undefined end end + + context 'when passing metadata' do + let(:node) { spy('node') } + + it 'passes metadata as a parameter' do + factory + .value('some value') + .metadata(some: 'hash') + .create! + + expect(node).to have_received(:new) + .with('some value', { some: 'hash' }) + end + end end end diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index dfcaebe35be..2a071b57c72 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -128,11 +128,14 @@ describe Gitlab::Ci::Config::Node::Global do describe '#jobs' do it 'returns jobs configuration' do - expect(global.jobs) - .to eq(rspec: { script: %w[rspec ls], - stage: 'test' }, - spinach: { script: %w[spinach], - stage: 'test' }) + expect(global.jobs).to eq( + rspec: { name: :rspec, + script: %w[rspec ls], + stage: 'test' }, + spinach: { name: :spinach, + script: %w[spinach], + stage: 'test' } + ) end end end diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index 4c7ac9949cc..b2559e6e73c 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Job do - let(:entry) { described_class.new(config, key: :rspec) } + let(:entry) { described_class.new(config, name: :rspec) } before do entry.process! @@ -19,7 +19,7 @@ describe Gitlab::Ci::Config::Node::Job do end context 'when job name is empty' do - let(:entry) { described_class.new(config, key: ''.to_sym) } + let(:entry) { described_class.new(config, name: ''.to_sym) } it 'reports error' do expect(entry.errors) @@ -35,7 +35,7 @@ describe Gitlab::Ci::Config::Node::Job do describe '#errors' do it 'reports error about a config type' do expect(entry.errors) - .to include 'rspec config should be a hash' + .to include 'job config should be a hash' end end end @@ -62,7 +62,8 @@ describe Gitlab::Ci::Config::Node::Job do it 'returns correct value' do expect(entry.value) - .to eq(before_script: %w[ls pwd], + .to eq(name: :rspec, + before_script: %w[ls pwd], script: %w[rspec], stage: 'test', after_script: %w[cleanup]) diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb index c4c130abb6d..4f08f2f9b69 100644 --- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -61,11 +61,13 @@ describe Gitlab::Ci::Config::Node::Jobs do describe '#value' do it 'returns key value' do - expect(entry.value) - .to eq(rspec: { script: %w[rspec], - stage: 'test' }, - spinach: { script: %w[spinach], - stage: 'test' }) + expect(entry.value).to eq( + rspec: { name: :rspec, + script: %w[rspec], + stage: 'test' }, + spinach: { name: :spinach, + script: %w[spinach], + stage: 'test' }) end end -- cgit v1.2.1 From 4bb60b0789a31061cbc81af90b7d5dc558f985b3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 15 Jul 2016 21:39:26 +0200 Subject: Simplify CI config and remove logical validation --- lib/gitlab/ci/config.rb | 1 - lib/gitlab/ci/config/node/entry.rb | 11 ++--------- lib/gitlab/ci/config/node/global.rb | 11 +---------- lib/gitlab/ci/config/node/jobs.rb | 16 +++++++--------- spec/lib/gitlab/ci/config/node/job_spec.rb | 5 +---- spec/lib/gitlab/ci/config/node/jobs_spec.rb | 5 +---- 6 files changed, 12 insertions(+), 37 deletions(-) diff --git a/lib/gitlab/ci/config.rb b/lib/gitlab/ci/config.rb index 20f5f8e2ff8..ae82c0db3f1 100644 --- a/lib/gitlab/ci/config.rb +++ b/lib/gitlab/ci/config.rb @@ -15,7 +15,6 @@ module Gitlab @global = Node::Global.new(@config) @global.process! - @global.validate! end def valid? diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 011c3be849e..559688c1bca 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -8,13 +8,13 @@ module Gitlab class Entry class InvalidError < StandardError; end - attr_reader :config, :attributes + attr_reader :config, :metadata attr_accessor :key, :parent, :description def initialize(config, **metadata) @config = config - @entries = {} @metadata = metadata + @entries = {} @validator = self.class.validator.new(self) @validator.validate(:new) @@ -27,13 +27,6 @@ module Gitlab @entries.each_value(&:process!) end - def validate! - return unless valid? - - @validator.validate(:processed) - @entries.each_value(&:validate!) - end - def leaf? nodes.none? end diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index bedacd904cc..3b0d0113d61 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -36,19 +36,13 @@ module Gitlab helpers :before_script, :image, :services, :after_script, :variables, :stages, :types, :cache, :jobs - def initialize(*) - super - - @global = self - end - private def compose! super - compose_stages! compose_jobs! + compose_stages! end def compose_jobs! @@ -65,9 +59,6 @@ module Gitlab # Deprecated `:types` key workaround - if types are defined and # stages are not defined we use types definition as stages. # - # Otherwise we use stages in favor of types, and remove types from - # processing. - # if types_defined? && !stages_defined? @entries[:stages] = @entries[:types] end diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index 3cabcd6b763..77ff3459958 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -11,23 +11,21 @@ module Gitlab validations do validates :config, type: Hash - with_options on: :processed do - validate do - unless has_visible_job? - errors.add(:config, 'should contain at least one visible job') - end + validate do + unless has_visible_job? + errors.add(:config, 'should contain at least one visible job') end end + + def has_visible_job? + config.any? { |key, _| !key.to_s.start_with?('.') } + end end def nodes @config end - def has_visible_job? - @entries.values.any?(&:relevant?) - end - private def create(name, config) diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index b2559e6e73c..2721908c5d7 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -3,10 +3,7 @@ require 'spec_helper' describe Gitlab::Ci::Config::Node::Job do let(:entry) { described_class.new(config, name: :rspec) } - before do - entry.process! - entry.validate! - end + before { entry.process! } describe 'validations' do context 'when entry config value is correct' do diff --git a/spec/lib/gitlab/ci/config/node/jobs_spec.rb b/spec/lib/gitlab/ci/config/node/jobs_spec.rb index 4f08f2f9b69..b8d9c70479c 100644 --- a/spec/lib/gitlab/ci/config/node/jobs_spec.rb +++ b/spec/lib/gitlab/ci/config/node/jobs_spec.rb @@ -4,10 +4,7 @@ describe Gitlab::Ci::Config::Node::Jobs do let(:entry) { described_class.new(config) } describe 'validations' do - before do - entry.process! - entry.validate! - end + before { entry.process! } context 'when entry config value is correct' do let(:config) { { rspec: { script: 'rspec' } } } -- cgit v1.2.1 From 17084d42aa4f2a9d58d6b6d30656d5b7cfffe007 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 15 Jul 2016 22:35:29 +0200 Subject: Simplify abstract class for CI config entry nodes --- lib/gitlab/ci/config/node/configurable.rb | 12 ++++++----- lib/gitlab/ci/config/node/entry.rb | 17 +-------------- lib/gitlab/ci/config/node/global.rb | 2 +- lib/gitlab/ci/config/node/jobs.rb | 31 ++++++++++++--------------- spec/lib/gitlab/ci/config/node/global_spec.rb | 14 ++++++++---- 5 files changed, 33 insertions(+), 43 deletions(-) diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 10b2db86d24..93a9a253322 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -25,12 +25,14 @@ module Gitlab private - def create(key, factory) - factory - .value(@config[key]) - .with(key: key, parent: self) + def compose! + self.class.nodes.each do |key, factory| + factory + .value(@config[key]) + .with(key: key, parent: self) - factory.create! + @entries[key] = factory.create! + end end class_methods do diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 559688c1bca..813e394e51b 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -28,11 +28,7 @@ module Gitlab end def leaf? - nodes.none? - end - - def nodes - self.class.nodes + @entries.none? end def descendants @@ -74,10 +70,6 @@ module Gitlab def self.default end - def self.nodes - {} - end - def self.validator Validator end @@ -85,13 +77,6 @@ module Gitlab private def compose! - nodes.each do |key, essence| - @entries[key] = create(key, essence) - end - end - - def create(entry, essence) - raise NotImplementedError end end end diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index 3b0d0113d61..b545b78a940 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -47,7 +47,7 @@ module Gitlab def compose_jobs! factory = Node::Factory.new(Node::Jobs) - .value(@config.except(*nodes.keys)) + .value(@config.except(*self.class.nodes.keys)) .with(key: :jobs, parent: self, description: 'Jobs definition for this pipeline') diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index 77ff3459958..908c8f4f120 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -22,28 +22,25 @@ module Gitlab end end - def nodes - @config - end - private - def create(name, config) - Node::Factory.new(job_class(name)) - .value(config || {}) - .metadata(name: name) - .with(key: name, parent: self, - description: "#{name} job definition.") - .create! - end + def compose! + @config.each do |name, config| + node = hidden?(name) ? Node::HiddenJob : Node::Job - def job_class(name) - if name.to_s.start_with?('.') - Node::HiddenJob - else - Node::Job + factory = Node::Factory.new(node) + .value(config || {}) + .metadata(name: name) + .with(key: name, parent: self, + description: "#{name} job definition.") + + @entries[name] = factory.create! end end + + def hidden?(name) + name.to_s.start_with?('.') + end end end end diff --git a/spec/lib/gitlab/ci/config/node/global_spec.rb b/spec/lib/gitlab/ci/config/node/global_spec.rb index 2a071b57c72..2f87d270b36 100644 --- a/spec/lib/gitlab/ci/config/node/global_spec.rb +++ b/spec/lib/gitlab/ci/config/node/global_spec.rb @@ -51,11 +51,11 @@ describe Gitlab::Ci::Config::Node::Global do expect(global.descendants.second.description) .to eq 'Docker image that will be used to execute jobs.' end - end - describe '#leaf?' do - it 'is not leaf' do - expect(global).not_to be_leaf + describe '#leaf?' do + it 'is not leaf' do + expect(global).not_to be_leaf + end end end @@ -65,6 +65,12 @@ describe Gitlab::Ci::Config::Node::Global do expect(global.before_script).to be nil end end + + describe '#leaf?' do + it 'is leaf' do + expect(global).to be_leaf + end + end end context 'when processed' do -- cgit v1.2.1 From 27e88efceb9d59affebf93be040b0a9b0bf31b2f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 18 Jul 2016 09:54:38 +0200 Subject: Move job image and services nodes to new CI config --- lib/ci/gitlab_ci_yaml_processor.rb | 8 -------- lib/gitlab/ci/config/node/job.rb | 11 ++++++++++- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 8 ++++---- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 0704e8f1683..b8d84de2dbe 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -120,14 +120,6 @@ module Ci end def validate_job_types!(name, job) - if job[:image] && !validate_string(job[:image]) - raise ValidationError, "#{name} job: image should be a string" - end - - if job[:services] && !validate_array_of_strings(job[:services]) - raise ValidationError, "#{name} job: services should be an array of strings" - end - if job[:tags] && !validate_array_of_strings(job[:tags]) raise ValidationError, "#{name} job: tags parameter should be an array of strings" end diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 9280412a638..1c28969be14 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -32,7 +32,14 @@ module Gitlab node :cache, Cache, description: 'Cache definition for this job.' - helpers :before_script, :script, :stage, :type, :after_script, :cache + node :image, Image, + description: 'Image that will be used to execute this job.' + + node :services, Services, + description: 'Services that will be used to execute this job.' + + helpers :before_script, :script, :stage, :type, :after_script, + :cache, :image, :services def name @metadata[:name] @@ -48,6 +55,8 @@ module Gitlab { name: name, before_script: before_script, script: script, + image: image, + services: services, stage: stage, cache: cache, after_script: after_script } diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index d629bd252e2..6e6898e758c 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1012,7 +1012,7 @@ EOT config = YAML.dump({ rspec: { script: "test", image: ["test"] } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: image should be a string") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:image config should be a string") end it "returns errors if services parameter is not an array" do @@ -1033,14 +1033,14 @@ EOT config = YAML.dump({ rspec: { script: "test", services: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: services should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be an array of strings") end it "returns errors if job services parameter is not an array of strings" do config = YAML.dump({ rspec: { script: "test", services: [10, "test"] } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: services should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:services config should be an array of strings") end it "returns error if job configuration is invalid" do @@ -1054,7 +1054,7 @@ EOT config = YAML.dump({ extra: { services: "test" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "Unknown parameter: extra") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:extra:services config should be an array of strings") end it "returns errors if there are no jobs defined" do -- cgit v1.2.1 From 1bf9e34713b414f0e1ac8bbfe464a4cd5300581f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 18 Jul 2016 12:37:42 +0200 Subject: Move except and only job nodes to new CI config --- lib/gitlab/ci/config/node/job.rb | 10 ++++- lib/gitlab/ci/config/node/while.rb | 26 +++++++++++++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 4 +- spec/lib/gitlab/ci/config/node/while_spec.rb | 56 ++++++++++++++++++++++++++++ 4 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 lib/gitlab/ci/config/node/while.rb create mode 100644 spec/lib/gitlab/ci/config/node/while_spec.rb diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 1c28969be14..401611def17 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -38,8 +38,14 @@ module Gitlab node :services, Services, description: 'Services that will be used to execute this job.' + node :only, While, + description: 'Refs policy this job will be executed for.' + + node :except, While, + description: 'Refs policy this job will be executed for.' + helpers :before_script, :script, :stage, :type, :after_script, - :cache, :image, :services + :cache, :image, :services, :only, :except def name @metadata[:name] @@ -59,6 +65,8 @@ module Gitlab services: services, stage: stage, cache: cache, + only: only, + except: except, after_script: after_script } end diff --git a/lib/gitlab/ci/config/node/while.rb b/lib/gitlab/ci/config/node/while.rb new file mode 100644 index 00000000000..84d4352624d --- /dev/null +++ b/lib/gitlab/ci/config/node/while.rb @@ -0,0 +1,26 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a ref and trigger policy for the job. + # + class While < Entry + include Validatable + + validations do + include LegacyValidationHelpers + + validate :array_of_strings_or_regexps + + def array_of_strings_or_regexps + unless validate_array_of_strings_or_regexps(config) + errors.add(:config, 'should be an array of strings or regexps') + end + end + end + end + end + end + end +end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 6e6898e758c..429ffd6ef35 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -163,7 +163,7 @@ module Ci shared_examples 'raises an error' do it do - expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'rspec job: only parameter should be an array of strings or regexps') + expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'jobs:rspec:only config should be an array of strings or regexps') end end @@ -319,7 +319,7 @@ module Ci shared_examples 'raises an error' do it do - expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'rspec job: except parameter should be an array of strings or regexps') + expect { processor }.to raise_error(GitlabCiYamlProcessor::ValidationError, 'jobs:rspec:except config should be an array of strings or regexps') end end diff --git a/spec/lib/gitlab/ci/config/node/while_spec.rb b/spec/lib/gitlab/ci/config/node/while_spec.rb new file mode 100644 index 00000000000..aac2ed7b3db --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/while_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::While do + let(:entry) { described_class.new(config) } + + describe 'validations' do + context 'when entry config value is valid' do + context 'when config is a branch or tag name' do + let(:config) { %w[master feature/branch] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns key value' do + expect(entry.value).to eq config + end + end + end + + context 'when config is a regexp' do + let(:config) { ['/^issue-.*$/'] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when config is a special keyword' do + let(:config) { %w[tags triggers branches] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + end + + context 'when entry value is not valid' do + let(:config) { [1] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'while config should be an array of strings or regexps' + end + end + end + end +end -- cgit v1.2.1 From 47fa9f33ca552e085df2158db41b614a79f3651f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 18 Jul 2016 13:09:00 +0200 Subject: Move job variables config entry to new CI config --- lib/ci/gitlab_ci_yaml_processor.rb | 16 ---------------- lib/gitlab/ci/config/node/job.rb | 6 +++++- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index b8d84de2dbe..7306fd110bd 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -106,7 +106,6 @@ module Ci validate_job_types!(name, job) validate_job_stage!(name, job) if job[:stage] - validate_job_variables!(name, job) if job[:variables] validate_job_artifacts!(name, job) if job[:artifacts] validate_job_dependencies!(name, job) if job[:dependencies] end @@ -124,14 +123,6 @@ module Ci raise ValidationError, "#{name} job: tags parameter should be an array of strings" end - if job[:only] && !validate_array_of_strings_or_regexps(job[:only]) - raise ValidationError, "#{name} job: only parameter should be an array of strings or regexps" - end - - if job[:except] && !validate_array_of_strings_or_regexps(job[:except]) - raise ValidationError, "#{name} job: except parameter should be an array of strings or regexps" - end - if job[:allow_failure] && !validate_boolean(job[:allow_failure]) raise ValidationError, "#{name} job: allow_failure parameter should be an boolean" end @@ -151,13 +142,6 @@ module Ci end end - def validate_job_variables!(name, job) - unless validate_variables(job[:variables]) - raise ValidationError, - "#{name} job: variables should be a map of key-value strings" - end - end - def validate_job_artifacts!(name, job) job[:artifacts].keys.each do |key| unless ALLOWED_ARTIFACTS_KEYS.include? key diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 401611def17..d2113556a08 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -44,8 +44,11 @@ module Gitlab node :except, While, description: 'Refs policy this job will be executed for.' + node :variables, Variables, + description: 'Environment variables available for this job.' + helpers :before_script, :script, :stage, :type, :after_script, - :cache, :image, :services, :only, :except + :cache, :image, :services, :only, :except, :variables def name @metadata[:name] @@ -67,6 +70,7 @@ module Gitlab cache: cache, only: only, except: except, + variables: variables_defined? ? variables : nil, after_script: after_script } end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 429ffd6ef35..fe2c109f463 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -534,7 +534,7 @@ module Ci expect { GitlabCiYamlProcessor.new(config, path) } .to raise_error(GitlabCiYamlProcessor::ValidationError, - /job: variables should be a map/) + /jobs:rspec:variables config should be a hash of key value pairs/) end end -- cgit v1.2.1 From 24b686ebb64e2f5a02d812e9aa726f1ba0868c2e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 18 Jul 2016 14:15:01 +0200 Subject: Move job artifacts configuration new CI config code --- lib/ci/gitlab_ci_yaml_processor.rb | 20 ---------- lib/gitlab/ci/config/node/artifacts.rb | 32 ++++++++++++++++ lib/gitlab/ci/config/node/attributable.rb | 23 ++++++++++++ lib/gitlab/ci/config/node/job.rb | 7 +++- lib/gitlab/ci/config/node/validators.rb | 10 +++++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 12 +++--- spec/lib/gitlab/ci/config/node/artifacts_spec.rb | 34 +++++++++++++++++ .../lib/gitlab/ci/config/node/attributable_spec.rb | 43 ++++++++++++++++++++++ 8 files changed, 154 insertions(+), 27 deletions(-) create mode 100644 lib/gitlab/ci/config/node/artifacts.rb create mode 100644 lib/gitlab/ci/config/node/attributable.rb create mode 100644 spec/lib/gitlab/ci/config/node/artifacts_spec.rb create mode 100644 spec/lib/gitlab/ci/config/node/attributable_spec.rb diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 7306fd110bd..ec85cf1bd3d 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -148,26 +148,6 @@ module Ci raise ValidationError, "#{name} job: artifacts unknown parameter #{key}" end end - - if job[:artifacts][:name] && !validate_string(job[:artifacts][:name]) - raise ValidationError, "#{name} job: artifacts:name parameter should be a string" - end - - if job[:artifacts][:untracked] && !validate_boolean(job[:artifacts][:untracked]) - raise ValidationError, "#{name} job: artifacts:untracked parameter should be an boolean" - end - - if job[:artifacts][:paths] && !validate_array_of_strings(job[:artifacts][:paths]) - raise ValidationError, "#{name} job: artifacts:paths parameter should be an array of strings" - end - - if job[:artifacts][:when] && !job[:artifacts][:when].in?(%w[on_success on_failure always]) - raise ValidationError, "#{name} job: artifacts:when parameter should be on_success, on_failure or always" - end - - if job[:artifacts][:expire_in] && !validate_duration(job[:artifacts][:expire_in]) - raise ValidationError, "#{name} job: artifacts:expire_in parameter should be a duration" - end end def validate_job_dependencies!(name, job) diff --git a/lib/gitlab/ci/config/node/artifacts.rb b/lib/gitlab/ci/config/node/artifacts.rb new file mode 100644 index 00000000000..7b3eb7e1992 --- /dev/null +++ b/lib/gitlab/ci/config/node/artifacts.rb @@ -0,0 +1,32 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a configuration of job artifacts. + # + class Artifacts < Entry + include Validatable + include Attributable + + attributes :name, :untracked, :paths, :when, :expire_in + + validations do + validates :config, type: Hash + + with_options allow_nil: true do + validates :name, type: String + validates :untracked, boolean: true + validates :paths, array_of_strings: true + validates :when, + inclusion: { in: %w[on_success on_failure always], + message: 'should be on_success, on_failure ' \ + 'or always' } + validates :expire_in, duration: true + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/attributable.rb b/lib/gitlab/ci/config/node/attributable.rb new file mode 100644 index 00000000000..6e935c025e4 --- /dev/null +++ b/lib/gitlab/ci/config/node/attributable.rb @@ -0,0 +1,23 @@ +module Gitlab + module Ci + class Config + module Node + module Attributable + extend ActiveSupport::Concern + + class_methods do + def attributes(*attributes) + attributes.each do |attribute| + define_method(attribute) do + return unless config.is_a?(Hash) + + config[attribute] + end + end + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index d2113556a08..a6d7f16769c 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -47,8 +47,12 @@ module Gitlab node :variables, Variables, description: 'Environment variables available for this job.' + node :artifacts, Artifacts, + description: 'Artifacts configuration for this job.' + helpers :before_script, :script, :stage, :type, :after_script, - :cache, :image, :services, :only, :except, :variables + :cache, :image, :services, :only, :except, :variables, + :artifacts def name @metadata[:name] @@ -71,6 +75,7 @@ module Gitlab only: only, except: except, variables: variables_defined? ? variables : nil, + artifacts: artifacts, after_script: after_script } end diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb index d33b407af68..5568be80166 100644 --- a/lib/gitlab/ci/config/node/validators.rb +++ b/lib/gitlab/ci/config/node/validators.rb @@ -33,6 +33,16 @@ module Gitlab end end + class DurationValidator < ActiveModel::EachValidator + include LegacyValidationHelpers + + def validate_each(record, attribute, value) + unless validate_duration(value) + record.errors.add(attribute, 'should be a duration') + end + end + end + class RequiredValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) if value.nil? diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index fe2c109f463..28849bcf3f4 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1138,42 +1138,42 @@ EOT config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { name: 1 } } }) expect do GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:name parameter should be a string") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts name should be a string") end it "returns errors if job artifacts:when is not an a predefined value" do config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { when: 1 } } }) expect do GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:when parameter should be on_success, on_failure or always") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts when should be on_success, on_failure or always") end it "returns errors if job artifacts:expire_in is not an a string" do config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { expire_in: 1 } } }) expect do GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:expire_in parameter should be a duration") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts expire in should be a duration") end it "returns errors if job artifacts:expire_in is not an a valid duration" do config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { expire_in: "7 elephants" } } }) expect do GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:expire_in parameter should be a duration") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts expire in should be a duration") end it "returns errors if job artifacts:untracked is not an array of strings" do config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { untracked: "string" } } }) expect do GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:untracked parameter should be an boolean") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts untracked should be a boolean value") end it "returns errors if job artifacts:paths is not an array of strings" do config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", artifacts: { paths: "string" } } }) expect do GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: artifacts:paths parameter should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec:artifacts paths should be an array of strings") end it "returns errors if cache:untracked is not an array of strings" do diff --git a/spec/lib/gitlab/ci/config/node/artifacts_spec.rb b/spec/lib/gitlab/ci/config/node/artifacts_spec.rb new file mode 100644 index 00000000000..4973f7d599a --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/artifacts_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Artifacts do + let(:entry) { described_class.new(config) } + + describe 'validation' do + context 'when entry config value is correct' do + let(:config) { { paths: %w[public/] } } + + describe '#value' do + it 'returns image string' do + expect(entry.value).to eq config + end + end + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when entry value is not correct' do + let(:config) { { name: 10 } } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'artifacts name should be a string' + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/attributable_spec.rb b/spec/lib/gitlab/ci/config/node/attributable_spec.rb new file mode 100644 index 00000000000..24d9daafd88 --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/attributable_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Attributable do + let(:node) { Class.new } + let(:instance) { node.new } + + before do + node.include(described_class) + + node.class_eval do + attributes :name, :test + end + end + + context 'config is a hash' do + before do + allow(instance) + .to receive(:config) + .and_return({ name: 'some name', test: 'some test' }) + end + + it 'returns the value of config' do + expect(instance.name).to eq 'some name' + expect(instance.test).to eq 'some test' + end + + it 'returns no method error for unknown attributes' do + expect { instance.unknown }.to raise_error(NoMethodError) + end + end + + context 'config is not a hash' do + before do + allow(instance) + .to receive(:config) + .and_return('some test') + end + + it 'returns nil' do + expect(instance.test).to be_nil + end + end +end -- cgit v1.2.1 From 7cef4f1908d99743bf1dfb9c1cfdd6b2936b2b3d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 18 Jul 2016 15:38:06 +0200 Subject: Improve valid keys validation for CI config nodes --- lib/ci/gitlab_ci_yaml_processor.rb | 9 --------- lib/gitlab/ci/config/node/artifacts.rb | 2 ++ lib/gitlab/ci/config/node/cache.rb | 8 ++++---- lib/gitlab/ci/config/node/validator.rb | 8 +------- lib/gitlab/ci/config/node/validators.rb | 9 +++++---- spec/lib/gitlab/ci/config/node/artifacts_spec.rb | 21 ++++++++++++++++----- 6 files changed, 28 insertions(+), 29 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index ec85cf1bd3d..6901dfbd15d 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -106,7 +106,6 @@ module Ci validate_job_types!(name, job) validate_job_stage!(name, job) if job[:stage] - validate_job_artifacts!(name, job) if job[:artifacts] validate_job_dependencies!(name, job) if job[:dependencies] end @@ -142,14 +141,6 @@ module Ci end end - def validate_job_artifacts!(name, job) - job[:artifacts].keys.each do |key| - unless ALLOWED_ARTIFACTS_KEYS.include? key - raise ValidationError, "#{name} job: artifacts unknown parameter #{key}" - end - end - end - def validate_job_dependencies!(name, job) unless validate_array_of_strings(job[:dependencies]) raise ValidationError, "#{name} job: dependencies parameter should be an array of strings" diff --git a/lib/gitlab/ci/config/node/artifacts.rb b/lib/gitlab/ci/config/node/artifacts.rb index 7b3eb7e1992..2c301cf2917 100644 --- a/lib/gitlab/ci/config/node/artifacts.rb +++ b/lib/gitlab/ci/config/node/artifacts.rb @@ -13,6 +13,8 @@ module Gitlab validations do validates :config, type: Hash + validates :config, + allowed_keys: %i[name untracked paths when expire_in] with_options allow_nil: true do validates :name, type: String diff --git a/lib/gitlab/ci/config/node/cache.rb b/lib/gitlab/ci/config/node/cache.rb index cdf8ba2e35d..21d96b220b8 100644 --- a/lib/gitlab/ci/config/node/cache.rb +++ b/lib/gitlab/ci/config/node/cache.rb @@ -8,6 +8,10 @@ module Gitlab class Cache < Entry include Configurable + validations do + validates :config, allowed_keys: %i[key untracked paths] + end + node :key, Node::Key, description: 'Cache key used to define a cache affinity.' @@ -16,10 +20,6 @@ module Gitlab node :paths, Node::Paths, description: 'Specify which paths should be cached across builds.' - - validations do - validates :config, allowed_keys: true - end end end end diff --git a/lib/gitlab/ci/config/node/validator.rb b/lib/gitlab/ci/config/node/validator.rb index ca000f245aa..43c7e102b50 100644 --- a/lib/gitlab/ci/config/node/validator.rb +++ b/lib/gitlab/ci/config/node/validator.rb @@ -21,12 +21,6 @@ module Gitlab 'Validator' end - def unknown_keys - return [] unless config.is_a?(Hash) - - config.keys - @node.class.nodes.keys - end - private def location @@ -35,7 +29,7 @@ module Gitlab end def key_name - if key.blank? || key.nil? + if key.blank? @node.class.name.demodulize.underscore.humanize else key diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb index 5568be80166..b43a0bc0bab 100644 --- a/lib/gitlab/ci/config/node/validators.rb +++ b/lib/gitlab/ci/config/node/validators.rb @@ -5,10 +5,11 @@ module Gitlab module Validators class AllowedKeysValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) - if record.unknown_keys.any? - unknown_list = record.unknown_keys.join(', ') - record.errors.add(:config, - "contains unknown keys: #{unknown_list}") + unknown_keys = record.config.try(:keys).to_a - options[:in] + + if unknown_keys.any? + record.errors.add(:config, 'contains unknown keys: ' + + unknown_keys.join(', ')) end end end diff --git a/spec/lib/gitlab/ci/config/node/artifacts_spec.rb b/spec/lib/gitlab/ci/config/node/artifacts_spec.rb index 4973f7d599a..418a88cabac 100644 --- a/spec/lib/gitlab/ci/config/node/artifacts_spec.rb +++ b/spec/lib/gitlab/ci/config/node/artifacts_spec.rb @@ -21,12 +21,23 @@ describe Gitlab::Ci::Config::Node::Artifacts do end context 'when entry value is not correct' do - let(:config) { { name: 10 } } - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include 'artifacts name should be a string' + context 'when value of attribute is invalid' do + let(:config) { { name: 10 } } + + it 'reports error' do + expect(entry.errors) + .to include 'artifacts name should be a string' + end + end + + context 'when there is uknown key' do + let(:config) { { test: 100 } } + + it 'reports error' do + expect(entry.errors) + .to include 'artifacts config contains unknown keys: test' + end end end end -- cgit v1.2.1 From 6d466733a2ab90f2a4d9904e7bfe1ef887568210 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 18 Jul 2016 15:46:24 +0200 Subject: Validate allowed keys only in new CI config --- lib/ci/gitlab_ci_yaml_processor.rb | 19 ------------------- lib/gitlab/ci/config/node/job.rb | 5 +++++ spec/lib/gitlab/ci/config/node/job_spec.rb | 10 ++++++++++ 3 files changed, 15 insertions(+), 19 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 6901dfbd15d..5c54489ab3f 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -4,15 +4,6 @@ module Ci include Gitlab::Ci::Config::Node::LegacyValidationHelpers - DEFAULT_STAGE = 'test' - ALLOWED_YAML_KEYS = [:before_script, :after_script, :image, :services, :types, :stages, :variables, :cache] - ALLOWED_JOB_KEYS = [:tags, :script, :only, :except, :type, :image, :services, - :allow_failure, :type, :stage, :when, :artifacts, :cache, - :dependencies, :before_script, :after_script, :variables, - :environment] - ALLOWED_CACHE_KEYS = [:key, :untracked, :paths] - ALLOWED_ARTIFACTS_KEYS = [:name, :untracked, :paths, :when, :expire_in] - attr_reader :path, :cache, :stages def initialize(config, path = nil) @@ -102,21 +93,11 @@ module Ci def validate_job!(name, job) raise ValidationError, "Unknown parameter: #{name}" unless job.is_a?(Hash) && job.has_key?(:script) - validate_job_keys!(name, job) validate_job_types!(name, job) - validate_job_stage!(name, job) if job[:stage] validate_job_dependencies!(name, job) if job[:dependencies] end - def validate_job_keys!(name, job) - job.keys.each do |key| - unless (ALLOWED_JOB_KEYS + %i[name]).include? key - raise ValidationError, "#{name} job: unknown parameter #{key}" - end - end - end - def validate_job_types!(name, job) if job[:tags] && !validate_array_of_strings(job[:tags]) raise ValidationError, "#{name} job: tags parameter should be an array of strings" diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index a6d7f16769c..48569a7e890 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -9,6 +9,11 @@ module Gitlab include Configurable validations do + validates :config, allowed_keys: + %i[tags script only except type image services allow_failure + type stage when artifacts cache dependencies before_script + after_script variables environment] + validates :config, presence: true validates :name, presence: true validates :name, type: Symbol diff --git a/spec/lib/gitlab/ci/config/node/job_spec.rb b/spec/lib/gitlab/ci/config/node/job_spec.rb index 2721908c5d7..1484fb60dd8 100644 --- a/spec/lib/gitlab/ci/config/node/job_spec.rb +++ b/spec/lib/gitlab/ci/config/node/job_spec.rb @@ -46,6 +46,16 @@ describe Gitlab::Ci::Config::Node::Job do end end end + + context 'when unknown keys detected' do + let(:config) { { unknown: true } } + + describe '#valid' do + it 'is not valid' do + expect(entry).not_to be_valid + end + end + end end end -- cgit v1.2.1 From 943ae747eac04e19c2614a5b48f41387c6d150d3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 18 Jul 2016 16:33:20 +0200 Subject: Move tags and allow_failure CI entries to new config --- lib/ci/gitlab_ci_yaml_processor.rb | 8 -------- lib/gitlab/ci/config/node/job.rb | 8 ++++++++ spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 5c54489ab3f..3b6fcd909f4 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -99,14 +99,6 @@ module Ci end def validate_job_types!(name, job) - if job[:tags] && !validate_array_of_strings(job[:tags]) - raise ValidationError, "#{name} job: tags parameter should be an array of strings" - end - - if job[:allow_failure] && !validate_boolean(job[:allow_failure]) - raise ValidationError, "#{name} job: allow_failure parameter should be an boolean" - end - if job[:when] && !job[:when].in?(%w[on_success on_failure always]) raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always" end diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 48569a7e890..c0ec2236231 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -7,6 +7,9 @@ module Gitlab # class Job < Entry include Configurable + include Attributable + + attributes :tags, :allow_failure validations do validates :config, allowed_keys: @@ -17,6 +20,11 @@ module Gitlab validates :config, presence: true validates :name, presence: true validates :name, type: Symbol + + with_options allow_nil: true do + validates :tags, array_of_strings: true + validates :allow_failure, boolean: true + end end node :before_script, Script, diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 28849bcf3f4..d354ddb7b13 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -956,7 +956,7 @@ EOT config = YAML.dump({ rspec: { script: "test", tags: "mysql" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: tags parameter should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec tags should be an array of strings") end it "returns errors if before_script parameter is invalid" do @@ -1075,7 +1075,7 @@ EOT config = YAML.dump({ rspec: { script: "test", allow_failure: "string" } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: allow_failure parameter should be an boolean") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec allow failure should be a boolean value") end it "returns errors if job stage is not a string" do -- cgit v1.2.1 From 7450248bae4327fdff2cd62e1c47180dbe23311f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 18 Jul 2016 16:28:37 +0100 Subject: Added tests to dateTime utility getDayName method --- spec/javascripts/datetime_utility_spec.js.coffee | 31 ++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 spec/javascripts/datetime_utility_spec.js.coffee diff --git a/spec/javascripts/datetime_utility_spec.js.coffee b/spec/javascripts/datetime_utility_spec.js.coffee new file mode 100644 index 00000000000..6b9617341fe --- /dev/null +++ b/spec/javascripts/datetime_utility_spec.js.coffee @@ -0,0 +1,31 @@ +#= require lib/utils/datetime_utility + +describe 'Date time utils', -> + describe 'get day name', -> + it 'should return Sunday', -> + day = gl.utils.getDayName(new Date('07/17/2016')) + expect(day).toBe('Sunday') + + it 'should return Monday', -> + day = gl.utils.getDayName(new Date('07/18/2016')) + expect(day).toBe('Monday') + + it 'should return Tuesday', -> + day = gl.utils.getDayName(new Date('07/19/2016')) + expect(day).toBe('Tuesday') + + it 'should return Wednesday', -> + day = gl.utils.getDayName(new Date('07/20/2016')) + expect(day).toBe('Wednesday') + + it 'should return Thursday', -> + day = gl.utils.getDayName(new Date('07/21/2016')) + expect(day).toBe('Thursday') + + it 'should return Friday', -> + day = gl.utils.getDayName(new Date('07/22/2016')) + expect(day).toBe('Friday') + + it 'should return Saturday', -> + day = gl.utils.getDayName(new Date('07/23/2016')) + expect(day).toBe('Saturday') -- cgit v1.2.1 From 5f9857113ec5a00d31cff8a743a7b94a2ae5f586 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 6 Jul 2016 14:48:25 +0100 Subject: Fixed enter submitting form in dropdown Closes #19549 --- app/assets/javascripts/gl_dropdown.js.coffee | 55 ++++++++++++++-------------- app/assets/javascripts/project.js.coffee | 7 +++- 2 files changed, 34 insertions(+), 28 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 1b0d0db8954..bfed7e438c4 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -24,33 +24,34 @@ class GitLabDropdownFilter # Key events timeout = "" - @input.on "keyup", (e) => - keyCode = e.which - - return if ARROW_KEY_CODES.indexOf(keyCode) >= 0 - - if @input.val() isnt "" and !$inputContainer.hasClass HAS_VALUE_CLASS - $inputContainer.addClass HAS_VALUE_CLASS - else if @input.val() is "" and $inputContainer.hasClass HAS_VALUE_CLASS - $inputContainer.removeClass HAS_VALUE_CLASS - - if keyCode is 13 - return false - - # Only filter asynchronously only if option remote is set - if @options.remote - clearTimeout timeout - timeout = setTimeout => - blur_field = @shouldBlur keyCode - - if blur_field and @filterInputBlur - @input.blur() - - @options.query @input.val(), (data) => - @options.callback(data) - , 250 - else - @filter @input.val() + @input + .on 'keydown', (e) -> + keyCode = e.which + e.preventDefault() if keyCode is 13 + .on 'keyup', (e) => + keyCode = e.which + + return if ARROW_KEY_CODES.indexOf(keyCode) >= 0 + + if @input.val() isnt '' and !$inputContainer.hasClass HAS_VALUE_CLASS + $inputContainer.addClass HAS_VALUE_CLASS + else if @input.val() is '' and $inputContainer.hasClass HAS_VALUE_CLASS + $inputContainer.removeClass HAS_VALUE_CLASS + + # Only filter asynchronously only if option remote is set + if @options.remote + clearTimeout timeout + timeout = setTimeout => + blur_field = @shouldBlur keyCode + + if blur_field and @filterInputBlur + @input.blur() + + @options.query @input.val(), (data) => + @options.callback(data) + , 250 + else + @filter @input.val() shouldBlur: (keyCode) -> return BLUR_KEYCODES.indexOf(keyCode) >= 0 diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index 3288c801388..80ea179948d 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -87,5 +87,10 @@ class @Project toggleLabel: (obj, $el) -> $el.text().trim() clicked: (e) -> - $dropdown.closest('form').submit() + if $('input[name="ref"]').length + $form = $dropdown.closest('form') + action = $form.attr('action') + divider = if action.indexOf('?') < 0 then '?' else '&' + + Turbolinks.visit "#{action}#{divider}#{$form.serialize()}" ) -- cgit v1.2.1 From f91cfa8ea48ab975c9f33085c6b13ae1b27c6025 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 14 Jul 2016 09:07:37 +0100 Subject: Fixed navigational keys not working when filtering --- app/assets/javascripts/gl_dropdown.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index bfed7e438c4..3688d049a83 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -554,7 +554,7 @@ class GitLabDropdown ARROW_KEY_CODES = [38, 40] $input = @dropdown.find(".dropdown-input-field") - selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator)' + selector = '.dropdown-content li:not(.divider,.dropdown-header,.separator):visible' if @dropdown.find(".dropdown-toggle-page").length selector = ".dropdown-page-one #{selector}" -- cgit v1.2.1 From 1dc706dbd57b3d63e02bbe04c9a1a152ef9af34f Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 18 Jul 2016 16:43:13 -0500 Subject: Improve conditional --- app/assets/javascripts/gl_dropdown.js.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index 3688d049a83..bd814dd15a2 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -31,7 +31,7 @@ class GitLabDropdownFilter .on 'keyup', (e) => keyCode = e.which - return if ARROW_KEY_CODES.indexOf(keyCode) >= 0 + return unless ARROW_KEY_CODES.indexOf(keyCode) is -1 if @input.val() isnt '' and !$inputContainer.hasClass HAS_VALUE_CLASS $inputContainer.addClass HAS_VALUE_CLASS -- cgit v1.2.1 From 5d7b2bd44e08414addde7eb700e5ccf21f59f94c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 19 Jul 2016 08:29:57 +0100 Subject: Prevent default on link click in ref switcher --- app/assets/javascripts/project.js.coffee | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index 80ea179948d..2a8e8f2552b 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -86,7 +86,8 @@ class @Project $el.attr('data-ref') toggleLabel: (obj, $el) -> $el.text().trim() - clicked: (e) -> + clicked: (selected, $el, e) -> + e.preventDefault() if $('input[name="ref"]').length $form = $dropdown.closest('form') action = $form.attr('action') -- cgit v1.2.1 From bb8bf6427d80cb4858318a44e395a2d1cd9115b7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Jul 2016 13:08:28 +0200 Subject: Move job environment validation to new CI config --- lib/ci/gitlab_ci_yaml_processor.rb | 11 ----------- lib/gitlab/ci/config/node/job.rb | 14 +++++++++++++- lib/gitlab/ci/config/node/legacy_validation_helpers.rb | 4 ---- lib/gitlab/ci/config/node/validators.rb | 3 ++- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 6 +++--- 5 files changed, 18 insertions(+), 20 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 3b6fcd909f4..aa2f7743a5e 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -93,21 +93,10 @@ module Ci def validate_job!(name, job) raise ValidationError, "Unknown parameter: #{name}" unless job.is_a?(Hash) && job.has_key?(:script) - validate_job_types!(name, job) validate_job_stage!(name, job) if job[:stage] validate_job_dependencies!(name, job) if job[:dependencies] end - def validate_job_types!(name, job) - if job[:when] && !job[:when].in?(%w[on_success on_failure always]) - raise ValidationError, "#{name} job: when parameter should be on_success, on_failure or always" - end - - if job[:environment] && !validate_environment(job[:environment]) - raise ValidationError, "#{name} job: environment parameter #{Gitlab::Regex.environment_name_regex_message}" - end - end - def validate_job_stage!(name, job) unless job[:stage].is_a?(String) && job[:stage].in?(@stages) raise ValidationError, "#{name} job: stage parameter should be #{@stages.join(", ")}" diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index c0ec2236231..464abd4d2d8 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -9,7 +9,7 @@ module Gitlab include Configurable include Attributable - attributes :tags, :allow_failure + attributes :tags, :allow_failure, :when, :environment validations do validates :config, allowed_keys: @@ -24,6 +24,18 @@ module Gitlab with_options allow_nil: true do validates :tags, array_of_strings: true validates :allow_failure, boolean: true + validates :when, + inclusion: { in: %w[on_success on_failure always], + message: 'should be on_success, on_failure ' \ + 'or always' } + validates :environment, + type: { + with: String, + message: Gitlab::Regex.environment_name_regex_message } + validates :environment, + format: { + with: Gitlab::Regex.environment_name_regex, + message: Gitlab::Regex.environment_name_regex_message } end end diff --git a/lib/gitlab/ci/config/node/legacy_validation_helpers.rb b/lib/gitlab/ci/config/node/legacy_validation_helpers.rb index 4d9a508796a..0c291efe6a5 100644 --- a/lib/gitlab/ci/config/node/legacy_validation_helpers.rb +++ b/lib/gitlab/ci/config/node/legacy_validation_helpers.rb @@ -41,10 +41,6 @@ module Gitlab false end - def validate_environment(value) - value.is_a?(String) && value =~ Gitlab::Regex.environment_name_regex - end - def validate_boolean(value) value.in?([true, false]) end diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb index b43a0bc0bab..23d5faf6f07 100644 --- a/lib/gitlab/ci/config/node/validators.rb +++ b/lib/gitlab/ci/config/node/validators.rb @@ -69,7 +69,8 @@ module Gitlab raise unless type.is_a?(Class) unless value.is_a?(type) - record.errors.add(attribute, "should be a #{type.name}") + message = options[:message] || "should be a #{type.name}" + record.errors.add(attribute, message) end end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index d354ddb7b13..9735af27aa1 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -754,7 +754,7 @@ module Ci let(:environment) { 1 } it 'raises error' do - expect { builds }.to raise_error("deploy_to_production job: environment parameter #{Gitlab::Regex.environment_name_regex_message}") + expect { builds }.to raise_error("jobs:deploy_to_production environment #{Gitlab::Regex.environment_name_regex_message}") end end @@ -762,7 +762,7 @@ module Ci let(:environment) { 'production staging' } it 'raises error' do - expect { builds }.to raise_error("deploy_to_production job: environment parameter #{Gitlab::Regex.environment_name_regex_message}") + expect { builds }.to raise_error("jobs:deploy_to_production environment #{Gitlab::Regex.environment_name_regex_message}") end end end @@ -1131,7 +1131,7 @@ EOT config = YAML.dump({ rspec: { script: "test", when: 1 } }) expect do GitlabCiYamlProcessor.new(config, path) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: when parameter should be on_success, on_failure or always") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec when should be on_success, on_failure or always") end it "returns errors if job artifacts:name is not an a string" do -- cgit v1.2.1 From c0ebfea6baf330ed5ae5bc870407fe47f76119cf Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 30 Jun 2016 14:41:19 +0200 Subject: Fix some useless access modifiers in the code --- app/controllers/import/bitbucket_controller.rb | 2 -- app/controllers/import/gitlab_controller.rb | 2 -- app/helpers/diff_helper.rb | 2 -- app/models/concerns/token_authenticatable.rb | 28 +++++++++++++------------- 4 files changed, 14 insertions(+), 20 deletions(-) diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb index 25e58724860..944c73d139a 100644 --- a/app/controllers/import/bitbucket_controller.rb +++ b/app/controllers/import/bitbucket_controller.rb @@ -82,8 +82,6 @@ class Import::BitbucketController < Import::BaseController go_to_bitbucket_for_permissions end - private - def access_params { bitbucket_access_token: session[:bitbucket_access_token], diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb index 23a396e8084..08130ee8176 100644 --- a/app/controllers/import/gitlab_controller.rb +++ b/app/controllers/import/gitlab_controller.rb @@ -61,8 +61,6 @@ class Import::GitlabController < Import::BaseController go_to_gitlab_for_permissions end - private - def access_params { gitlab_access_token: session[:gitlab_access_token] } end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 75b029365f9..05366d06b1c 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -142,8 +142,6 @@ module DiffHelper toggle_whitespace_link(url, options) end - private - def hide_whitespace? params[:w] == '1' end diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb index 885deaf78d2..27a03b3bada 100644 --- a/app/models/concerns/token_authenticatable.rb +++ b/app/models/concerns/token_authenticatable.rb @@ -1,6 +1,20 @@ module TokenAuthenticatable extend ActiveSupport::Concern + private + + def write_new_token(token_field) + new_token = generate_token(token_field) + write_attribute(token_field, new_token) + end + + def generate_token(token_field) + loop do + token = Devise.friendly_token + break token unless self.class.unscoped.find_by(token_field => token) + end + end + class_methods do def authentication_token_fields @token_fields || [] @@ -32,18 +46,4 @@ module TokenAuthenticatable end end end - - private - - def write_new_token(token_field) - new_token = generate_token(token_field) - write_attribute(token_field, new_token) - end - - def generate_token(token_field) - loop do - token = Devise.friendly_token - break token unless self.class.unscoped.find_by(token_field => token) - end - end end -- cgit v1.2.1 From 1cf164f14a09fcb6322c33f62e80496568f83d8e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Jul 2016 14:17:47 +0200 Subject: Fix private method visibility in container registry --- .../auth/container_registry_authentication_service.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb index e294a962352..6072123b851 100644 --- a/app/services/auth/container_registry_authentication_service.rb +++ b/app/services/auth/container_registry_authentication_service.rb @@ -24,10 +24,14 @@ module Auth token[:access] = names.map do |name| { type: 'repository', name: name, actions: %w(*) } end - + token.encoded end + def self.token_expire_at + Time.now + current_application_settings.container_registry_token_expire_delay.minutes + end + private def authorized_token(*accesses) @@ -35,7 +39,7 @@ module Auth token.issuer = registry.issuer token.audience = params[:service] token.subject = current_user.try(:username) - token.expire_time = ContainerRegistryAuthenticationService.token_expire_at + token.expire_time = self.class.token_expire_at token[:access] = accesses.compact token end @@ -81,9 +85,5 @@ module Auth def registry Gitlab.config.registry end - - def self.token_expire_at - Time.now + current_application_settings.container_registry_token_expire_delay.minutes - end end end -- cgit v1.2.1 From e9c09c92a7b56c40a06600a8856b4d4eaf15008e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Jul 2016 14:18:51 +0200 Subject: Refactor system notes service to make it singleton --- app/services/system_note_service.rb | 154 ++++++++++++++++++------------------ 1 file changed, 78 insertions(+), 76 deletions(-) diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 1ab3b5789bc..e13dc9265b8 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -2,7 +2,9 @@ # # Used for creating system notes (e.g., when a user references a merge request # from an issue, an issue's assignee changes, an issue is closed, etc.) -class SystemNoteService +module SystemNoteService + extend self + # Called when commits are added to a Merge Request # # noteable - Noteable object @@ -15,7 +17,7 @@ class SystemNoteService # See new_commit_summary and existing_commit_summary. # # Returns the created Note object - def self.add_commits(noteable, project, author, new_commits, existing_commits = [], oldrev = nil) + def add_commits(noteable, project, author, new_commits, existing_commits = [], oldrev = nil) total_count = new_commits.length + existing_commits.length commits_text = "#{total_count} commit".pluralize(total_count) @@ -40,7 +42,7 @@ class SystemNoteService # "Reassigned to @rspeicher" # # Returns the created Note object - def self.change_assignee(noteable, project, author, assignee) + def change_assignee(noteable, project, author, assignee) body = assignee.nil? ? 'Assignee removed' : "Reassigned to #{assignee.to_reference}" create_note(noteable: noteable, project: project, author: author, note: body) @@ -63,7 +65,7 @@ class SystemNoteService # "Removed ~5 label" # # Returns the created Note object - def self.change_label(noteable, project, author, added_labels, removed_labels) + def change_label(noteable, project, author, added_labels, removed_labels) labels_count = added_labels.count + removed_labels.count references = ->(label) { label.to_reference(format: :id) } @@ -101,7 +103,7 @@ class SystemNoteService # "Miletone changed to 7.11" # # Returns the created Note object - def self.change_milestone(noteable, project, author, milestone) + def change_milestone(noteable, project, author, milestone) body = 'Milestone ' body += milestone.nil? ? 'removed' : "changed to #{milestone.to_reference(project)}" @@ -123,7 +125,7 @@ class SystemNoteService # "Status changed to closed by bc17db76" # # Returns the created Note object - def self.change_status(noteable, project, author, status, source) + def change_status(noteable, project, author, status, source) body = "Status changed to #{status}" body << " by #{source.gfm_reference(project)}" if source @@ -131,26 +133,26 @@ class SystemNoteService end # Called when 'merge when build succeeds' is executed - def self.merge_when_build_succeeds(noteable, project, author, last_commit) + def merge_when_build_succeeds(noteable, project, author, last_commit) body = "Enabled an automatic merge when the build for #{last_commit.to_reference(project)} succeeds" create_note(noteable: noteable, project: project, author: author, note: body) end # Called when 'merge when build succeeds' is canceled - def self.cancel_merge_when_build_succeeds(noteable, project, author) + def cancel_merge_when_build_succeeds(noteable, project, author) body = 'Canceled the automatic merge' create_note(noteable: noteable, project: project, author: author, note: body) end - def self.remove_merge_request_wip(noteable, project, author) + def remove_merge_request_wip(noteable, project, author) body = 'Unmarked this merge request as a Work In Progress' create_note(noteable: noteable, project: project, author: author, note: body) end - def self.add_merge_request_wip(noteable, project, author) + def add_merge_request_wip(noteable, project, author) body = 'Marked this merge request as a **Work In Progress**' create_note(noteable: noteable, project: project, author: author, note: body) @@ -168,7 +170,7 @@ class SystemNoteService # "Title changed from **Old** to **New**" # # Returns the created Note object - def self.change_title(noteable, project, author, old_title) + def change_title(noteable, project, author, old_title) new_title = noteable.title.dup old_diffs, new_diffs = Gitlab::Diff::InlineDiff.new(old_title, new_title).inline_diffs @@ -191,7 +193,7 @@ class SystemNoteService # "Made the issue confidential" # # Returns the created Note object - def self.change_issue_confidentiality(issue, project, author) + def change_issue_confidentiality(issue, project, author) body = issue.confidential ? 'Made the issue confidential' : 'Made the issue visible' create_note(noteable: issue, project: project, author: author, note: body) end @@ -210,7 +212,7 @@ class SystemNoteService # "Target branch changed from `Old` to `New`" # # Returns the created Note object - def self.change_branch(noteable, project, author, branch_type, old_branch, new_branch) + def change_branch(noteable, project, author, branch_type, old_branch, new_branch) body = "#{branch_type} branch changed from `#{old_branch}` to `#{new_branch}`".capitalize create_note(noteable: noteable, project: project, author: author, note: body) end @@ -229,7 +231,7 @@ class SystemNoteService # "Restored target branch `feature`" # # Returns the created Note object - def self.change_branch_presence(noteable, project, author, branch_type, branch, presence) + def change_branch_presence(noteable, project, author, branch_type, branch, presence) verb = if presence == :add 'restored' @@ -245,7 +247,7 @@ class SystemNoteService # Example note text: # # "Started branch `201-issue-branch-button`" - def self.new_issue_branch(issue, project, author, branch) + def new_issue_branch(issue, project, author, branch) h = Gitlab::Routing.url_helpers link = h.namespace_project_compare_url(project.namespace, project, from: project.default_branch, to: branch) @@ -270,7 +272,7 @@ class SystemNoteService # See cross_reference_note_content. # # Returns the created Note object - def self.cross_reference(noteable, mentioner, author) + def cross_reference(noteable, mentioner, author) return if cross_reference_disallowed?(noteable, mentioner) gfm_reference = mentioner.gfm_reference(noteable.project) @@ -294,7 +296,7 @@ class SystemNoteService end end - def self.cross_reference?(note_text) + def cross_reference?(note_text) note_text.start_with?(cross_reference_note_prefix) end @@ -308,7 +310,7 @@ class SystemNoteService # mentioner - Mentionable object # # Returns Boolean - def self.cross_reference_disallowed?(noteable, mentioner) + def cross_reference_disallowed?(noteable, mentioner) return true if noteable.is_a?(ExternalIssue) && !noteable.project.jira_tracker_active? return false unless mentioner.is_a?(MergeRequest) return false unless noteable.is_a?(Commit) @@ -328,7 +330,7 @@ class SystemNoteService # # Returns Boolean - def self.cross_reference_exists?(noteable, mentioner) + def cross_reference_exists?(noteable, mentioner) # Initial scope should be system notes of this noteable type notes = Note.system.where(noteable_type: noteable.class) @@ -342,9 +344,60 @@ class SystemNoteService notes_for_mentioner(mentioner, noteable, notes).count > 0 end + # Build an Array of lines detailing each commit added in a merge request + # + # new_commits - Array of new Commit objects + # + # Returns an Array of Strings + def new_commit_summary(new_commits) + new_commits.collect do |commit| + "* #{commit.short_id} - #{escape_html(commit.title)}" + end + end + + # Called when the status of a Task has changed + # + # noteable - Noteable object. + # project - Project owning noteable + # author - User performing the change + # new_task - TaskList::Item object. + # + # Example Note text: + # + # "Soandso marked the task Whatever as completed." + # + # Returns the created Note object + def change_task_status(noteable, project, author, new_task) + status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE + body = "Marked the task **#{new_task.source}** as #{status_label}" + create_note(noteable: noteable, project: project, author: author, note: body) + end + + # Called when noteable has been moved to another project + # + # direction - symbol, :to or :from + # noteable - Noteable object + # noteable_ref - Referenced noteable + # author - User performing the move + # + # Example Note text: + # + # "Moved to some_namespace/project_new#11" + # + # Returns the created Note object + def noteable_moved(noteable, project, noteable_ref, author, direction:) + unless [:to, :from].include?(direction) + raise ArgumentError, "Invalid direction `#{direction}`" + end + + cross_reference = noteable_ref.to_reference(project) + body = "Moved #{direction} #{cross_reference}" + create_note(noteable: noteable, project: project, author: author, note: body) + end + private - def self.notes_for_mentioner(mentioner, noteable, notes) + def notes_for_mentioner(mentioner, noteable, notes) if mentioner.is_a?(Commit) notes.where('note LIKE ?', "#{cross_reference_note_prefix}%#{mentioner.to_reference(nil)}") else @@ -353,29 +406,18 @@ class SystemNoteService end end - def self.create_note(args = {}) + def create_note(args = {}) Note.create(args.merge(system: true)) end - def self.cross_reference_note_prefix + def cross_reference_note_prefix 'mentioned in ' end - def self.cross_reference_note_content(gfm_reference) + def cross_reference_note_content(gfm_reference) "#{cross_reference_note_prefix}#{gfm_reference}" end - # Build an Array of lines detailing each commit added in a merge request - # - # new_commits - Array of new Commit objects - # - # Returns an Array of Strings - def self.new_commit_summary(new_commits) - new_commits.collect do |commit| - "* #{commit.short_id} - #{escape_html(commit.title)}" - end - end - # Build a single line summarizing existing commits being added in a merge # request # @@ -392,7 +434,7 @@ class SystemNoteService # "* ea0f8418 - 1 commit from branch `feature`" # # Returns a newline-terminated String - def self.existing_commit_summary(noteable, existing_commits, oldrev = nil) + def existing_commit_summary(noteable, existing_commits, oldrev = nil) return '' if existing_commits.empty? count = existing_commits.size @@ -415,47 +457,7 @@ class SystemNoteService "* #{commit_ids} - #{commits_text} from branch `#{branch}`\n" end - # Called when the status of a Task has changed - # - # noteable - Noteable object. - # project - Project owning noteable - # author - User performing the change - # new_task - TaskList::Item object. - # - # Example Note text: - # - # "Soandso marked the task Whatever as completed." - # - # Returns the created Note object - def self.change_task_status(noteable, project, author, new_task) - status_label = new_task.complete? ? Taskable::COMPLETED : Taskable::INCOMPLETE - body = "Marked the task **#{new_task.source}** as #{status_label}" - create_note(noteable: noteable, project: project, author: author, note: body) - end - - # Called when noteable has been moved to another project - # - # direction - symbol, :to or :from - # noteable - Noteable object - # noteable_ref - Referenced noteable - # author - User performing the move - # - # Example Note text: - # - # "Moved to some_namespace/project_new#11" - # - # Returns the created Note object - def self.noteable_moved(noteable, project, noteable_ref, author, direction:) - unless [:to, :from].include?(direction) - raise ArgumentError, "Invalid direction `#{direction}`" - end - - cross_reference = noteable_ref.to_reference(project) - body = "Moved #{direction} #{cross_reference}" - create_note(noteable: noteable, project: project, author: author, note: body) - end - - def self.escape_html(text) + def escape_html(text) Rack::Utils.escape_html(text) end end -- cgit v1.2.1 From 239d8ab30c660167bd42ff745df3568b16ef82b3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Jul 2016 14:19:54 +0200 Subject: Refactor gitlab themes module to make it singleton --- lib/gitlab/themes.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/gitlab/themes.rb b/lib/gitlab/themes.rb index 83f91de810c..d4020af76f9 100644 --- a/lib/gitlab/themes.rb +++ b/lib/gitlab/themes.rb @@ -2,6 +2,8 @@ module Gitlab # Module containing GitLab's application theme definitions and helper methods # for accessing them. module Themes + extend self + # Theme ID used when no `default_theme` configuration setting is provided. APPLICATION_DEFAULT = 2 @@ -22,7 +24,7 @@ module Gitlab # classes that might be applied to the `body` element # # Returns a String - def self.body_classes + def body_classes THEMES.collect(&:css_class).uniq.join(' ') end @@ -33,26 +35,26 @@ module Gitlab # id - Integer ID # # Returns a Theme - def self.by_id(id) + def by_id(id) THEMES.detect { |t| t.id == id } || default end # Returns the number of defined Themes - def self.count + def count THEMES.size end # Get the default Theme # # Returns a Theme - def self.default + def default by_id(default_id) end # Iterate through each Theme # # Yields the Theme object - def self.each(&block) + def each(&block) THEMES.each(&block) end @@ -61,7 +63,7 @@ module Gitlab # user - User record # # Returns a Theme - def self.for_user(user) + def for_user(user) if user by_id(user.theme_id) else @@ -71,7 +73,7 @@ module Gitlab private - def self.default_id + def default_id id = Gitlab.config.gitlab.default_theme.to_i # Prevent an invalid configuration setting from causing an infinite loop -- cgit v1.2.1 From e15b63b34eba510ba14258f1719ecec2f90b1b49 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Jul 2016 14:21:39 +0200 Subject: Fix methods visibility in gitlab database module --- lib/gitlab/database.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/database.rb b/lib/gitlab/database.rb index 078609c86f1..55b8f888d53 100644 --- a/lib/gitlab/database.rb +++ b/lib/gitlab/database.rb @@ -55,12 +55,12 @@ module Gitlab end end - private - def self.connection ActiveRecord::Base.connection end + private_class_method :connection + def self.database_version row = connection.execute("SELECT VERSION()").first @@ -70,5 +70,7 @@ module Gitlab row.first end end + + private_class_method :database_version end end -- cgit v1.2.1 From 20a5033d79ba8375a79d7d5d8781b53be891ba42 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Jul 2016 14:43:11 +0200 Subject: Fix method visibility in inline diff class --- lib/gitlab/diff/inline_diff.rb | 74 ++++++++++++++++++++++-------------------- 1 file changed, 39 insertions(+), 35 deletions(-) diff --git a/lib/gitlab/diff/inline_diff.rb b/lib/gitlab/diff/inline_diff.rb index 28ad637fda4..55708d42161 100644 --- a/lib/gitlab/diff/inline_diff.rb +++ b/lib/gitlab/diff/inline_diff.rb @@ -19,24 +19,6 @@ module Gitlab attr_accessor :old_line, :new_line, :offset - def self.for_lines(lines) - changed_line_pairs = self.find_changed_line_pairs(lines) - - inline_diffs = [] - - changed_line_pairs.each do |old_index, new_index| - old_line = lines[old_index] - new_line = lines[new_index] - - old_diffs, new_diffs = new(old_line, new_line, offset: 1).inline_diffs - - inline_diffs[old_index] = old_diffs - inline_diffs[new_index] = new_diffs - end - - inline_diffs - end - def initialize(old_line, new_line, offset: 0) @old_line = old_line[offset..-1] @new_line = new_line[offset..-1] @@ -63,32 +45,54 @@ module Gitlab [old_diffs, new_diffs] end - private + class << self + def for_lines(lines) + changed_line_pairs = find_changed_line_pairs(lines) - # Finds pairs of old/new line pairs that represent the same line that changed - def self.find_changed_line_pairs(lines) - # Prefixes of all diff lines, indicating their types - # For example: `" - + -+ ---+++ --+ -++"` - line_prefixes = lines.each_with_object("") { |line, s| s << line[0] }.gsub(/[^ +-]/, ' ') + inline_diffs = [] - changed_line_pairs = [] - line_prefixes.scan(LINE_PAIRS_PATTERN) do - # For `"---+++"`, `begin_index == 0`, `end_index == 6` - begin_index, end_index = Regexp.last_match.offset(:del_ins) + changed_line_pairs.each do |old_index, new_index| + old_line = lines[old_index] + new_line = lines[new_index] - # For `"---+++"`, `changed_line_count == 3` - changed_line_count = (end_index - begin_index) / 2 + old_diffs, new_diffs = new(old_line, new_line, offset: 1).inline_diffs - halfway_index = begin_index + changed_line_count - (begin_index...halfway_index).each do |i| - # For `"---+++"`, index 1 maps to 1 + 3 = 4 - changed_line_pairs << [i, i + changed_line_count] + inline_diffs[old_index] = old_diffs + inline_diffs[new_index] = new_diffs end + + inline_diffs end - changed_line_pairs + private + + # Finds pairs of old/new line pairs that represent the same line that changed + def find_changed_line_pairs(lines) + # Prefixes of all diff lines, indicating their types + # For example: `" - + -+ ---+++ --+ -++"` + line_prefixes = lines.each_with_object("") { |line, s| s << line[0] }.gsub(/[^ +-]/, ' ') + + changed_line_pairs = [] + line_prefixes.scan(LINE_PAIRS_PATTERN) do + # For `"---+++"`, `begin_index == 0`, `end_index == 6` + begin_index, end_index = Regexp.last_match.offset(:del_ins) + + # For `"---+++"`, `changed_line_count == 3` + changed_line_count = (end_index - begin_index) / 2 + + halfway_index = begin_index + changed_line_count + (begin_index...halfway_index).each do |i| + # For `"---+++"`, index 1 maps to 1 + 3 = 4 + changed_line_pairs << [i, i + changed_line_count] + end + end + + changed_line_pairs + end end + private + def longest_common_prefix(a, b) max_length = [a.length, b.length].max -- cgit v1.2.1 From f281041fea2884ea666d7de701c8e824078b86cc Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Jul 2016 15:05:22 +0200 Subject: Make banzai module that handles markdown singleton --- lib/banzai/renderer.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb index 910687a7b6a..a4ae27eefd8 100644 --- a/lib/banzai/renderer.rb +++ b/lib/banzai/renderer.rb @@ -1,5 +1,7 @@ module Banzai module Renderer + extend self + # Convert a Markdown String into an HTML-safe String of HTML # # Note that while the returned HTML will have been sanitized of dangerous @@ -14,7 +16,7 @@ module Banzai # context - Hash of context options passed to our HTML Pipeline # # Returns an HTML-safe String - def self.render(text, context = {}) + def render(text, context = {}) cache_key = context.delete(:cache_key) cache_key = full_cache_key(cache_key, context[:pipeline]) @@ -52,7 +54,7 @@ module Banzai # texts_and_contexts # => [{ text: '### Hello', # context: { cache_key: [note, :note] } }] - def self.cache_collection_render(texts_and_contexts) + def cache_collection_render(texts_and_contexts) items_collection = texts_and_contexts.each_with_index do |item, index| context = item[:context] cache_key = full_cache_multi_key(context.delete(:cache_key), context[:pipeline]) @@ -81,7 +83,7 @@ module Banzai items_collection.map { |item| item[:rendered] } end - def self.render_result(text, context = {}) + def render_result(text, context = {}) text = Pipeline[:pre_process].to_html(text, context) if text Pipeline[context[:pipeline]].call(text, context) @@ -100,7 +102,7 @@ module Banzai # :user - User object # # Returns an HTML-safe String - def self.post_process(html, context) + def post_process(html, context) context = Pipeline[context[:pipeline]].transform_context(context) pipeline = Pipeline[:post_process] @@ -113,7 +115,7 @@ module Banzai private - def self.cacheless_render(text, context = {}) + def cacheless_render(text, context = {}) Gitlab::Metrics.measure(:banzai_cacheless_render) do result = render_result(text, context) @@ -126,7 +128,7 @@ module Banzai end end - def self.full_cache_key(cache_key, pipeline_name) + def full_cache_key(cache_key, pipeline_name) return unless cache_key ["banzai", *cache_key, pipeline_name || :full] end @@ -134,7 +136,7 @@ module Banzai # To map Rails.cache.read_multi results we need to know the Rails.cache.expanded_key. # Other option will be to generate stringified keys on our side and don't delegate to Rails.cache.expanded_key # method. - def self.full_cache_multi_key(cache_key, pipeline_name) + def full_cache_multi_key(cache_key, pipeline_name) return unless cache_key Rails.cache.send(:expanded_key, full_cache_key(cache_key, pipeline_name)) end -- cgit v1.2.1 From 4d9b34bb7e8eddef4db3e664356b19e121ea381c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Jul 2016 15:05:46 +0200 Subject: Fix method visibility in gitlab metrics class --- lib/gitlab/metrics.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 49f702f91f6..f2758b5c44a 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -136,10 +136,10 @@ module Gitlab end end - private - def self.current_transaction Transaction.current end + + private_class_method :current_transaction end end -- cgit v1.2.1 From 3d2b6a3d7b51b8caf5a9133b6c0f0a6da3ab6d9d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Jul 2016 15:06:16 +0200 Subject: Fix methods visibility in markdown filter class --- lib/banzai/filter/markdown_filter.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/banzai/filter/markdown_filter.rb b/lib/banzai/filter/markdown_filter.rb index 9b209533a89..ff580ec68f8 100644 --- a/lib/banzai/filter/markdown_filter.rb +++ b/lib/banzai/filter/markdown_filter.rb @@ -12,7 +12,12 @@ module Banzai html end - private + def self.renderer + @renderer ||= begin + renderer = Redcarpet::Render::HTML.new + Redcarpet::Markdown.new(renderer, redcarpet_options) + end + end def self.redcarpet_options # https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use @@ -28,12 +33,7 @@ module Banzai }.freeze end - def self.renderer - @renderer ||= begin - renderer = Redcarpet::Render::HTML.new - Redcarpet::Markdown.new(renderer, redcarpet_options) - end - end + private_class_method :redcarpet_options end end end -- cgit v1.2.1 From b6d3eadfe23997770ca260460c0e22fed6859d45 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Jul 2016 15:06:39 +0200 Subject: Fix method visiblity in emoji filter class --- lib/banzai/filter/emoji_filter.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb index ae7d31cf191..2492b5213ac 100644 --- a/lib/banzai/filter/emoji_filter.rb +++ b/lib/banzai/filter/emoji_filter.rb @@ -38,6 +38,11 @@ module Banzai end end + # Build a regexp that matches all valid :emoji: names. + def self.emoji_pattern + @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/ + end + private def emoji_url(name) @@ -59,11 +64,6 @@ module Banzai ActionController::Base.helpers.url_to_image(image) end - # Build a regexp that matches all valid :emoji: names. - def self.emoji_pattern - @emoji_pattern ||= /:(#{Gitlab::Emoji.emojis_names.map { |name| Regexp.escape(name) }.join('|')}):/ - end - def emoji_pattern self.class.emoji_pattern end -- cgit v1.2.1 From d6f669774481b160c2d963b56309ab6262216c42 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 19 Jul 2016 15:07:52 +0200 Subject: Enable Rubocop cops for invalid access modifiers This enables following cops: Check for useless access modifiers Lint/UselessAccessModifier Checks for attempts to use `private` or `protected` to set the visibility of a class method, which does not work. Lint/IneffectiveAccessModifier This also disables two false possitives in concerns. --- .rubocop.yml | 9 +++++++++ .rubocop_todo.yml | 8 -------- app/models/concerns/token_authenticatable.rb | 2 +- lib/gitlab/ci/config/node/configurable.rb | 2 +- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index db0bcfadcf4..3f3a0561710 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -506,6 +506,15 @@ Metrics/PerceivedComplexity: #################### Lint ################################ +# Checks for useless access modifiers. +Lint/UselessAccessModifier: + Enabled: true + +# Checks for attempts to use `private` or `protected` to set the visibility +# of a class method, which does not work. +Lint/IneffectiveAccessModifier: + Enabled: false + # Checks for ambiguous operators in the first argument of a method invocation # without parentheses. Lint/AmbiguousOperator: diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 9310e711889..05a5fae8543 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -19,10 +19,6 @@ Lint/AssignmentInCondition: Lint/HandleExceptions: Enabled: false -# Offense count: 21 -Lint/IneffectiveAccessModifier: - Enabled: false - # Offense count: 2 Lint/Loop: Enabled: false @@ -48,10 +44,6 @@ Lint/UnusedBlockArgument: Lint/UnusedMethodArgument: Enabled: false -# Offense count: 11 -Lint/UselessAccessModifier: - Enabled: false - # Offense count: 12 # Cop supports --auto-correct. Performance/PushSplat: diff --git a/app/models/concerns/token_authenticatable.rb b/app/models/concerns/token_authenticatable.rb index 27a03b3bada..24c7b26d223 100644 --- a/app/models/concerns/token_authenticatable.rb +++ b/app/models/concerns/token_authenticatable.rb @@ -20,7 +20,7 @@ module TokenAuthenticatable @token_fields || [] end - private + private # rubocop:disable Lint/UselessAccessModifier def add_authentication_token_field(token_field) @token_fields = [] unless @token_fields diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 37936fc8242..2592e1ec244 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -36,7 +36,7 @@ module Gitlab Hash[(@nodes || {}).map { |key, factory| [key, factory.dup] }] end - private + private # rubocop:disable Lint/UselessAccessModifier def node(symbol, entry_class, metadata) factory = Node::Factory.new(entry_class) -- cgit v1.2.1 From 896b9c1dc80af349bab9681bb0c668de65151572 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 19 Jul 2016 19:52:40 +0100 Subject: Fixed issue with filtering & pressing enter key --- app/assets/javascripts/gl_dropdown.js.coffee | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js.coffee b/app/assets/javascripts/gl_dropdown.js.coffee index bd814dd15a2..ab41caa28f8 100644 --- a/app/assets/javascripts/gl_dropdown.js.coffee +++ b/app/assets/javascripts/gl_dropdown.js.coffee @@ -311,6 +311,7 @@ class GitLabDropdown return true opened: => + currentIndex = -1 @addArrowKeyEvent() if @options.setIndeterminateIds @@ -568,9 +569,6 @@ class GitLabDropdown PREV_INDEX = currentIndex $listItems = $(selector, @dropdown) - # if @options.filterable - # $input.blur() - if currentKeyCode is 40 # Move down currentIndex += 1 if currentIndex < ($listItems.length - 1) @@ -583,7 +581,7 @@ class GitLabDropdown return false if currentKeyCode is 13 and currentIndex isnt -1 - @selectRowAtIndex e, currentIndex + @selectRowAtIndex e, $('.is-focused', @dropdown).closest('li').index() - 1 removeArrayKeyEvent: -> $('body').off 'keydown' -- cgit v1.2.1 From e5b64f20c730bd6e18af694b2c1503020ba1db51 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 19 Jul 2016 20:26:41 +0100 Subject: Added ref switcher enter key tests --- spec/features/projects/ref_switcher_spec.rb | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 spec/features/projects/ref_switcher_spec.rb diff --git a/spec/features/projects/ref_switcher_spec.rb b/spec/features/projects/ref_switcher_spec.rb new file mode 100644 index 00000000000..b3ba40b35af --- /dev/null +++ b/spec/features/projects/ref_switcher_spec.rb @@ -0,0 +1,29 @@ +require 'rails_helper' + +feature 'Ref switcher', feature: true, js: true do + include WaitForAjax + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + + before do + project.team << [user, :master] + login_as(user) + visit namespace_project_tree_path(project.namespace, project, 'master') + end + + it 'allow user to change ref by enter key' do + click_button 'master' + wait_for_ajax + + page.within '.project-refs-form' do + input = find('input[type="search"]') + input.set 'expand' + + input.native.send_keys :down + input.native.send_keys :down + input.native.send_keys :enter + + expect(page).to have_content 'expand-collapse-files' + end + end +end -- cgit v1.2.1 From f83bccfe4f98281ed80c95189c5f7ed77799b2b3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 20 Jul 2016 10:59:49 +0200 Subject: Add minor readability, style improvements in CI config --- lib/ci/gitlab_ci_yaml_processor.rb | 5 ++--- lib/gitlab/ci/config/node/artifacts.rb | 7 ++++--- lib/gitlab/ci/config/node/attributable.rb | 2 +- lib/gitlab/ci/config/node/entry.rb | 4 ++-- lib/gitlab/ci/config/node/job.rb | 9 +++++---- lib/gitlab/ci/config/node/jobs.rb | 10 +++++----- lib/gitlab/ci/config/node/undefined.rb | 2 +- spec/lib/gitlab/ci/config/node/artifacts_spec.rb | 2 +- 8 files changed, 21 insertions(+), 20 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 013813ef00b..24601fdfe85 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -8,7 +8,8 @@ module Ci def initialize(config, path = nil) @ci_config = Gitlab::Ci::Config.new(config) - @config, @path = @ci_config.to_hash, path + @config = @ci_config.to_hash + @path = path unless @ci_config.valid? raise ValidationError, @ci_config.errors.first @@ -120,8 +121,6 @@ module Ci end def validate_job!(name, job) - raise ValidationError, "Unknown parameter: #{name}" unless job.is_a?(Hash) && job.has_key?(:script) - validate_job_stage!(name, job) if job[:stage] validate_job_dependencies!(name, job) if job[:dependencies] end diff --git a/lib/gitlab/ci/config/node/artifacts.rb b/lib/gitlab/ci/config/node/artifacts.rb index 2c301cf2917..844bd2fe998 100644 --- a/lib/gitlab/ci/config/node/artifacts.rb +++ b/lib/gitlab/ci/config/node/artifacts.rb @@ -9,12 +9,13 @@ module Gitlab include Validatable include Attributable - attributes :name, :untracked, :paths, :when, :expire_in + ALLOWED_KEYS = %i[name untracked paths when expire_in] + + attributes ALLOWED_KEYS validations do validates :config, type: Hash - validates :config, - allowed_keys: %i[name untracked paths when expire_in] + validates :config, allowed_keys: ALLOWED_KEYS with_options allow_nil: true do validates :name, type: String diff --git a/lib/gitlab/ci/config/node/attributable.rb b/lib/gitlab/ci/config/node/attributable.rb index 6e935c025e4..221b666f9f6 100644 --- a/lib/gitlab/ci/config/node/attributable.rb +++ b/lib/gitlab/ci/config/node/attributable.rb @@ -7,7 +7,7 @@ module Gitlab class_methods do def attributes(*attributes) - attributes.each do |attribute| + attributes.flatten.each do |attribute| define_method(attribute) do return unless config.is_a?(Hash) diff --git a/lib/gitlab/ci/config/node/entry.rb b/lib/gitlab/ci/config/node/entry.rb index 813e394e51b..0c782c422b5 100644 --- a/lib/gitlab/ci/config/node/entry.rb +++ b/lib/gitlab/ci/config/node/entry.rb @@ -24,7 +24,7 @@ module Gitlab return unless valid? compose! - @entries.each_value(&:process!) + descendants.each(&:process!) end def leaf? @@ -44,7 +44,7 @@ module Gitlab end def errors - @validator.messages + @entries.values.flat_map(&:errors) + @validator.messages + descendants.flat_map(&:errors) end def value diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index aea9fef8229..dc0813a8c18 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -9,13 +9,14 @@ module Gitlab include Configurable include Attributable + ALLOWED_KEYS = %i[tags script only except type image services allow_failure + type stage when artifacts cache dependencies before_script + after_script variables environment] + attributes :tags, :allow_failure, :when, :environment validations do - validates :config, allowed_keys: - %i[tags script only except type image services allow_failure - type stage when artifacts cache dependencies before_script - after_script variables environment] + validates :config, allowed_keys: ALLOWED_KEYS validates :config, presence: true validates :name, presence: true diff --git a/lib/gitlab/ci/config/node/jobs.rb b/lib/gitlab/ci/config/node/jobs.rb index 908c8f4f120..51683c82ceb 100644 --- a/lib/gitlab/ci/config/node/jobs.rb +++ b/lib/gitlab/ci/config/node/jobs.rb @@ -18,10 +18,14 @@ module Gitlab end def has_visible_job? - config.any? { |key, _| !key.to_s.start_with?('.') } + config.any? { |name, _| !hidden?(name) } end end + def hidden?(name) + name.to_s.start_with?('.') + end + private def compose! @@ -37,10 +41,6 @@ module Gitlab @entries[name] = factory.create! end end - - def hidden?(name) - name.to_s.start_with?('.') - end end end end diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb index 384774c9b69..84dab61e7e9 100644 --- a/lib/gitlab/ci/config/node/undefined.rb +++ b/lib/gitlab/ci/config/node/undefined.rb @@ -5,7 +5,7 @@ module Gitlab ## # This class represents an undefined and unspecified entry node. # - # It decorates original entry adding method that idicates it is + # It decorates original entry adding method that indicates it is # unspecified. # class Undefined < SimpleDelegator diff --git a/spec/lib/gitlab/ci/config/node/artifacts_spec.rb b/spec/lib/gitlab/ci/config/node/artifacts_spec.rb index 418a88cabac..beed29b18ae 100644 --- a/spec/lib/gitlab/ci/config/node/artifacts_spec.rb +++ b/spec/lib/gitlab/ci/config/node/artifacts_spec.rb @@ -31,7 +31,7 @@ describe Gitlab::Ci::Config::Node::Artifacts do end end - context 'when there is uknown key' do + context 'when there is an unknown key present' do let(:config) { { test: 100 } } it 'reports error' do -- cgit v1.2.1 From dff10976da42cc729069cddf0f032347fe2f6b14 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 20 Jul 2016 14:15:18 +0200 Subject: Move job dependencies entry to the new CI config --- lib/ci/gitlab_ci_yaml_processor.rb | 24 ++++++++++-------------- lib/gitlab/ci/config/node/job.rb | 4 +++- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 2 +- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 24601fdfe85..a2e8bd22a52 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -98,21 +98,22 @@ module Ci @jobs = @ci_config.jobs @jobs.each do |name, job| - validate_job!(name, job) + # logical validation for job + + validate_job_stage!(name, job) + validate_job_dependencies!(name, job) end end def yaml_variables(name) - variables = global_variables.merge(job_variables(name)) + variables = (@variables || {}) + .merge(job_variables(name)) + variables.map do |key, value| { key: key, value: value, public: true } end end - def global_variables - @variables || {} - end - def job_variables(name) job = @jobs[name.to_sym] return {} unless job @@ -120,21 +121,16 @@ module Ci job[:variables] || {} end - def validate_job!(name, job) - validate_job_stage!(name, job) if job[:stage] - validate_job_dependencies!(name, job) if job[:dependencies] - end - def validate_job_stage!(name, job) + return unless job[:stage] + unless job[:stage].is_a?(String) && job[:stage].in?(@stages) raise ValidationError, "#{name} job: stage parameter should be #{@stages.join(", ")}" end end def validate_job_dependencies!(name, job) - unless validate_array_of_strings(job[:dependencies]) - raise ValidationError, "#{name} job: dependencies parameter should be an array of strings" - end + return unless job[:dependencies] stage_index = @stages.index(job[:stage]) diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index dc0813a8c18..ace79d829f2 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -13,7 +13,7 @@ module Gitlab type stage when artifacts cache dependencies before_script after_script variables environment] - attributes :tags, :allow_failure, :when, :environment + attributes :tags, :allow_failure, :when, :environment, :dependencies validations do validates :config, allowed_keys: ALLOWED_KEYS @@ -37,6 +37,8 @@ module Gitlab format: { with: Gitlab::Regex.environment_name_regex, message: Gitlab::Regex.environment_name_regex_message } + + validates :dependencies, array_of_strings: true end end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index 5785b7e59fb..61490555ff5 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -1239,7 +1239,7 @@ EOT config = YAML.dump({ types: ["build", "test"], rspec: { script: "test", dependencies: "string" } }) expect do GitlabCiYamlProcessor.new(config) - end.to raise_error(GitlabCiYamlProcessor::ValidationError, "rspec job: dependencies parameter should be an array of strings") + end.to raise_error(GitlabCiYamlProcessor::ValidationError, "jobs:rspec dependencies should be an array of strings") end end -- cgit v1.2.1 From 647da42af99a703fc3af3452b39acf0c3dc3050d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 30 Jun 2016 15:44:29 +0200 Subject: Merge coverage report --- .gitlab-ci.yml | 11 ++++++++ .simplecov | 4 --- lib/tasks/ci/simplecov.rake | 63 +++++++++++++++++++++++++++++++++++++++++++++ spec/spec_helper.rb | 10 ++++++- 4 files changed, 83 insertions(+), 5 deletions(-) delete mode 100644 .simplecov create mode 100644 lib/tasks/ci/simplecov.rake diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2d33bad5886..b0010572833 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -61,6 +61,15 @@ update-knapsack: only: - master +update-coverage: + <<: *knapsack-state + stage: post-test + script: + - bundle exec rake ci:simplecov:merge + artifacts: + paths: + - coverage/ + # Execute all testing suites .use-db: &use-db @@ -83,6 +92,7 @@ update-knapsack: artifacts: paths: - knapsack/ + - coverage/ .spinach-knapsack: &spinach-knapsack stage: test @@ -99,6 +109,7 @@ update-knapsack: artifacts: paths: - knapsack/ + - coverage/ rspec 0 20: *rspec-knapsack rspec 1 20: *rspec-knapsack diff --git a/.simplecov b/.simplecov deleted file mode 100644 index d979288df44..00000000000 --- a/.simplecov +++ /dev/null @@ -1,4 +0,0 @@ -# .simplecov -SimpleCov.start 'rails' do - merge_timeout 3600 -end diff --git a/lib/tasks/ci/simplecov.rake b/lib/tasks/ci/simplecov.rake new file mode 100644 index 00000000000..0c8322940ec --- /dev/null +++ b/lib/tasks/ci/simplecov.rake @@ -0,0 +1,63 @@ +require 'simplecov' + +namespace :ci do + namespace :simplecov do + desc 'GitLab CI | Merge all coverage results and generate report' + task merge: :environment do + merged_result.format! + end + + private + + def read(file) + return unless File.exist?(file) + data = File.read(file) + return if data.nil? || data.length < 2 + data + end + + def load(file) + begin + JSON.parse(read(file)) + rescue + {} + end + end + + def files + Dir.glob(File.join(SimpleCov.coverage_path, '*/.resultset.json')) + end + + def resultsfiles + files.map { |file| load(file) } + end + + def resultsets + resultsfiles.reduce({}, :merge) + end + + def all_results + results = [] + resultsets.each do |command_name, data| + result = SimpleCov::Result.from_hash(command_name => data) + # Only add result if the timeout is above the configured threshold + if (Time.now - result.created_at) < SimpleCov.merge_timeout + results << result + end + end + results + end + + def merged_result + merged = {} + results = all_results + results.each do |result| + merged = result.original_result.merge_resultset(merged) + end + result = SimpleCov::Result.new(merged) + # Specify the command name + result.command_name = results.map(&:command_name).sort.join(", ") + result + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 3638dcbb2d3..644f767402c 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,14 @@ if ENV['SIMPLECOV'] require 'simplecov' - SimpleCov.start :rails + require 'simplecov-rcov' + + SimpleCov.start :rails do + if ENV['CI_BUILD_NAME'] + coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}" + command_name ENV['CI_BUILD_NAME'] + merge_timeout 7200 + end + end end ENV["RAILS_ENV"] ||= 'test' -- cgit v1.2.1 From bd709e29b50940409d6b1abc869fe2969d6a3b51 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 30 Jun 2016 16:08:01 +0200 Subject: Use `scripts/merge-simplecov` --- .gitlab-ci.yml | 2 +- lib/tasks/ci/simplecov.rake | 63 ------------------------------------------- scripts/merge-simplecov | 65 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 66 insertions(+), 64 deletions(-) delete mode 100644 lib/tasks/ci/simplecov.rake create mode 100755 scripts/merge-simplecov diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index b0010572833..9a90c99a996 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -65,7 +65,7 @@ update-coverage: <<: *knapsack-state stage: post-test script: - - bundle exec rake ci:simplecov:merge + - bundle exec scripts/merge-simplecov artifacts: paths: - coverage/ diff --git a/lib/tasks/ci/simplecov.rake b/lib/tasks/ci/simplecov.rake deleted file mode 100644 index 0c8322940ec..00000000000 --- a/lib/tasks/ci/simplecov.rake +++ /dev/null @@ -1,63 +0,0 @@ -require 'simplecov' - -namespace :ci do - namespace :simplecov do - desc 'GitLab CI | Merge all coverage results and generate report' - task merge: :environment do - merged_result.format! - end - - private - - def read(file) - return unless File.exist?(file) - data = File.read(file) - return if data.nil? || data.length < 2 - data - end - - def load(file) - begin - JSON.parse(read(file)) - rescue - {} - end - end - - def files - Dir.glob(File.join(SimpleCov.coverage_path, '*/.resultset.json')) - end - - def resultsfiles - files.map { |file| load(file) } - end - - def resultsets - resultsfiles.reduce({}, :merge) - end - - def all_results - results = [] - resultsets.each do |command_name, data| - result = SimpleCov::Result.from_hash(command_name => data) - # Only add result if the timeout is above the configured threshold - if (Time.now - result.created_at) < SimpleCov.merge_timeout - results << result - end - end - results - end - - def merged_result - merged = {} - results = all_results - results.each do |result| - merged = result.original_result.merge_resultset(merged) - end - result = SimpleCov::Result.new(merged) - # Specify the command name - result.command_name = results.map(&:command_name).sort.join(", ") - result - end - end -end diff --git a/scripts/merge-simplecov b/scripts/merge-simplecov new file mode 100755 index 00000000000..b59ff0ae9a0 --- /dev/null +++ b/scripts/merge-simplecov @@ -0,0 +1,65 @@ +#!/usr/bin/env ruby +begin + load File.expand_path('../spring', __FILE__) +rescue LoadError => e + raise unless e.message.include?('spring') +end + +require 'simplecov' + +def read(file) + return unless File.exist?(file) + data = File.read(file) + return if data.nil? || data.length < 2 + data +end + +def load(file) + begin + JSON.parse(read(file)) + rescue + {} + end +end + +def files + Dir.glob(File.join(SimpleCov.coverage_path, '*', '.resultset.json')) +end + +def resultsfiles + files.map { |file| load(file) } +end + +def resultsets + resultsfiles.reduce({}, :merge) +end + +def all_results + results = [] + resultsets.each do |command_name, data| + result = SimpleCov::Result.from_hash(command_name => data) + # Only add result if the timeout is above the configured threshold + if (Time.now - result.created_at) < SimpleCov.merge_timeout + results << result + end + end + results +end + +def merged_result + merged = {} + results = all_results + results.each do |result| + merged = result.original_result.merge_resultset(merged) + end + result = SimpleCov::Result.new(merged) + # Specify the command name + result.command_name = results.map(&:command_name).sort.join(", ") + result +end + +SimpleCov.configure do + merge_timeout 7200 +end + +merged_result.format! -- cgit v1.2.1 From 93f98fb8cade1376e607a92ed866b2d569f17d4a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 30 Jun 2016 16:23:57 +0200 Subject: Remove unused simplecov-rcov --- spec/spec_helper.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 644f767402c..72076258dcf 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,6 +1,5 @@ if ENV['SIMPLECOV'] require 'simplecov' - require 'simplecov-rcov' SimpleCov.start :rails do if ENV['CI_BUILD_NAME'] -- cgit v1.2.1 From caec732c7629ddd87792b76a67b41ff26611eec1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 30 Jun 2016 16:49:17 +0200 Subject: Add simplecov to spinach tests --- features/support/env.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/features/support/env.rb b/features/support/env.rb index f0a3dd8d2d0..c1cfabd0d6a 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,5 +1,13 @@ if ENV['SIMPLECOV'] require 'simplecov' + + SimpleCov.start :rails do + if ENV['CI_BUILD_NAME'] + coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}" + command_name ENV['CI_BUILD_NAME'] + merge_timeout 7200 + end + end end ENV['RAILS_ENV'] = 'test' -- cgit v1.2.1 From 2d3978842cb1742d1c974fa57c29b6508a365c24 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 30 Jun 2016 17:55:16 +0200 Subject: Fix update-coverage job --- .gitlab-ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9a90c99a996..4814cbe4ae7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -62,8 +62,11 @@ update-knapsack: - master update-coverage: - <<: *knapsack-state stage: post-test + services: [] + variables: + USE_DB: "false" + USE_BUNDLE_INSTALL: "true" script: - bundle exec scripts/merge-simplecov artifacts: -- cgit v1.2.1 From 8cc7a2dd6f201019d6848aade74d938db356cc39 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 30 Jun 2016 18:54:03 +0200 Subject: Store all simplecov configuration in one file --- .gitlab-ci.yml | 4 ++++ features/support/env.rb | 12 +----------- spec/simplecov_env.rb | 18 ++++++++++++++++++ spec/spec_helper.rb | 12 +----------- 4 files changed, 24 insertions(+), 22 deletions(-) create mode 100644 spec/simplecov_env.rb diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4814cbe4ae7..aa22bf688a0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -40,6 +40,7 @@ stages: paths: - knapsack/ artifacts: + expire_in: 31d paths: - knapsack/ @@ -70,6 +71,7 @@ update-coverage: script: - bundle exec scripts/merge-simplecov artifacts: + expire_in: 31d paths: - coverage/ @@ -93,6 +95,7 @@ update-coverage: - cp knapsack/rspec_report.json ${KNAPSACK_REPORT_PATH} - knapsack rspec artifacts: + expire_in: 31d paths: - knapsack/ - coverage/ @@ -110,6 +113,7 @@ update-coverage: - cp knapsack/spinach_report.json ${KNAPSACK_REPORT_PATH} - knapsack spinach "-r rerun" || retry '[ ! -e tmp/spinach-rerun.txt ] || bundle exec spinach -r rerun $(cat tmp/spinach-rerun.txt)' artifacts: + expire_in: 31d paths: - knapsack/ - coverage/ diff --git a/features/support/env.rb b/features/support/env.rb index c1cfabd0d6a..1c39367ad42 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,14 +1,4 @@ -if ENV['SIMPLECOV'] - require 'simplecov' - - SimpleCov.start :rails do - if ENV['CI_BUILD_NAME'] - coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}" - command_name ENV['CI_BUILD_NAME'] - merge_timeout 7200 - end - end -end +require_relative Rails.root.join('spec', 'simplecov_env') ENV['RAILS_ENV'] = 'test' require './config/environment' diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb new file mode 100644 index 00000000000..a833255d407 --- /dev/null +++ b/spec/simplecov_env.rb @@ -0,0 +1,18 @@ +if ENV['SIMPLECOV'] + require 'simplecov' + + SimpleCov.start :rails do + if ENV['CI_BUILD_NAME'] + coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}" + command_name ENV['CI_BUILD_NAME'] + merge_timeout 7200 + end + + add_filter '/vendor/ruby/' + + add_group 'Services', 'app/services' + add_group 'Finders', 'app/finders' + add_group 'Uploaders', 'app/uploaders' + add_group 'Validators', 'app/validators' + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 72076258dcf..6a882bea571 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,14 +1,4 @@ -if ENV['SIMPLECOV'] - require 'simplecov' - - SimpleCov.start :rails do - if ENV['CI_BUILD_NAME'] - coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}" - command_name ENV['CI_BUILD_NAME'] - merge_timeout 7200 - end - end -end +require_relative 'simplecov_env' ENV["RAILS_ENV"] ||= 'test' -- cgit v1.2.1 From 070a96f54faac289d9254f0e8735f3926418335c Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 30 Jun 2016 19:04:00 +0200 Subject: Update configuration of SimpleCov --- .gitlab-ci.yml | 1 + features/support/env.rb | 1 + scripts/merge-simplecov | 4 +--- spec/simplecov_env.rb | 35 ++++++++++++++++++++++------------- spec/spec_helper.rb | 1 + 5 files changed, 26 insertions(+), 16 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index aa22bf688a0..1ca266ef4f1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -28,6 +28,7 @@ stages: - prepare - test - post-test +- pages # Prepare and merge knapsack tests .knapsack-state: &knapsack-state diff --git a/features/support/env.rb b/features/support/env.rb index 1c39367ad42..16ce1b6ca77 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,4 +1,5 @@ require_relative Rails.root.join('spec', 'simplecov_env') +SimpleCov.start if ENV['SIMPLECOV'] ENV['RAILS_ENV'] = 'test' require './config/environment' diff --git a/scripts/merge-simplecov b/scripts/merge-simplecov index b59ff0ae9a0..0d764d2b406 100755 --- a/scripts/merge-simplecov +++ b/scripts/merge-simplecov @@ -58,8 +58,6 @@ def merged_result result end -SimpleCov.configure do - merge_timeout 7200 -end +require_relative '../spec/simplecov_env' merged_result.format! diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb index a833255d407..956476fd065 100644 --- a/spec/simplecov_env.rb +++ b/spec/simplecov_env.rb @@ -1,18 +1,27 @@ -if ENV['SIMPLECOV'] - require 'simplecov' +require 'simplecov' - SimpleCov.start :rails do - if ENV['CI_BUILD_NAME'] - coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}" - command_name ENV['CI_BUILD_NAME'] - merge_timeout 7200 - end +SimpleCov.configure do + load_profile :rails - add_filter '/vendor/ruby/' + if ENV['CI_BUILD_NAME'] + coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}" + command_name ENV['CI_BUILD_NAME'] + end - add_group 'Services', 'app/services' - add_group 'Finders', 'app/finders' - add_group 'Uploaders', 'app/uploaders' - add_group 'Validators', 'app/validators' + if ENV['CI'] + SimpleCov.at_exit do + # In CI environment don't generate formatted reports + # Only generate .resultset.json + SimpleCov.result + end end + + add_filter '/vendor/ruby/' + + add_group 'Services', 'app/services' + add_group 'Finders', 'app/finders' + add_group 'Uploaders', 'app/uploaders' + add_group 'Validators', 'app/validators' + + merge_timeout 7200 end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6a882bea571..d2760d0ef28 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,5 @@ require_relative 'simplecov_env' +SimpleCov.start if ENV['SIMPLECOV'] ENV["RAILS_ENV"] ||= 'test' -- cgit v1.2.1 From 3dac444ca17bdd17414bebcc55e7a53310dee544 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 30 Jun 2016 19:47:40 +0200 Subject: Fix SimpleCov report merging --- .gitlab-ci.yml | 3 ++- scripts/merge-simplecov | 37 ++++++++++++++++++++++++++++++++++++- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 1ca266ef4f1..8f75a46dfe8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -74,7 +74,8 @@ update-coverage: artifacts: expire_in: 31d paths: - - coverage/ + - coverage/index.html + - coverage/assets/ # Execute all testing suites diff --git a/scripts/merge-simplecov b/scripts/merge-simplecov index 0d764d2b406..2af5f8c0918 100755 --- a/scripts/merge-simplecov +++ b/scripts/merge-simplecov @@ -46,11 +46,42 @@ def all_results results end +def merge_resultset(a1, a2) + return a1 || [] unless a2 + return a2 || [] unless a1 + + new_array = a1.dup + a2.each_with_index do |element, i| + if element.nil? && new_array[i].nil? + new_array[i] = nil + elsif element.nil? && new_array[i] == 0 || element == 0 && new_array[i].nil? + new_array[i] = nil + else + local_value = element || 0 + other_value = new_array[i] || 0 + new_array[i] = local_value + other_value + end + end + new_array +end + +def merge_hashes(h1, h2) + new_resultset = {} + (h1.keys + h2.keys).each do |filename| + new_resultset[filename] = [] + end + + new_resultset.each_key do |filename| + new_resultset[filename] = merge_resultset(h1[filename], h2[filename]) + end + new_resultset +end + def merged_result merged = {} results = all_results results.each do |result| - merged = result.original_result.merge_resultset(merged) + merged = merge_hashes(result.original_result, merged) end result = SimpleCov::Result.new(merged) # Specify the command name @@ -58,6 +89,10 @@ def merged_result result end +# Ignore CI environment +ENV['CI'] = nil +ENV['CI_BUILD_NAME'] = nil + require_relative '../spec/simplecov_env' merged_result.format! -- cgit v1.2.1 From 63ceb31f8b15b4ba7fd1b4c971367a5e20aca311 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 1 Jul 2016 00:33:12 +0200 Subject: Fix spinach tests --- features/support/env.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/support/env.rb b/features/support/env.rb index 16ce1b6ca77..2a3e21d28bc 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,4 +1,4 @@ -require_relative Rails.root.join('spec', 'simplecov_env') +require_relative '../../spec/simplecov_env' SimpleCov.start if ENV['SIMPLECOV'] ENV['RAILS_ENV'] = 'test' -- cgit v1.2.1 From a9f6679ac1fc90d808a3db363e6c3af2ebeac6a1 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 4 Jul 2016 14:58:51 +0200 Subject: Use simplecov 0.12.0 which fixes merging test results --- Gemfile | 2 +- scripts/merge-simplecov | 33 +-------------------------------- 2 files changed, 2 insertions(+), 33 deletions(-) diff --git a/Gemfile b/Gemfile index ead64a6d4df..35dd69ef408 100644 --- a/Gemfile +++ b/Gemfile @@ -302,7 +302,7 @@ group :development, :test do gem 'rubocop', '~> 0.41.2', require: false gem 'rubocop-rspec', '~> 1.5.0', require: false gem 'scss_lint', '~> 0.47.0', require: false - gem 'simplecov', '~> 0.11.0', require: false + gem 'simplecov', '~> 0.12.0', require: false gem 'flog', '~> 4.3.2', require: false gem 'flay', '~> 2.6.1', require: false gem 'bundler-audit', '~> 0.5.0', require: false diff --git a/scripts/merge-simplecov b/scripts/merge-simplecov index 2af5f8c0918..9504321e6d7 100755 --- a/scripts/merge-simplecov +++ b/scripts/merge-simplecov @@ -46,42 +46,11 @@ def all_results results end -def merge_resultset(a1, a2) - return a1 || [] unless a2 - return a2 || [] unless a1 - - new_array = a1.dup - a2.each_with_index do |element, i| - if element.nil? && new_array[i].nil? - new_array[i] = nil - elsif element.nil? && new_array[i] == 0 || element == 0 && new_array[i].nil? - new_array[i] = nil - else - local_value = element || 0 - other_value = new_array[i] || 0 - new_array[i] = local_value + other_value - end - end - new_array -end - -def merge_hashes(h1, h2) - new_resultset = {} - (h1.keys + h2.keys).each do |filename| - new_resultset[filename] = [] - end - - new_resultset.each_key do |filename| - new_resultset[filename] = merge_resultset(h1[filename], h2[filename]) - end - new_resultset -end - def merged_result merged = {} results = all_results results.each do |result| - merged = merge_hashes(result.original_result, merged) + merged = result.original_result.merge_resultset(merged) end result = SimpleCov::Result.new(merged) # Specify the command name -- cgit v1.2.1 From e18eddcb208fecd0abd9ce328a0e71f2560a0672 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 4 Jul 2016 15:02:27 +0200 Subject: Remove unused coveralls --- Gemfile.lock | 6 +++--- lib/tasks/test.rake | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 8739f8579d5..3ec11f46a5c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -668,9 +668,9 @@ GEM rufus-scheduler (>= 2.0.24) sidekiq (>= 4.0.0) simple_oauth (0.1.9) - simplecov (0.11.2) + simplecov (0.12.0) docile (~> 1.1.0) - json (~> 1.8) + json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.0) sinatra (1.4.7) @@ -952,7 +952,7 @@ DEPENDENCIES shoulda-matchers (~> 2.8.0) sidekiq (~> 4.0) sidekiq-cron (~> 0.4.0) - simplecov (~> 0.11.0) + simplecov (~> 0.12.0) sinatra (~> 1.4.4) six (~> 0.2.0) slack-notifier (~> 1.2.0) diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake index 21c0e5f1d41..d3dcbd2c29b 100644 --- a/lib/tasks/test.rake +++ b/lib/tasks/test.rake @@ -7,5 +7,5 @@ end unless Rails.env.production? desc "GitLab | Run all tests on CI with simplecov" - task test_ci: [:rubocop, :brakeman, 'teaspoon', :spinach, :spec] + task test_ci: [:rubocop, :brakeman, :teaspoon, :spinach, :spec] end -- cgit v1.2.1 From 47db7fd3e2c6b6ef65f6b7e7e022d769a3bbc2e2 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 21 Jul 2016 10:02:52 +0200 Subject: Load app in test env eagerly to improve test coverage --- config/environments/test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/test.rb b/config/environments/test.rb index fb25d3a8b14..6e797c79820 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -30,7 +30,7 @@ Rails.application.configure do # Print deprecation notices to the stderr config.active_support.deprecation = :stderr - config.eager_load = false + config.eager_load = true config.cache_store = :null_store -- cgit v1.2.1 From be3ad008aad2745e3a80ca789025005e180c805e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 21 Jul 2016 10:03:41 +0200 Subject: Improve simplecov config to avoid missing files --- spec/simplecov_env.rb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb index 956476fd065..112ca1b176a 100644 --- a/spec/simplecov_env.rb +++ b/spec/simplecov_env.rb @@ -1,7 +1,8 @@ require 'simplecov' SimpleCov.configure do - load_profile :rails + load_profile 'test_frameworks' + track_files '{app,lib}/**/*.rb' if ENV['CI_BUILD_NAME'] coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}" @@ -17,7 +18,14 @@ SimpleCov.configure do end add_filter '/vendor/ruby/' + add_filter 'config/initializers/' + add_group 'Controllers', 'app/controllers' + add_group 'Models', 'app/models' + add_group 'Mailers', 'app/mailers' + add_group 'Helpers', 'app/helpers' + add_group 'Workers', %w(app/jobs app/workers) + add_group 'Libraries', 'lib' add_group 'Services', 'app/services' add_group 'Finders', 'app/finders' add_group 'Uploaders', 'app/uploaders' -- cgit v1.2.1 From 8e5e668e1a0844ebafbfc8081d9f71a030feb1d6 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 21 Jul 2016 10:25:57 +0200 Subject: Patch SimpleCov to reuse code for coverage merger --- scripts/merge-simplecov | 74 ++++++++++++++----------------------------------- 1 file changed, 21 insertions(+), 53 deletions(-) diff --git a/scripts/merge-simplecov b/scripts/merge-simplecov index 9504321e6d7..ab15b43bd0f 100755 --- a/scripts/merge-simplecov +++ b/scripts/merge-simplecov @@ -1,61 +1,29 @@ #!/usr/bin/env ruby -begin - load File.expand_path('../spring', __FILE__) -rescue LoadError => e - raise unless e.message.include?('spring') -end require 'simplecov' -def read(file) - return unless File.exist?(file) - data = File.read(file) - return if data.nil? || data.length < 2 - data -end - -def load(file) - begin - JSON.parse(read(file)) - rescue - {} - end -end - -def files - Dir.glob(File.join(SimpleCov.coverage_path, '*', '.resultset.json')) -end - -def resultsfiles - files.map { |file| load(file) } -end - -def resultsets - resultsfiles.reduce({}, :merge) -end - -def all_results - results = [] - resultsets.each do |command_name, data| - result = SimpleCov::Result.from_hash(command_name => data) - # Only add result if the timeout is above the configured threshold - if (Time.now - result.created_at) < SimpleCov.merge_timeout - results << result +module SimpleCov + module ResultMerger + class << self + def resultset_files + Dir.glob(File.join(SimpleCov.coverage_path, '*', '.resultset.json')) + end + + def resultset_hashes + resultset_files.map do |path| + begin + JSON.parse(File.read(path)) + rescue + {} + end + end + end + + def resultset + resultset_hashes.reduce({}, :merge) + end end end - results -end - -def merged_result - merged = {} - results = all_results - results.each do |result| - merged = result.original_result.merge_resultset(merged) - end - result = SimpleCov::Result.new(merged) - # Specify the command name - result.command_name = results.map(&:command_name).sort.join(", ") - result end # Ignore CI environment @@ -64,4 +32,4 @@ ENV['CI_BUILD_NAME'] = nil require_relative '../spec/simplecov_env' -merged_result.format! +SimpleCov::ResultMerger.merged_result.format! -- cgit v1.2.1 From 7a0f4d3c905cc92880697deac365a6c596fd55a8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 21 Jul 2016 10:44:18 +0200 Subject: Introduce SimpleCovEnv singleton helper and use it --- features/support/env.rb | 4 +-- scripts/merge-simplecov | 9 ++----- spec/simplecov_env.rb | 71 +++++++++++++++++++++++++++++++------------------ spec/spec_helper.rb | 4 +-- 4 files changed, 51 insertions(+), 37 deletions(-) diff --git a/features/support/env.rb b/features/support/env.rb index 2a3e21d28bc..569fd444e86 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,5 +1,5 @@ -require_relative '../../spec/simplecov_env' -SimpleCov.start if ENV['SIMPLECOV'] +require './spec/simplecov_env' +SimpleCovEnv.start! ENV['RAILS_ENV'] = 'test' require './config/environment' diff --git a/scripts/merge-simplecov b/scripts/merge-simplecov index ab15b43bd0f..65f93f8830b 100755 --- a/scripts/merge-simplecov +++ b/scripts/merge-simplecov @@ -1,6 +1,7 @@ #!/usr/bin/env ruby -require 'simplecov' +require_relative '../spec/simplecov_env' +SimpleCovEnv.configure_profile module SimpleCov module ResultMerger @@ -26,10 +27,4 @@ module SimpleCov end end -# Ignore CI environment -ENV['CI'] = nil -ENV['CI_BUILD_NAME'] = nil - -require_relative '../spec/simplecov_env' - SimpleCov::ResultMerger.merged_result.format! diff --git a/spec/simplecov_env.rb b/spec/simplecov_env.rb index 112ca1b176a..6f8f7109e14 100644 --- a/spec/simplecov_env.rb +++ b/spec/simplecov_env.rb @@ -1,35 +1,54 @@ require 'simplecov' -SimpleCov.configure do - load_profile 'test_frameworks' - track_files '{app,lib}/**/*.rb' +module SimpleCovEnv + extend self - if ENV['CI_BUILD_NAME'] - coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}" - command_name ENV['CI_BUILD_NAME'] + def start! + return unless ENV['SIMPLECOV'] + + configure_profile + configure_job + + SimpleCov.start end - if ENV['CI'] - SimpleCov.at_exit do - # In CI environment don't generate formatted reports - # Only generate .resultset.json - SimpleCov.result + def configure_job + SimpleCov.configure do + if ENV['CI_BUILD_NAME'] + coverage_dir "coverage/#{ENV['CI_BUILD_NAME']}" + command_name ENV['CI_BUILD_NAME'] + end + + if ENV['CI'] + SimpleCov.at_exit do + # In CI environment don't generate formatted reports + # Only generate .resultset.json + SimpleCov.result + end + end end end - add_filter '/vendor/ruby/' - add_filter 'config/initializers/' - - add_group 'Controllers', 'app/controllers' - add_group 'Models', 'app/models' - add_group 'Mailers', 'app/mailers' - add_group 'Helpers', 'app/helpers' - add_group 'Workers', %w(app/jobs app/workers) - add_group 'Libraries', 'lib' - add_group 'Services', 'app/services' - add_group 'Finders', 'app/finders' - add_group 'Uploaders', 'app/uploaders' - add_group 'Validators', 'app/validators' - - merge_timeout 7200 + def configure_profile + SimpleCov.configure do + load_profile 'test_frameworks' + track_files '{app,lib}/**/*.rb' + + add_filter '/vendor/ruby/' + add_filter 'config/initializers/' + + add_group 'Controllers', 'app/controllers' + add_group 'Models', 'app/models' + add_group 'Mailers', 'app/mailers' + add_group 'Helpers', 'app/helpers' + add_group 'Workers', %w(app/jobs app/workers) + add_group 'Libraries', 'lib' + add_group 'Services', 'app/services' + add_group 'Finders', 'app/finders' + add_group 'Uploaders', 'app/uploaders' + add_group 'Validators', 'app/validators' + + merge_timeout 7200 + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d2760d0ef28..4f3aacf55be 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,5 +1,5 @@ -require_relative 'simplecov_env' -SimpleCov.start if ENV['SIMPLECOV'] +require './spec/simplecov_env' +SimpleCovEnv.start! ENV["RAILS_ENV"] ||= 'test' -- cgit v1.2.1 From 133df7ee57ae217720af9887d8373299ca15d982 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 21 Jul 2016 14:00:54 +0200 Subject: Revert eager loading in test env that breakes teaspoon --- config/environments/test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/environments/test.rb b/config/environments/test.rb index 6e797c79820..fb25d3a8b14 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -30,7 +30,7 @@ Rails.application.configure do # Print deprecation notices to the stderr config.active_support.deprecation = :stderr - config.eager_load = true + config.eager_load = false config.cache_store = :null_store -- cgit v1.2.1 From f0f77252b851f0ecc14f51f06df595556b89e442 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 21 Jul 2016 14:54:31 +0200 Subject: Deploy ruby test coverage report to gitlab pages --- .gitlab-ci.yml | 43 +++++++++++++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 14 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8f75a46dfe8..d5305d9a26b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -63,20 +63,6 @@ update-knapsack: only: - master -update-coverage: - stage: post-test - services: [] - variables: - USE_DB: "false" - USE_BUNDLE_INSTALL: "true" - script: - - bundle exec scripts/merge-simplecov - artifacts: - expire_in: 31d - paths: - - coverage/index.html - - coverage/assets/ - # Execute all testing suites .use-db: &use-db @@ -246,6 +232,21 @@ bundler:audit: script: - "bundle exec bundle-audit check --update --ignore OSVDB-115941" +coverage: + stage: post-test + services: [] + variables: + USE_DB: "false" + USE_BUNDLE_INSTALL: "true" + script: + - bundle exec scripts/merge-simplecov + artifacts: + expire_in: 31d + paths: + - coverage/index.html + - coverage/assets/ + + # Notify slack in the end notify:slack: @@ -258,3 +259,17 @@ notify:slack: - tags@gitlab-org/gitlab-ce - master@gitlab-org/gitlab-ee - tags@gitlab-org/gitlab-ee + +pages: + before_script: [] + services: [] + variables: {} + stage: pages + dependencies: + - coverage + script: + - mkdir -p public/coverage-ruby + - mv coverage public/coverage-ruby + artifacts: + paths: + - public -- cgit v1.2.1 From dc7805cd999dc6c97c407cbb419d1bb1cc36a71f Mon Sep 17 00:00:00 2001 From: Butch Anton Date: Thu, 21 Jul 2016 16:49:40 +0000 Subject: Update start-using-git.md --- doc/gitlab-basics/start-using-git.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md index 89ce8bcc3e8..b61f436c1a4 100644 --- a/doc/gitlab-basics/start-using-git.md +++ b/doc/gitlab-basics/start-using-git.md @@ -120,3 +120,11 @@ You need to be in the created branch. git checkout NAME-OF-BRANCH git merge master ``` + +### Merge master branch with created branch +You need to be in the master branch. +``` +git checkout master +git merge NAME-OF-BRANCH +``` + -- cgit v1.2.1 From ccbd77b2a712a4f70fdb1fb59ae8858e6a15f6e1 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Jul 2016 10:02:54 +0200 Subject: Improve CI configuration file for pages stage --- .gitlab-ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d5305d9a26b..ea97a317931 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -192,14 +192,14 @@ spinach 9 10 ruby23: *spinach-knapsack-ruby23 # Other generic tests -.static-analyses-variables: &static-analyses-variables +.ruby-static-analysis: &ruby-static-analysis variables: SIMPLECOV: "false" USE_DB: "false" USE_BUNDLE_INSTALL: "true" .exec: &exec - <<: *static-analyses-variables + <<: *ruby-static-analysis stage: test script: - bundle exec $CI_BUILD_NAME @@ -226,7 +226,7 @@ teaspoon: bundler:audit: stage: test - <<: *static-analyses-variables + <<: *ruby-static-analysis only: - master script: @@ -241,6 +241,7 @@ coverage: script: - bundle exec scripts/merge-simplecov artifacts: + name: coverage expire_in: 31d paths: - coverage/index.html @@ -262,13 +263,12 @@ notify:slack: pages: before_script: [] - services: [] - variables: {} stage: pages dependencies: - coverage script: - - mkdir -p public/coverage-ruby + - mv public/ .public/ + - mkdir public/ - mv coverage public/coverage-ruby artifacts: paths: -- cgit v1.2.1 From 89f46d3ae2c9586b0bd0efa165deedf09c176744 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Jul 2016 13:42:41 +0200 Subject: Deploy test coverage report on master pipeline only --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ea97a317931..2eda2a6007d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -273,3 +273,5 @@ pages: artifacts: paths: - public + only: + - master -- cgit v1.2.1 From 868e42b32250192e2b05735b94976ad6c43b76cc Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Fri, 22 Jul 2016 14:24:48 -0600 Subject: Update rack-oauth2 from 1.2.1 to 1.2.3. Removes methods deprecated in Rails 5. https://github.com/nov/rack-oauth2/compare/v1.2.1...v1.2.3 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 195516d1bf1..aec7c4421f7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -505,7 +505,7 @@ GEM rack-cors (0.4.0) rack-mount (0.8.3) rack (>= 1.0.0) - rack-oauth2 (1.2.1) + rack-oauth2 (1.2.3) activesupport (>= 2.3) attr_required (>= 0.0.5) httpclient (>= 2.4) -- cgit v1.2.1 From 172a5a8ab6e67e273156ee2aaa6dd178ffac3f01 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Fri, 22 Jul 2016 14:30:58 -0600 Subject: Upgrade default_value_for from 3.0.1 to 3.0.2. Includes support for Rails 5. Changelog: https://github.com/FooBarWidget/default_value_for/compare/release-3.0.1...release-3.0.2 --- Gemfile.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 195516d1bf1..8685c29c630 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -152,8 +152,8 @@ GEM database_cleaner (1.4.1) debug_inspector (0.0.2) debugger-ruby_core_source (1.3.8) - default_value_for (3.0.1) - activerecord (>= 3.2.0, < 5.0) + default_value_for (3.0.2) + activerecord (>= 3.2.0, < 5.1) descendants_tracker (0.0.4) thread_safe (~> 0.3, >= 0.3.1) devise (4.1.1) -- cgit v1.2.1 From 02913a96359938f58743ac962f9b16081db92ffc Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Fri, 22 Jul 2016 14:43:17 -0600 Subject: Upgrade gon from 6.0.1 to 6.1.0. Removes methods that were deprecated in Rails 5. Changelog: https://github.com/gazay/gon/compare/3a2b56d...9d89bc9 --- Gemfile | 2 +- Gemfile.lock | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 0504e643ed7..75dddd91702 100644 --- a/Gemfile +++ b/Gemfile @@ -224,7 +224,7 @@ gem 'addressable', '~> 2.3.8' gem 'bootstrap-sass', '~> 3.3.0' gem 'font-awesome-rails', '~> 4.6.1' gem 'gemojione', '~> 3.0' -gem 'gon', '~> 6.0.1' +gem 'gon', '~> 6.1.0' gem 'jquery-atwho-rails', '~> 1.3.2' gem 'jquery-rails', '~> 4.1.0' gem 'jquery-ui-rails', '~> 5.0.0' diff --git a/Gemfile.lock b/Gemfile.lock index 195516d1bf1..1d8d55b848d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -299,7 +299,7 @@ GEM gollum-rugged_adapter (0.4.2) mime-types (>= 1.15) rugged (~> 0.24.0, >= 0.21.3) - gon (6.0.1) + gon (6.1.0) actionpack (>= 3.0) json multi_json @@ -571,7 +571,7 @@ GEM redis-store (~> 1.1.0) redis-store (1.1.7) redis (>= 2.2) - request_store (1.3.0) + request_store (1.3.1) rerun (0.11.0) listen (~> 3.0) responders (2.1.1) @@ -866,7 +866,7 @@ DEPENDENCIES gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) gollum-rugged_adapter (~> 0.4.2) - gon (~> 6.0.1) + gon (~> 6.1.0) grape (~> 0.13.0) grape-entity (~> 0.4.2) hamlit (~> 2.5) -- cgit v1.2.1 From d6ebd4a3f7af1b78259edc4a3ffdc4778f5d0725 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Fri, 22 Jul 2016 10:53:45 -0400 Subject: Update docs for CoffeeScript -> JavaScript. --- CONTRIBUTING.md | 3 ++- doc/development/gotchas.md | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14ff05c9aa3..dfb35fa6e88 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -460,7 +460,8 @@ merge request: - string literal quoting style **Option A**: single quoted by default 1. [Rails](https://github.com/bbatsov/rails-style-guide) 1. [Testing](doc/development/testing.md) -1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style/coffeescript) +1. [JavaScript (ES6)](https://github.com/airbnb/javascript) +1. [JavaScript (ES5)](https://github.com/airbnb/javascript/tree/master/es5) 1. [SCSS styleguide][scss-styleguide] 1. [Shell commands](doc/development/shell_commands.md) created by GitLab contributors to enhance security diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md index 9d7fe7440d2..fc52027ab94 100644 --- a/doc/development/gotchas.md +++ b/doc/development/gotchas.md @@ -41,10 +41,10 @@ Rubocop](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/.rubocop.yml#L9 [Exception]: http://stackoverflow.com/q/10048173/223897 -## Don't use inline CoffeeScript in views +## Don't use inline CoffeeScript/Javascript in views Using the inline `:coffee` or `:coffeescript` Haml filters comes with a -performance overhead. +performance overhead. Using inline Javascript is not a good way to structure your code and should be avoided. _**Note:** We've [removed these two filters](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/initializers/hamlit.rb) in an initializer._ @@ -52,6 +52,7 @@ in an initializer._ ### Further reading - Pull Request: [Replace CoffeeScript block into JavaScript in Views](https://git.io/vztMu) +- Stack Overflow: [Why you should not write inline JavaScript](http://programmers.stackexchange.com/questions/86589/why-should-i-avoid-inline-scripting) - Stack Overflow: [Performance implications of using :coffescript filter inside HAML templates?](http://stackoverflow.com/a/17571242/223897) ## ID-based CSS selectors need to be a bit more specific -- cgit v1.2.1 From d63263ee1c0477394cc370054ba93ae4f2a698dd Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Fri, 22 Jul 2016 11:02:39 -0400 Subject: Update docs for CoffeeScript migration --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index fa272755033..36e99a3d3b1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ v 8.11.0 (unreleased) - Limit git rev-list output count to one in forced push check - Retrieve rendered HTML from cache in one request - Load project invited groups and members eagerly in ProjectTeam#fetch_members + - Updated docs for CoffeeScript migration. v 8.10.0 - Fix profile activity heatmap to show correct day name (eanplatter) -- cgit v1.2.1 From 95ea2119bdcba43f08a5181c332207568a4e80ca Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Fri, 22 Jul 2016 14:14:01 -0600 Subject: Update attr_encrypted from 3.0.1 to 3.0.3 This removes methods that were deprecated by Rails 5. Changelog: https://github.com/attr-encrypted/attr_encrypted/blob/master/CHANGELOG.md#303 --- Gemfile.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Gemfile.lock b/Gemfile.lock index 195516d1bf1..157cdf1b843 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -59,7 +59,7 @@ GEM oauth2 (~> 1.0) asciidoctor (1.5.3) ast (2.3.0) - attr_encrypted (3.0.1) + attr_encrypted (3.0.3) encryptor (~> 3.0.0) attr_required (1.0.0) autoprefixer-rails (6.2.3) -- cgit v1.2.1 From 5495166bf5c00e91a7d4b9aa15c3aed083dbc290 Mon Sep 17 00:00:00 2001 From: Mark Pundsack Date: Mon, 25 Jul 2016 12:32:14 -0700 Subject: Clarify CI script needing to quote colons --- doc/ci/yaml/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index ea3fff1596e..189ce652a66 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -379,6 +379,8 @@ job: - bundle exec rspec ``` +Sometimes, `script` commands will need to be wrapped in single or double quotes. For example, commands that contain a colon (`:`) need to be wrapped in quotes so that the YAML parser knows to interpret the whole thing as a string rather than a "key: value" pair. Be careful when using special characters (`:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `\``). + ### stage `stage` allows to group build into different stages. Builds of the same `stage` -- cgit v1.2.1 From 69b5c92e608924684cb2bad07cd56bd33f100bf2 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 26 Jul 2016 16:19:37 +0800 Subject: Fix duplicated issues partial and two cases need the button: For both having existing issues and having no issues. --- app/views/projects/issues/index.html.haml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 3742f61a1f8..6b7c156b5ae 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -25,6 +25,11 @@ .issues-holder = render "issues" + - if new_issue_email + .issues-footer.text-center + %button.issue-email-modal-btn{ type: "button", data: { toggle: "modal", target: "#issue-email-modal" } } + Email a new issue to this project + = render "issue_by_email", email: new_issue_email - else .blank-state.blank-state-welcome %h2.blank-state-title.blank-state-welcome-title @@ -41,13 +46,8 @@ - if can? current_user, :create_issue, @project = link_to new_namespace_project_issue_path(@project.namespace, @project), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do New Issue - - = render 'shared/issuable/filter', type: :issues - - .issues-holder - = render "issues" - - if new_issue_email - .issues-footer.text-center - %button.issue-email-modal-btn{ type: "button", data: { toggle: "modal", target: "#issue-email-modal" } } - Email a new issue to this project - = render "issue_by_email", email: new_issue_email + - if new_issue_email + .issues-footer.text-center + %button.issue-email-modal-btn{ type: "button", data: { toggle: "modal", target: "#issue-email-modal" } } + Email a new issue to this project + = render "issue_by_email", email: new_issue_email -- cgit v1.2.1 From 189e3b575a739c65665af172d607eded967d348f Mon Sep 17 00:00:00 2001 From: David Eisner Date: Wed, 6 Jan 2016 16:01:48 +0000 Subject: HipChat notification color overridden by build status --- app/models/project_services/hipchat_service.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 23e5b16221b..413200199b4 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -46,7 +46,7 @@ class HipchatService < Service return unless supported_events.include?(data[:object_kind]) message = create_message(data) return unless message.present? - gate[room].send('GitLab', message, message_options) + gate[room].send('GitLab', message, message_options(data)) end def test(data) @@ -67,8 +67,8 @@ class HipchatService < Service @gate ||= HipChat::Client.new(token, options) end - def message_options - { notify: notify.present? && notify == '1', color: color || 'yellow' } + def message_options(data) + { notify: notify.present? && notify == '1', color: build_status_color(data) || color || 'yellow' } end def create_message(data) @@ -240,6 +240,14 @@ class HipchatService < Service "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)" end + def build_status_color(data) + if data[:commit][:status] == 'success' + 'green' + else + 'red' + end if data[:object_kind] == 'build' + end + def project_name project.name_with_namespace.gsub(/\s/, '') end -- cgit v1.2.1 From 255162e194918d1d9c3cd7c0c6d3927e3f2006bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 26 Jul 2016 11:55:00 +0200 Subject: Little refactor, add specs, and a CHANGELOG entry MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + app/models/project_services/hipchat_service.rb | 9 ++++--- .../project_services/hipchat_service_spec.rb | 30 +++++++++++++++++----- 3 files changed, 31 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 181829a86a5..bae44702fc8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 8.11.0 (unreleased) - Add green outline to New Branch button. !5447 (winniehell) - Retrieve rendered HTML from cache in one request - Nokogiri's various parsing methods are now instrumented + - Add build event color in HipChat messages (David Eisner) - Make fork counter always clickable. !5463 (winniehell) - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le) - Load project invited groups and members eagerly in `ProjectTeam#fetch_members` diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 413200199b4..4fc8d640310 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -67,7 +67,7 @@ class HipchatService < Service @gate ||= HipChat::Client.new(token, options) end - def message_options(data) + def message_options(data = nil) { notify: notify.present? && notify == '1', color: build_status_color(data) || color || 'yellow' } end @@ -241,11 +241,14 @@ class HipchatService < Service end def build_status_color(data) - if data[:commit][:status] == 'success' + return unless data && data[:object_kind] == 'build' + + case data[:commit][:status] + when 'success' 'green' else 'red' - end if data[:object_kind] == 'build' + end end def project_name diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index 5f618322aab..62ae5f6cf74 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -340,18 +340,36 @@ describe HipchatService, models: true do end context "#message_options" do - it "should be set to the defaults" do - expect(hipchat.send(:message_options)).to eq({ notify: false, color: 'yellow' }) + it "is set to the defaults" do + expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'yellow' }) end - it "should set notfiy to true" do + it "sets notify to true" do allow(hipchat).to receive(:notify).and_return('1') - expect(hipchat.send(:message_options)).to eq({ notify: true, color: 'yellow' }) + + expect(hipchat.__send__(:message_options)).to eq({ notify: true, color: 'yellow' }) end - it "should set the color" do + it "sets the color" do allow(hipchat).to receive(:color).and_return('red') - expect(hipchat.send(:message_options)).to eq({ notify: false, color: 'red' }) + + expect(hipchat.__send__(:message_options)).to eq({ notify: false, color: 'red' }) + end + + context 'with a successful build' do + it 'uses the green color' do + build_data = { object_kind: 'build', commit: { status: 'success' } } + + expect(hipchat.__send__(:message_options, build_data)).to eq({ notify: false, color: 'green' }) + end + end + + context 'with a failed build' do + it 'uses the red color' do + build_data = { object_kind: 'build', commit: { status: 'failed' } } + + expect(hipchat.__send__(:message_options, build_data)).to eq({ notify: false, color: 'red' }) + end end end end -- cgit v1.2.1 From 53e64926b45aca0d66e19815398434d40ed13b1c Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 26 Jul 2016 19:00:36 +0800 Subject: Test if we're showing the modal and has the right email --- spec/features/issues_spec.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index d51c9abea19..af5e90dc803 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -524,6 +524,25 @@ describe 'Issues', feature: true do end end + describe 'new issue by email' do + context 'find the modal for new issue by email', js: true do + before do + stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") + + visit namespace_project_issues_path(project.namespace, project) + click_button('Email a new issue') + end + + it 'has a button to show the email address' do + page.within '#issue-email-modal' do + email = project.new_issue_address(@user) + + expect(page).to have_selector("input[value='#{email}']") + end + end + end + end + describe 'due date' do context 'update due on issue#show', js: true do let(:issue) { create(:issue, project: project, author: @user, assignee: @user) } -- cgit v1.2.1 From 1b1e3ae42507e467ebc539068d423a718a4b1e75 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 26 Jul 2016 19:01:58 +0800 Subject: Update the description a bit --- spec/features/issues_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index af5e90dc803..9ceefc9150b 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -525,7 +525,7 @@ describe 'Issues', feature: true do end describe 'new issue by email' do - context 'find the modal for new issue by email', js: true do + context 'click the button to show modal for new issue email', js: true do before do stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") @@ -533,7 +533,7 @@ describe 'Issues', feature: true do click_button('Email a new issue') end - it 'has a button to show the email address' do + it 'shows the email in the modal' do page.within '#issue-email-modal' do email = project.new_issue_address(@user) -- cgit v1.2.1 From 2e7641e02e0f48e392732b96b43dd3c1b5c7398d Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Mon, 25 Jul 2016 11:10:10 -0500 Subject: Replace ci icons with slightly thicker ones --- app/views/shared/icons/_icon_status_cancel.svg | 14 ++++---------- app/views/shared/icons/_icon_status_failed.svg | 14 ++++---------- app/views/shared/icons/_icon_status_pending.svg | 15 ++++----------- app/views/shared/icons/_icon_status_running.svg | 14 ++++---------- app/views/shared/icons/_icon_status_success.svg | 17 ++++------------- app/views/shared/icons/_icon_status_warning.svg | 17 ++++------------- 6 files changed, 24 insertions(+), 67 deletions(-) diff --git a/app/views/shared/icons/_icon_status_cancel.svg b/app/views/shared/icons/_icon_status_cancel.svg index 6a0bc1490c4..fd1ebbcbabd 100644 --- a/app/views/shared/icons/_icon_status_cancel.svg +++ b/app/views/shared/icons/_icon_status_cancel.svg @@ -1,12 +1,6 @@ - - - - - - - - - - + + + + diff --git a/app/views/shared/icons/_icon_status_failed.svg b/app/views/shared/icons/_icon_status_failed.svg index c41ca18cae7..e56e0887416 100644 --- a/app/views/shared/icons/_icon_status_failed.svg +++ b/app/views/shared/icons/_icon_status_failed.svg @@ -1,12 +1,6 @@ - - - - - - - - - - + + + + diff --git a/app/views/shared/icons/_icon_status_pending.svg b/app/views/shared/icons/_icon_status_pending.svg index 035cd8b4ccc..117f0367161 100644 --- a/app/views/shared/icons/_icon_status_pending.svg +++ b/app/views/shared/icons/_icon_status_pending.svg @@ -1,13 +1,6 @@ - - - - - - - - - - - + + + + diff --git a/app/views/shared/icons/_icon_status_running.svg b/app/views/shared/icons/_icon_status_running.svg index a48b3a25099..920d7952eb5 100644 --- a/app/views/shared/icons/_icon_status_running.svg +++ b/app/views/shared/icons/_icon_status_running.svg @@ -1,12 +1,6 @@ - - - - - - - - - - + + + + diff --git a/app/views/shared/icons/_icon_status_success.svg b/app/views/shared/icons/_icon_status_success.svg index 260eab013a3..67b378b3571 100644 --- a/app/views/shared/icons/_icon_status_success.svg +++ b/app/views/shared/icons/_icon_status_success.svg @@ -1,15 +1,6 @@ - - - - - - - - - - - - - + + + + diff --git a/app/views/shared/icons/_icon_status_warning.svg b/app/views/shared/icons/_icon_status_warning.svg index d47e7a1c93f..d0ad4bd65b1 100644 --- a/app/views/shared/icons/_icon_status_warning.svg +++ b/app/views/shared/icons/_icon_status_warning.svg @@ -1,15 +1,6 @@ - - - - - - - - - - - - - + + + + -- cgit v1.2.1 From 9116ec57bba8ede272bf167954e7e903a687c8ac Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Fri, 22 Jul 2016 14:40:29 -0600 Subject: Upgrade Bullet from 5.0.0 to 5.2.0. Removes methods that were deprecated in Rails 5. Changelog: https://github.com/flyerhzm/bullet/blob/master/CHANGELOG.md#520 --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index 0504e643ed7..25704cf2e17 100644 --- a/Gemfile +++ b/Gemfile @@ -252,7 +252,7 @@ group :development do gem 'letter_opener_web', '~> 1.3.0' gem 'rerun', '~> 0.11.0' - gem 'bullet', '~> 5.0.0', require: false + gem 'bullet', '~> 5.2.0', require: false gem 'rblineprof', '~> 0.3.6', platform: :mri, require: false gem 'web-console', '~> 2.0' diff --git a/Gemfile.lock b/Gemfile.lock index 195516d1bf1..c69906cd2c4 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -100,9 +100,9 @@ GEM brakeman (3.3.2) browser (2.2.0) builder (3.2.2) - bullet (5.0.0) + bullet (5.2.0) activesupport (>= 3.0.0) - uniform_notifier (~> 1.9.0) + uniform_notifier (~> 1.10.0) bundler-audit (0.5.0) bundler (~> 1.2) thor (~> 0.18) @@ -766,7 +766,7 @@ GEM unicorn-worker-killer (0.4.4) get_process_mem (~> 0) unicorn (>= 4, < 6) - uniform_notifier (1.9.0) + uniform_notifier (1.10.0) uuid (2.3.8) macaddr (~> 1.0) version_sorter (2.0.0) @@ -821,7 +821,7 @@ DEPENDENCIES bootstrap-sass (~> 3.3.0) brakeman (~> 3.3.0) browser (~> 2.2) - bullet (~> 5.0.0) + bullet (~> 5.2.0) bundler-audit (~> 0.5.0) byebug (~> 8.2.1) capybara (~> 2.6.2) -- cgit v1.2.1 From 105e51c1dc774f443a9097d6b4acbfae4047403c Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 26 Jul 2016 14:25:42 -0500 Subject: Line tooltip up with icon --- app/assets/stylesheets/pages/commits.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 0298577c494..cbc980f52ff 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -61,6 +61,10 @@ font-size: 0; } + .ci-status-link { + display: inline-block; + } + .btn-clipboard, .btn-transparent { padding-left: 0; padding-right: 0; -- cgit v1.2.1 From 9c34fafb8b728358a516a25120aa5f28567eae48 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Sat, 16 Jul 2016 11:42:44 -0500 Subject: Submit new issues created via the WebUI by non project members to Akismet for spam check. --- CHANGELOG | 1 + app/controllers/projects/issues_controller.rb | 18 ++++++++++++- doc/integration/akismet.md | 3 +++ lib/api/issues.rb | 13 +-------- lib/gitlab/akismet_helper.rb | 11 ++++++++ .../controllers/projects/issues_controller_spec.rb | 31 ++++++++++++++++++++++ 6 files changed, 64 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dca9b209582..2efacc9e05b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -93,6 +93,7 @@ v 8.10.0 - Fix viewing notification settings when a project is pending deletion - Updated compare dropdown menus to use GL dropdown - Redirects back to issue after clicking login link + - Submit issues created via the WebUI by non project members to Akismet !5333 - Eager load award emoji on notes - Allow to define manual actions/builds on Pipelines and Environments - Fix pagination when sorting by columns with lots of ties (like priority) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index fa663c9bda4..10de472aad5 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -2,6 +2,7 @@ class Projects::IssuesController < Projects::ApplicationController include ToggleSubscriptionAction include IssuableActions include ToggleAwardEmoji + include Gitlab::AkismetHelper before_action :module_enabled before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests, @@ -79,6 +80,21 @@ class Projects::IssuesController < Projects::ApplicationController end def create + text = [params[:issue][:title], params[:issue][:description]].reject(&:blank?).join("\n") + + if check_for_spam?(project, current_user) && is_spam?(request.env, current_user, text) + attrs = { + user_id: current_user.id, + project_id: project.id, + title: params[:issue][:title], + description: params[:issue][:description] + } + create_spam_log(project, current_user, attrs, request.env, api: false) + @issue = @project.issues.new + flash[:notice] = 'Your issue has been recognized as spam and has been discarded.' + render :new and return + end + @issue = Issues::CreateService.new(project, current_user, issue_params).execute respond_to do |format| @@ -89,7 +105,7 @@ class Projects::IssuesController < Projects::ApplicationController render :new end end - format.js do |format| + format.js do @link = @issue.attachment.url.to_js end end diff --git a/doc/integration/akismet.md b/doc/integration/akismet.md index 5cc09bd536d..52c1b0a75d8 100644 --- a/doc/integration/akismet.md +++ b/doc/integration/akismet.md @@ -5,6 +5,9 @@ GitLab uses Akismet to prevent users who are not members of a project from creating spam via the GitLab API. Detected spam will be rejected, and an entry in the "Spam Log" section in the Admin page will be created. +> *Note:* As of 8.10 GitLab also submits issues created via the WebUI by non +project members to Akismet to prevent spam. + Privacy note: GitLab submits the user's IP and user agent to Akismet. Note that adding a user to a project will disable the Akismet check and prevent this from happening. diff --git a/lib/api/issues.rb b/lib/api/issues.rb index c588103e517..9adbde04884 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -21,17 +21,6 @@ module API def filter_issues_milestone(issues, milestone) issues.includes(:milestone).where('milestones.title' => milestone) end - - def create_spam_log(project, current_user, attrs) - params = attrs.merge({ - source_ip: client_ip(env), - user_agent: user_agent(env), - noteable_type: 'Issue', - via_api: true - }) - - ::CreateSpamLogService.new(project, current_user, params).execute - end end resource :issues do @@ -171,7 +160,7 @@ module API text = [attrs[:title], attrs[:description]].reject(&:blank?).join("\n") if check_for_spam?(project, current_user) && is_spam?(env, current_user, text) - create_spam_log(project, current_user, attrs) + create_spam_log(project, current_user, attrs, env) render_api_error!({ error: 'Spam detected' }, 400) end diff --git a/lib/gitlab/akismet_helper.rb b/lib/gitlab/akismet_helper.rb index 04676fdb748..fa8a79207f6 100644 --- a/lib/gitlab/akismet_helper.rb +++ b/lib/gitlab/akismet_helper.rb @@ -43,5 +43,16 @@ module Gitlab false end end + + def create_spam_log(project, current_user, attrs, env, api: true) + params = attrs.merge({ + source_ip: client_ip(env), + user_agent: user_agent(env), + noteable_type: 'Issue', + via_api: api + }) + + ::CreateSpamLogService.new(project, current_user, params).execute + end end end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 7cf09fa4a4a..77f65057f71 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -243,6 +243,37 @@ describe Projects::IssuesController do end end + describe 'POST #create' do + context 'Akismet is enabled' do + before do + allow_any_instance_of(Gitlab::AkismetHelper).to receive(:check_for_spam?).and_return(true) + allow_any_instance_of(Gitlab::AkismetHelper).to receive(:is_spam?).and_return(true) + end + + def post_spam_issue + sign_in(user) + spam_project = create(:empty_project, :public) + post :create, { + namespace_id: spam_project.namespace.to_param, + project_id: spam_project.to_param, + issue: { title: 'Spam Title', description: 'Spam lives here' } + } + end + + it 'rejects an issue recognized as spam' do + expect{ post_spam_issue }.not_to change(Issue, :count) + expect(response).to render_template(:new) + end + + it 'creates a spam log' do + post_spam_issue + spam_logs = SpamLog.all + expect(spam_logs.count).to eq(1) + expect(spam_logs[0].title).to eq('Spam Title') + end + end + end + describe "DELETE #destroy" do context "when the user is a developer" do before { sign_in(user) } -- cgit v1.2.1 From f7807c5b68b59f6a5b984ee64a6c82a3bd993d92 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Mon, 18 Jul 2016 18:17:43 -0500 Subject: Submit all issues on public projects to Akismet if enabled. --- CHANGELOG | 1 + app/controllers/projects/issues_controller.rb | 16 +++------------- app/services/issues/create_service.rb | 6 +++++- app/services/issues/spam_check_service.rb | 25 +++++++++++++++++++++++++ doc/integration/akismet.md | 12 +++++++----- lib/api/issues.rb | 8 +++----- lib/gitlab/akismet_helper.rb | 4 ++-- spec/lib/gitlab/akismet_helper_spec.rb | 12 ++++++------ spec/requests/api/issues_spec.rb | 6 ++---- 9 files changed, 54 insertions(+), 36 deletions(-) create mode 100644 app/services/issues/spam_check_service.rb diff --git a/CHANGELOG b/CHANGELOG index 2efacc9e05b..27e68431488 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -94,6 +94,7 @@ v 8.10.0 - Updated compare dropdown menus to use GL dropdown - Redirects back to issue after clicking login link - Submit issues created via the WebUI by non project members to Akismet !5333 + - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333 - Eager load award emoji on notes - Allow to define manual actions/builds on Pipelines and Environments - Fix pagination when sorting by columns with lots of ties (like priority) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 10de472aad5..b527dd0f4f2 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -2,7 +2,6 @@ class Projects::IssuesController < Projects::ApplicationController include ToggleSubscriptionAction include IssuableActions include ToggleAwardEmoji - include Gitlab::AkismetHelper before_action :module_enabled before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests, @@ -80,23 +79,14 @@ class Projects::IssuesController < Projects::ApplicationController end def create - text = [params[:issue][:title], params[:issue][:description]].reject(&:blank?).join("\n") - - if check_for_spam?(project, current_user) && is_spam?(request.env, current_user, text) - attrs = { - user_id: current_user.id, - project_id: project.id, - title: params[:issue][:title], - description: params[:issue][:description] - } - create_spam_log(project, current_user, attrs, request.env, api: false) + @issue = Issues::CreateService.new(project, current_user, issue_params.merge({ request: request })).execute + + if @issue.nil? @issue = @project.issues.new flash[:notice] = 'Your issue has been recognized as spam and has been discarded.' render :new and return end - @issue = Issues::CreateService.new(project, current_user, issue_params).execute - respond_to do |format| format.html do if @issue.valid? diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index e63e1af8766..496ea5a86a2 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -3,9 +3,13 @@ module Issues def execute filter_params label_params = params[:label_ids] - issue = project.issues.new(params.except(:label_ids)) + issue = project.issues.new(params.except(:label_ids, :request)) issue.author = params[:author] || current_user + if Issues::SpamCheckService.new(project, current_user, params).spam_detected? + return nil + end + if issue.save issue.update_attributes(label_ids: label_params) notification_service.new_issue(issue, current_user) diff --git a/app/services/issues/spam_check_service.rb b/app/services/issues/spam_check_service.rb new file mode 100644 index 00000000000..b8d4e37faf5 --- /dev/null +++ b/app/services/issues/spam_check_service.rb @@ -0,0 +1,25 @@ +module Issues + class SpamCheckService < BaseService + include Gitlab::AkismetHelper + + def spam_detected? + text = [params[:title], params[:description]].reject(&:blank?).join("\n") + request = params[:request] + + if request + if check_for_spam?(project) && is_spam?(request.env, current_user, text) + attrs = { + user_id: current_user.id, + project_id: project.id, + title: params[:title], + description: params[:description] + } + create_spam_log(project, current_user, attrs, request.env, api: false) + return true + end + end + + false + end + end +end diff --git a/doc/integration/akismet.md b/doc/integration/akismet.md index 52c1b0a75d8..99a28b493c9 100644 --- a/doc/integration/akismet.md +++ b/doc/integration/akismet.md @@ -1,12 +1,14 @@ # Akismet +> *Note:* Before 8.10 only issues submitted via the API and for non-project +members were submitted to Akismet. + GitLab leverages [Akismet](http://akismet.com) to protect against spam. Currently -GitLab uses Akismet to prevent users who are not members of a project from -creating spam via the GitLab API. Detected spam will be rejected, and -an entry in the "Spam Log" section in the Admin page will be created. +GitLab uses Akismet to prevent the creation of spam issues on public projects. Issues +created via the WebUI or the API can be submitted to Akismet for review. -> *Note:* As of 8.10 GitLab also submits issues created via the WebUI by non -project members to Akismet to prevent spam. +Detected spam will be rejected, and an entry in the "Spam Log" section in the +Admin page will be created. Privacy note: GitLab submits the user's IP and user agent to Akismet. Note that adding a user to a project will disable the Akismet check and prevent this diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 9adbde04884..787d416b960 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -157,15 +157,13 @@ module API end project = user_project - text = [attrs[:title], attrs[:description]].reject(&:blank?).join("\n") - if check_for_spam?(project, current_user) && is_spam?(env, current_user, text) - create_spam_log(project, current_user, attrs, env) + issue = ::Issues::CreateService.new(project, current_user, attrs.merge({ request: request })).execute + + if issue.nil? render_api_error!({ error: 'Spam detected' }, 400) end - issue = ::Issues::CreateService.new(project, current_user, attrs).execute - if issue.valid? # Find or create labels and attach to issue. Labels are valid because # we already checked its name, so there can't be an error here diff --git a/lib/gitlab/akismet_helper.rb b/lib/gitlab/akismet_helper.rb index fa8a79207f6..fc1fbc5b600 100644 --- a/lib/gitlab/akismet_helper.rb +++ b/lib/gitlab/akismet_helper.rb @@ -17,8 +17,8 @@ module Gitlab env['HTTP_USER_AGENT'] end - def check_for_spam?(project, user) - akismet_enabled? && !project.team.member?(user) + def check_for_spam?(project) + akismet_enabled? && project.public? end def is_spam?(environment, user, text) diff --git a/spec/lib/gitlab/akismet_helper_spec.rb b/spec/lib/gitlab/akismet_helper_spec.rb index 88a71528867..b08396da4d2 100644 --- a/spec/lib/gitlab/akismet_helper_spec.rb +++ b/spec/lib/gitlab/akismet_helper_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::AkismetHelper, type: :helper do - let(:project) { create(:project) } + let(:project) { create(:project, :public) } let(:user) { create(:user) } before do @@ -11,13 +11,13 @@ describe Gitlab::AkismetHelper, type: :helper do end describe '#check_for_spam?' do - it 'returns true for non-member' do - expect(helper.check_for_spam?(project, user)).to eq(true) + it 'returns true for public project' do + expect(helper.check_for_spam?(project)).to eq(true) end - it 'returns false for member' do - project.team << [user, :guest] - expect(helper.check_for_spam?(project, user)).to eq(false) + it 'returns false for private project' do + project.update_attribute(:visibility_level, Gitlab::VisibilityLevel::PRIVATE) + expect(helper.check_for_spam?(project)).to eq(false) end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 12f2cfa6942..9d3d28e0b91 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -531,10 +531,8 @@ describe API::API, api: true do describe 'POST /projects/:id/issues with spam filtering' do before do - Grape::Endpoint.before_each do |endpoint| - allow(endpoint).to receive(:check_for_spam?).and_return(true) - allow(endpoint).to receive(:is_spam?).and_return(true) - end + allow_any_instance_of(Gitlab::AkismetHelper).to receive(:check_for_spam?).and_return(true) + allow_any_instance_of(Gitlab::AkismetHelper).to receive(:is_spam?).and_return(true) end let(:params) do -- cgit v1.2.1 From 8f04cf0eadbcde7fc5d1c970741e30ca8b97967d Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 20 Jul 2016 20:15:17 -0500 Subject: Refactor `SpamCheckService` to make it cleaner and clearer. --- CHANGELOG | 1 - app/controllers/projects/issues_controller.rb | 8 +------- app/services/issues/create_service.rb | 5 +++-- app/services/issues/spam_check_service.rb | 25 ------------------------ app/services/spam_check_service.rb | 28 +++++++++++++++++++++++++++ lib/api/issues.rb | 10 ++++++---- 6 files changed, 38 insertions(+), 39 deletions(-) delete mode 100644 app/services/issues/spam_check_service.rb create mode 100644 app/services/spam_check_service.rb diff --git a/CHANGELOG b/CHANGELOG index 27e68431488..6a5b887b16e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -93,7 +93,6 @@ v 8.10.0 - Fix viewing notification settings when a project is pending deletion - Updated compare dropdown menus to use GL dropdown - Redirects back to issue after clicking login link - - Submit issues created via the WebUI by non project members to Akismet !5333 - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333 - Eager load award emoji on notes - Allow to define manual actions/builds on Pipelines and Environments diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index b527dd0f4f2..f11e3fac959 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -81,15 +81,9 @@ class Projects::IssuesController < Projects::ApplicationController def create @issue = Issues::CreateService.new(project, current_user, issue_params.merge({ request: request })).execute - if @issue.nil? - @issue = @project.issues.new - flash[:notice] = 'Your issue has been recognized as spam and has been discarded.' - render :new and return - end - respond_to do |format| format.html do - if @issue.valid? + if @issue.errors.empty? && @issue.valid? redirect_to issue_path(@issue) else render :new diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index 496ea5a86a2..1085a1b93b8 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -6,8 +6,9 @@ module Issues issue = project.issues.new(params.except(:label_ids, :request)) issue.author = params[:author] || current_user - if Issues::SpamCheckService.new(project, current_user, params).spam_detected? - return nil + if SpamCheckService.new(project, current_user, params).spam_detected? + issue.errors.add(:base, 'Your issue has been recognized as spam and has been discarded.') + return issue end if issue.save diff --git a/app/services/issues/spam_check_service.rb b/app/services/issues/spam_check_service.rb deleted file mode 100644 index b8d4e37faf5..00000000000 --- a/app/services/issues/spam_check_service.rb +++ /dev/null @@ -1,25 +0,0 @@ -module Issues - class SpamCheckService < BaseService - include Gitlab::AkismetHelper - - def spam_detected? - text = [params[:title], params[:description]].reject(&:blank?).join("\n") - request = params[:request] - - if request - if check_for_spam?(project) && is_spam?(request.env, current_user, text) - attrs = { - user_id: current_user.id, - project_id: project.id, - title: params[:title], - description: params[:description] - } - create_spam_log(project, current_user, attrs, request.env, api: false) - return true - end - end - - false - end - end -end diff --git a/app/services/spam_check_service.rb b/app/services/spam_check_service.rb new file mode 100644 index 00000000000..6768047aa63 --- /dev/null +++ b/app/services/spam_check_service.rb @@ -0,0 +1,28 @@ +class SpamCheckService + include Gitlab::AkismetHelper + + attr_accessor :subject, :current_user, :params + + def initialize(subject, user, params = {}) + @subject, @current_user, @params = subject, user, params.dup + end + + def spam_detected? + request = params[:request] + return false unless request || check_for_spam?(subject) + + text = [params[:title], params[:description]].reject(&:blank?).join("\n") + + return false unless is_spam?(request.env, current_user, text) + + attrs = { + user_id: current_user.id, + project_id: subject.id, + title: params[:title], + description: params[:description] + } + create_spam_log(subject, current_user, attrs, request.env, api: false) + + true + end +end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 787d416b960..21b9eb367e7 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -160,11 +160,13 @@ module API issue = ::Issues::CreateService.new(project, current_user, attrs.merge({ request: request })).execute - if issue.nil? - render_api_error!({ error: 'Spam detected' }, 400) - end - if issue.valid? + # Need to check if id is nil here, because if issue is spam, errors + # get added, but Rails still thinks it's valid, but it is never saved + # so id will be nil + if issue.id.nil? + render_api_error!({ error: 'Spam detected' }, 400) + end # Find or create labels and attach to issue. Labels are valid because # we already checked its name, so there can't be an error here if params[:labels].present? -- cgit v1.2.1 From f01fce7f4683e06e83d3f91d38ca5b749e27e7ec Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Thu, 21 Jul 2016 18:11:53 -0500 Subject: Refactor spam validation to a concern that can be easily reused and improve legibility in `SpamCheckService` --- CHANGELOG | 2 +- app/controllers/projects/issues_controller.rb | 4 +-- app/models/concerns/spammable.rb | 16 ++++++++++ app/models/issue.rb | 1 + app/services/issues/create_service.rb | 17 +++++++---- app/services/spam_check_service.rb | 42 +++++++++++++++++---------- doc/integration/akismet.md | 2 +- lib/api/issues.rb | 12 ++++---- lib/gitlab/akismet_helper.rb | 11 ------- 9 files changed, 63 insertions(+), 44 deletions(-) create mode 100644 app/models/concerns/spammable.rb diff --git a/CHANGELOG b/CHANGELOG index 6a5b887b16e..a1e233a6539 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.11.0 (unreleased) - Retrieve rendered HTML from cache in one request - Nokogiri's various parsing methods are now instrumented - Make fork counter always clickable. !5463 (winniehell) + - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333 - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le) - Load project invited groups and members eagerly in `ProjectTeam#fetch_members` - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) @@ -93,7 +94,6 @@ v 8.10.0 - Fix viewing notification settings when a project is pending deletion - Updated compare dropdown menus to use GL dropdown - Redirects back to issue after clicking login link - - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333 - Eager load award emoji on notes - Allow to define manual actions/builds on Pipelines and Environments - Fix pagination when sorting by columns with lots of ties (like priority) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index f11e3fac959..d169b408b41 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -79,11 +79,11 @@ class Projects::IssuesController < Projects::ApplicationController end def create - @issue = Issues::CreateService.new(project, current_user, issue_params.merge({ request: request })).execute + @issue = Issues::CreateService.new(project, current_user, issue_params.merge(request: request)).execute respond_to do |format| format.html do - if @issue.errors.empty? && @issue.valid? + if @issue.valid? redirect_to issue_path(@issue) else render :new diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb new file mode 100644 index 00000000000..3b8e6df2da9 --- /dev/null +++ b/app/models/concerns/spammable.rb @@ -0,0 +1,16 @@ +module Spammable + extend ActiveSupport::Concern + + included do + attr_accessor :spam + after_validation :check_for_spam, on: :create + end + + def spam? + @spam + end + + def check_for_spam + self.errors.add(:base, "Your #{self.class.name.underscore} has been recognized as spam and has been discarded.") if spam? + end +end diff --git a/app/models/issue.rb b/app/models/issue.rb index 60af8c15340..d9428ebc9fb 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -6,6 +6,7 @@ class Issue < ActiveRecord::Base include Referable include Sortable include Taskable + include Spammable DueDateStruct = Struct.new(:title, :name).freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb index 1085a1b93b8..5e2de2ccf64 100644 --- a/app/services/issues/create_service.rb +++ b/app/services/issues/create_service.rb @@ -2,14 +2,13 @@ module Issues class CreateService < Issues::BaseService def execute filter_params - label_params = params[:label_ids] - issue = project.issues.new(params.except(:label_ids, :request)) + label_params = params.delete(:label_ids) + request = params.delete(:request) + api = params.delete(:api) + issue = project.issues.new(params) issue.author = params[:author] || current_user - if SpamCheckService.new(project, current_user, params).spam_detected? - issue.errors.add(:base, 'Your issue has been recognized as spam and has been discarded.') - return issue - end + issue.spam = spam_check_service.execute(request, api) if issue.save issue.update_attributes(label_ids: label_params) @@ -22,5 +21,11 @@ module Issues issue end + + private + + def spam_check_service + SpamCheckService.new(project, current_user, params) + end end end diff --git a/app/services/spam_check_service.rb b/app/services/spam_check_service.rb index 6768047aa63..7c3e692bde9 100644 --- a/app/services/spam_check_service.rb +++ b/app/services/spam_check_service.rb @@ -1,28 +1,38 @@ -class SpamCheckService +class SpamCheckService < BaseService include Gitlab::AkismetHelper - attr_accessor :subject, :current_user, :params + attr_accessor :request, :api - def initialize(subject, user, params = {}) - @subject, @current_user, @params = subject, user, params.dup - end + def execute(request, api) + @request, @api = request, api + return false unless request || check_for_spam?(project) + return false unless is_spam?(request.env, current_user, text) + + create_spam_log - def spam_detected? - request = params[:request] - return false unless request || check_for_spam?(subject) + true + end - text = [params[:title], params[:description]].reject(&:blank?).join("\n") + private - return false unless is_spam?(request.env, current_user, text) - - attrs = { + def text + [params[:title], params[:description]].reject(&:blank?).join("\n") + end + + def spam_log_attrs + { user_id: current_user.id, - project_id: subject.id, + project_id: project.id, title: params[:title], - description: params[:description] + description: params[:description], + source_ip: client_ip(request.env), + user_agent: user_agent(request.env), + noteable_type: 'Issue', + via_api: api } - create_spam_log(subject, current_user, attrs, request.env, api: false) + end - true + def create_spam_log + CreateSpamLogService.new(project, current_user, spam_log_attrs).execute end end diff --git a/doc/integration/akismet.md b/doc/integration/akismet.md index 99a28b493c9..c222d21612f 100644 --- a/doc/integration/akismet.md +++ b/doc/integration/akismet.md @@ -1,6 +1,6 @@ # Akismet -> *Note:* Before 8.10 only issues submitted via the API and for non-project +> *Note:* Before 8.11 only issues submitted via the API and for non-project members were submitted to Akismet. GitLab leverages [Akismet](http://akismet.com) to protect against spam. Currently diff --git a/lib/api/issues.rb b/lib/api/issues.rb index 21b9eb367e7..c4d3134da6c 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -158,15 +158,13 @@ module API project = user_project - issue = ::Issues::CreateService.new(project, current_user, attrs.merge({ request: request })).execute + issue = ::Issues::CreateService.new(project, current_user, attrs.merge(request: request, api: true)).execute + + if issue.spam? + render_api_error!({ error: 'Spam detected' }, 400) + end if issue.valid? - # Need to check if id is nil here, because if issue is spam, errors - # get added, but Rails still thinks it's valid, but it is never saved - # so id will be nil - if issue.id.nil? - render_api_error!({ error: 'Spam detected' }, 400) - end # Find or create labels and attach to issue. Labels are valid because # we already checked its name, so there can't be an error here if params[:labels].present? diff --git a/lib/gitlab/akismet_helper.rb b/lib/gitlab/akismet_helper.rb index fc1fbc5b600..207736b59db 100644 --- a/lib/gitlab/akismet_helper.rb +++ b/lib/gitlab/akismet_helper.rb @@ -43,16 +43,5 @@ module Gitlab false end end - - def create_spam_log(project, current_user, attrs, env, api: true) - params = attrs.merge({ - source_ip: client_ip(env), - user_agent: user_agent(env), - noteable_type: 'Issue', - via_api: api - }) - - ::CreateSpamLogService.new(project, current_user, params).execute - end end end -- cgit v1.2.1 From f00dc0fcae6cf482c1bf60065bd2c1ecfaa9748d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 27 Jul 2016 10:05:57 +0200 Subject: Depened on exact version of SimpleCov when patched --- Gemfile | 2 +- Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 35dd69ef408..cc99b5b39fe 100644 --- a/Gemfile +++ b/Gemfile @@ -302,7 +302,7 @@ group :development, :test do gem 'rubocop', '~> 0.41.2', require: false gem 'rubocop-rspec', '~> 1.5.0', require: false gem 'scss_lint', '~> 0.47.0', require: false - gem 'simplecov', '~> 0.12.0', require: false + gem 'simplecov', '0.12.0', require: false gem 'flog', '~> 4.3.2', require: false gem 'flay', '~> 2.6.1', require: false gem 'bundler-audit', '~> 0.5.0', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 3ec11f46a5c..b53deb8c233 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -952,7 +952,7 @@ DEPENDENCIES shoulda-matchers (~> 2.8.0) sidekiq (~> 4.0) sidekiq-cron (~> 0.4.0) - simplecov (~> 0.12.0) + simplecov (= 0.12.0) sinatra (~> 1.4.4) six (~> 0.2.0) slack-notifier (~> 1.2.0) -- cgit v1.2.1 From 386478d800e2e098371d27817ebda6627d6838bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 27 Jul 2016 11:29:05 +0200 Subject: Move color-logic into HipchatService#HipchatService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/project_services/hipchat_service.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 4fc8d640310..d7c986c1a91 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -68,7 +68,7 @@ class HipchatService < Service end def message_options(data = nil) - { notify: notify.present? && notify == '1', color: build_status_color(data) || color || 'yellow' } + { notify: notify.present? && notify == '1', color: message_color(data) } end def create_message(data) @@ -240,6 +240,10 @@ class HipchatService < Service "#{project_link}: Commit #{commit_link} of #{branch_link} #{ref_type} by #{user_name} #{humanized_status(status)} in #{duration} second(s)" end + def message_color(data) + build_status_color(data) || color || 'yellow' + end + def build_status_color(data) return unless data && data[:object_kind] == 'build' -- cgit v1.2.1 From ae0942575479ad1694766d0ec5c4dd61b2bb3550 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 27 Jul 2016 18:06:47 +0800 Subject: Add CHANGELOG entry for creating new issues from email --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 534f57cb08e..a8892c82819 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.11.0 (unreleased) - Nokogiri's various parsing methods are now instrumented - Make fork counter always clickable !5463 (winniehell) - Load project invited groups and members eagerly in ProjectTeam#fetch_members + - Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363 - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) - Add ES6 gem -- cgit v1.2.1 From 7d3c98e75497f5bb937dda0905b81e42cfe87f23 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 27 Jul 2016 18:07:11 +0800 Subject: Merge some repeated codes to the partial --- app/views/projects/issues/_issue_by_email.html.haml | 4 ++++ app/views/projects/issues/index.html.haml | 12 +++--------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/views/projects/issues/_issue_by_email.html.haml b/app/views/projects/issues/_issue_by_email.html.haml index 72022881c09..72669372497 100644 --- a/app/views/projects/issues/_issue_by_email.html.haml +++ b/app/views/projects/issues/_issue_by_email.html.haml @@ -1,3 +1,7 @@ +.issues-footer.text-center + %button.issue-email-modal-btn{ type: "button", data: { toggle: "modal", target: "#issue-email-modal" } } + Email a new issue to this project + #issue-email-modal.modal.fade{ tabindex: "-1", role: "dialog" } .modal-dialog{ role: "document" } .modal-content diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 6b7c156b5ae..d0edd2f22ec 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -24,12 +24,9 @@ = render 'shared/issuable/filter', type: :issues .issues-holder - = render "issues" + = render 'issues' - if new_issue_email - .issues-footer.text-center - %button.issue-email-modal-btn{ type: "button", data: { toggle: "modal", target: "#issue-email-modal" } } - Email a new issue to this project - = render "issue_by_email", email: new_issue_email + = render 'issue_by_email', email: new_issue_email - else .blank-state.blank-state-welcome %h2.blank-state-title.blank-state-welcome-title @@ -47,7 +44,4 @@ = link_to new_namespace_project_issue_path(@project.namespace, @project), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do New Issue - if new_issue_email - .issues-footer.text-center - %button.issue-email-modal-btn{ type: "button", data: { toggle: "modal", target: "#issue-email-modal" } } - Email a new issue to this project - = render "issue_by_email", email: new_issue_email + = render 'issue_by_email', email: new_issue_email -- cgit v1.2.1 From d2b026f09288fd1abd3ad5dcf27816189c69c729 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 27 Jul 2016 18:07:24 +0800 Subject: Test both for having existing issues or not --- spec/features/issues_spec.rb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 9ceefc9150b..93dcb2ec3fc 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -525,7 +525,7 @@ describe 'Issues', feature: true do end describe 'new issue by email' do - context 'click the button to show modal for new issue email', js: true do + shared_examples 'show the email in the modal' do before do stub_incoming_email_setting(enabled: true, address: "p+%{key}@gl.ab") @@ -533,7 +533,7 @@ describe 'Issues', feature: true do click_button('Email a new issue') end - it 'shows the email in the modal' do + it 'click the button to show modal for the new email' do page.within '#issue-email-modal' do email = project.new_issue_address(@user) @@ -541,6 +541,16 @@ describe 'Issues', feature: true do end end end + + context 'with existing issues' do + let!(:issue) { create(:issue, project: project, author: @user) } + + it_behaves_like 'show the email in the modal' + end + + context 'without existing issues' do + it_behaves_like 'show the email in the modal' + end end describe 'due date' do -- cgit v1.2.1 From fced4b6e50f6bc70b2de06573003b47ced2f2cdc Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Thu, 30 Jun 2016 00:50:33 +0900 Subject: Make error pages responsive design --- public/404.html | 24 +++++++++++++++++------- public/422.html | 24 +++++++++++++++++------- public/500.html | 24 +++++++++++++++++------- public/502.html | 24 +++++++++++++++++------- public/503.html | 24 +++++++++++++++++------- public/deploy.html | 22 ++++++++++++++++------ 6 files changed, 101 insertions(+), 41 deletions(-) diff --git a/public/404.html b/public/404.html index 4862770cc2a..d9052221f45 100644 --- a/public/404.html +++ b/public/404.html @@ -1,14 +1,13 @@ + The page you're looking for could not be found (404) @@ -47,9 +55,11 @@
404 -

The page you're looking for could not be found.

-
-

Make sure the address is correct and that the page hasn't moved.

-

Please contact your GitLab administrator if you think this is a mistake.

+
+

The page you're looking for could not be found.

+
+

Make sure the address is correct and that the page hasn't moved.

+

Please contact your GitLab administrator if you think this is a mistake.

+
diff --git a/public/422.html b/public/422.html index 055b0bde165..b6986482daa 100644 --- a/public/422.html +++ b/public/422.html @@ -1,14 +1,13 @@ + The change you requested was rejected (422) @@ -47,9 +55,11 @@
422 -

The change you requested was rejected.

-
-

Make sure you have access to the thing you tried to change.

-

Please contact your GitLab administrator if you think this is a mistake.

+
+

The change you requested was rejected.

+
+

Make sure you have access to the thing you tried to change.

+

Please contact your GitLab administrator if you think this is a mistake.

+
diff --git a/public/500.html b/public/500.html index 3d59d1392f5..0c69ab5b19e 100644 --- a/public/500.html +++ b/public/500.html @@ -1,14 +1,13 @@ + Something went wrong (500) @@ -46,9 +54,11 @@
500 -

Whoops, something went wrong on our end.

-
-

Try refreshing the page, or going back and attempting the action again.

-

Please contact your GitLab administrator if this problem persists.

+
+

Whoops, something went wrong on our end.

+
+

Try refreshing the page, or going back and attempting the action again.

+

Please contact your GitLab administrator if this problem persists.

+
diff --git a/public/502.html b/public/502.html index 67dfd8a2743..0f2a310d34c 100644 --- a/public/502.html +++ b/public/502.html @@ -1,14 +1,13 @@ + GitLab is not responding (502) @@ -46,9 +54,11 @@
502 -

Whoops, GitLab is taking too much time to respond.

-
-

Try refreshing the page, or going back and attempting the action again.

-

Please contact your GitLab administrator if this problem persists.

+
+

Whoops, GitLab is taking too much time to respond.

+
+

Try refreshing the page, or going back and attempting the action again.

+

Please contact your GitLab administrator if this problem persists.

+
diff --git a/public/503.html b/public/503.html index 6ab1185658d..c26da8354ea 100644 --- a/public/503.html +++ b/public/503.html @@ -1,14 +1,13 @@ + GitLab is not responding (503) @@ -46,9 +54,11 @@ GitLab Logo
503 -

Whoops, GitLab is currently unavailable.

-
-

Try refreshing the page, or going back and attempting the action again.

-

Please contact your GitLab administrator if this problem persists.

+
+

Whoops, GitLab is currently unavailable.

+
+

Try refreshing the page, or going back and attempting the action again.

+

Please contact your GitLab administrator if this problem persists.

+
diff --git a/public/deploy.html b/public/deploy.html index 48976dacf41..def717a1da4 100644 --- a/public/deploy.html +++ b/public/deploy.html @@ -1,14 +1,13 @@ + Deploy in progress @@ -47,8 +55,10 @@
Deploy in progress -

Please try again in a few minutes.

-
-

Please contact your GitLab administrator if this problem persists.

+
+

Please try again in a few minutes.

+
+

Please contact your GitLab administrator if this problem persists.

+
-- cgit v1.2.1 From 175a910b2a0ca9a61a8646c5577ccd902df7d416 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Thu, 30 Jun 2016 01:19:44 +0900 Subject: Unify HTML format in static error pages --- public/404.html | 78 +++++++++++++++++++------------------- public/422.html | 74 ++++++++++++++++++------------------ public/500.html | 55 +++++++++++++-------------- public/502.html | 5 ++- public/503.html | 5 ++- public/deploy.html | 108 ++++++++++++++++++++++++++--------------------------- 6 files changed, 164 insertions(+), 161 deletions(-) diff --git a/public/404.html b/public/404.html index d9052221f45..92b7f4da0b9 100644 --- a/public/404.html +++ b/public/404.html @@ -4,60 +4,60 @@ The page you're looking for could not be found (404)

-
+ GitLab Logo
404

The page you're looking for could not be found.

-
+

Make sure the address is correct and that the page hasn't moved.

Please contact your GitLab administrator if you think this is a mistake.

diff --git a/public/422.html b/public/422.html index b6986482daa..f625f8a33b7 100644 --- a/public/422.html +++ b/public/422.html @@ -6,53 +6,53 @@ + .container { + margin: auto 20px; + } +

-
+ GitLab Logo
422

diff --git a/public/500.html b/public/500.html index 0c69ab5b19e..d76c66ba92a 100644 --- a/public/500.html +++ b/public/500.html @@ -4,35 +4,35 @@ Something went wrong (500) +

-
+ GitLab Logo
500

Whoops, something went wrong on our end.

-
+

Try refreshing the page, or going back and attempting the action again.

Please contact your GitLab administrator if this problem persists.

diff --git a/public/502.html b/public/502.html index 0f2a310d34c..1a3c7efc769 100644 --- a/public/502.html +++ b/public/502.html @@ -49,14 +49,15 @@ } +

-
+ GitLab Logo
502

Whoops, GitLab is taking too much time to respond.

-
+

Try refreshing the page, or going back and attempting the action again.

Please contact your GitLab administrator if this problem persists.

diff --git a/public/503.html b/public/503.html index c26da8354ea..c1c4e3ffdb8 100644 --- a/public/503.html +++ b/public/503.html @@ -49,14 +49,15 @@ } +

- GitLab Logo
+ GitLab Logo
503

Whoops, GitLab is currently unavailable.

-
+

Try refreshing the page, or going back and attempting the action again.

Please contact your GitLab administrator if this problem persists.

diff --git a/public/deploy.html b/public/deploy.html index def717a1da4..142472b6c35 100644 --- a/public/deploy.html +++ b/public/deploy.html @@ -1,64 +1,64 @@ - - - Deploy in progress - - + .container { + margin: auto 20px; + } + + - -

-
- Deploy in progress -

-
-

Please try again in a few minutes.

-
-

Please contact your GitLab administrator if this problem persists.

-
- + +

+ GitLab Logo
+ Deploy in progress +

+
+

Please try again in a few minutes.

+
+

Please contact your GitLab administrator if this problem persists.

+
+ -- cgit v1.2.1 From c27e1b58d8f41d995ff3a9ff606ce2c4c71a9563 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Thu, 30 Jun 2016 01:28:59 +0900 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 791b4435ae9..2e7c5133072 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,6 +19,7 @@ v 8.11.0 (unreleased) - Add the `sprockets-es6` gem - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) - Profile requests when a header is passed + - Make error pages responsive (Takuya Noguchi) v 8.10.2 (unreleased) - User can now search branches by name. !5144 -- cgit v1.2.1 From 2c0f8eb1cc844561c294a3d4fe9388c6c7ed2acf Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Wed, 27 Jul 2016 10:51:37 +0100 Subject: Respective cache is now expired when creating a new branch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Project and branch cache is expired when project is still empty or new branch is created. develops tests accordingly Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + app/models/repository.rb | 4 ++++ spec/models/repository_spec.rb | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 791b4435ae9..5c6fa8220c1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,6 +26,7 @@ v 8.10.2 (unreleased) - Fix backup restore. !5459 - Rescue Rugged::OSError (lock exists) when creating references. !5497 - Disable MySQL foreign key checks before dropping all tables. !5472 + - Page is now properlty rendered after commiting the first file and creating the first branch - Fix a bug where forking a project from a repository storage to another would fail - Show release notes in tags list - Use project ID in repository cache to prevent stale data from persisting across projects. !5460 diff --git a/app/models/repository.rb b/app/models/repository.rb index d8775ecbd6c..9fd8f5ec787 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -988,6 +988,10 @@ class Repository if was_empty || !target_branch # Create branch rugged.references.create(ref, newrev) + + # If repo was empty expire cache + after_create if was_empty + after_create_branch else # Update head current_head = find_branch(branch).target diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 881ab5ff8dc..5bc1bd9a930 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -446,6 +446,43 @@ describe Repository, models: true do end.to raise_error(GitHooksService::PreReceiveError) end end + + context 'when target branch is different from source branch' do + before do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, '']) + end + + it 'expires branch cache' do + expect(repository).not_to receive(:expire_exists_cache) + expect(repository).not_to receive(:expire_root_ref_cache) + expect(repository).not_to receive(:expire_emptiness_caches) + expect(repository).to receive(:expire_branches_cache) + expect(repository).to receive(:expire_has_visible_content_cache) + expect(repository).to receive(:expire_branch_count_cache) + + repository.commit_with_hooks(user, 'new-feature') { sample_commit.id } + end + end + + context 'when repository is empty' do + before do + allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, '']) + end + + it 'expires creation and branch cache' do + empty_repository = create(:empty_project, :empty_repo).repository + + expect(empty_repository).to receive(:expire_exists_cache) + expect(empty_repository).to receive(:expire_root_ref_cache) + expect(empty_repository).to receive(:expire_emptiness_caches) + expect(empty_repository).to receive(:expire_branches_cache) + expect(empty_repository).to receive(:expire_has_visible_content_cache) + expect(empty_repository).to receive(:expire_branch_count_cache) + + empty_repository.commit_file(user, 'CHANGELOG', 'Changelog!', + 'Updates file content', 'master', false) + end + end end describe '#exists?' do -- cgit v1.2.1 From d24aafd647c0bf5e904da106e804512e4cd3d415 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 27 Jul 2016 13:54:09 +0200 Subject: Update CHANGELOG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5c6fa8220c1..f46c338885e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,19 +20,28 @@ v 8.11.0 (unreleased) - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) - Profile requests when a header is passed -v 8.10.2 (unreleased) +v 8.10.3 (unreleased) + +v 8.10.2 - User can now search branches by name. !5144 - - Add ENV variable to skip repository storages validations + - Page is now properlty rendered after commiting the first file and creating the first branch. !5399 + - Add branch or tag icon to ref in builds page. !5434 - Fix backup restore. !5459 - - Rescue Rugged::OSError (lock exists) when creating references. !5497 - - Disable MySQL foreign key checks before dropping all tables. !5472 - - Page is now properlty rendered after commiting the first file and creating the first branch - - Fix a bug where forking a project from a repository storage to another would fail - - Show release notes in tags list - Use project ID in repository cache to prevent stale data from persisting across projects. !5460 + - Fix issue with autocomplete search not working with enter key. !5466 + - Add iid to MR API response. !5468 + - Disable MySQL foreign key checks before dropping all tables. !5472 - Ensure relative paths for video are rewritten as we do for images. !5474 - Ensure current user can retry a build before showing the 'Retry' button. !5476 - - Fix expand all diffs button in compare view + - Add ENV variable to skip repository storages validations. !5478 + - Added `*.js.es6 gitlab-language=javascript` to `.gitattributes`. !5486 + - Don't show comment button in gutter of diffs on MR discussion tab. !5493 + - Rescue Rugged::OSError (lock exists) when creating references. !5497 + - Fix expand all diffs button in compare view. !5500 + - Show release notes in tags list. !5503 + - Fix a bug where forking a project from a repository storage to another would fail. !5509 + - Fix missing schema update for `20160722221922`. !5512 + - Update `gitlab-shell` version to 3.2.1 in the 8.9->8.10 update guide. !5516 v 8.10.1 - Refactor repository storages documentation. !5428 -- cgit v1.2.1 From 274769978ca576f4aea14eff2e3ec6532e3bcccd Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Tue, 26 Jul 2016 16:45:14 +0200 Subject: Use fewer queries for CI charts --- lib/ci/charts.rb | 96 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 70 insertions(+), 26 deletions(-) diff --git a/lib/ci/charts.rb b/lib/ci/charts.rb index 1d7126a432d..3decc3b1a26 100644 --- a/lib/ci/charts.rb +++ b/lib/ci/charts.rb @@ -1,5 +1,37 @@ module Ci module Charts + module DailyInterval + def grouped_count(query) + query. + group("DATE(#{Ci::Build.table_name}.created_at)"). + count(:created_at). + transform_keys { |date| date.strftime(@format) } + end + + def interval_step + @interval_step ||= 1.day + end + end + + module MonthlyInterval + def grouped_count(query) + if Gitlab::Database.postgresql? + query. + group("to_char(#{Ci::Build.table_name}.created_at, '01 Month YYYY')"). + count(:created_at). + transform_keys(&:squish) + else + query. + group("DATE_FORMAT(#{Ci::Build.table_name}.created_at, '01 %M %Y')"). + count(:created_at) + end + end + + def interval_step + @interval_step ||= 1.month + end + end + class Chart attr_reader :labels, :total, :success, :project, :build_times @@ -13,47 +45,59 @@ module Ci collect end - def push(from, to, format) - @labels << from.strftime(format) - @total << project.builds. - where("? > #{Ci::Build.table_name}.created_at AND #{Ci::Build.table_name}.created_at > ?", to, from). - count(:all) - @success << project.builds. - where("? > #{Ci::Build.table_name}.created_at AND #{Ci::Build.table_name}.created_at > ?", to, from). - success.count(:all) + def collect + query = project.builds. + where("? > #{Ci::Build.table_name}.created_at AND #{Ci::Build.table_name}.created_at > ?", @to, @from) + + totals_count = grouped_count(query) + success_count = grouped_count(query.success) + + current = @from + while current < @to + label = current.strftime(@format) + + @labels << label + @total << (totals_count[label] || 0) + @success << (success_count[label] || 0) + + current += interval_step + end end end class YearChart < Chart - def collect - 13.times do |i| - start_month = (Date.today.years_ago(1) + i.month).beginning_of_month - end_month = start_month.end_of_month + include MonthlyInterval - push(start_month, end_month, "%d %B %Y") - end + def initialize(*) + @to = Date.today.end_of_month + @from = @to.years_ago(1).beginning_of_month + @format = '%d %B %Y' + + super end end class MonthChart < Chart - def collect - 30.times do |i| - start_day = Date.today - 30.days + i.days - end_day = Date.today - 30.days + i.day + 1.day + include DailyInterval - push(start_day, end_day, "%d %B") - end + def initialize(*) + @to = Date.today + @from = @to - 30.days + @format = '%d %B' + + super end end class WeekChart < Chart - def collect - 7.times do |i| - start_day = Date.today - 7.days + i.days - end_day = Date.today - 7.days + i.day + 1.day + include DailyInterval - push(start_day, end_day, "%d %B") - end + def initialize(*) + @to = Date.today + @from = @to - 7.days + @format = '%d %B' + + super end end -- cgit v1.2.1 From aed644ccc084223cb1f9c5d955569c5ff9eeaf88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 27 Jul 2016 14:51:54 +0200 Subject: Fix typo in CHANGELOG MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [ci skip] Signed-off-by: Rémy Coutable --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index f46c338885e..ab1e96919a6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,7 +24,7 @@ v 8.10.3 (unreleased) v 8.10.2 - User can now search branches by name. !5144 - - Page is now properlty rendered after commiting the first file and creating the first branch. !5399 + - Page is now properly rendered after committing the first file and creating the first branch. !5399 - Add branch or tag icon to ref in builds page. !5434 - Fix backup restore. !5459 - Use project ID in repository cache to prevent stale data from persisting across projects. !5460 -- cgit v1.2.1 From dc4de2d8e2a42640cc24816fd28112c2f2d0bc91 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 13 Jul 2016 15:38:21 +0100 Subject: Allow skipping users in autocomplete Pass an array of user IDs in the `skip_users` param to have them excluded from the results (unless they are explicitly included through the `current_user` or `author_id` params). --- app/assets/javascripts/users_select.js | 4 +++- app/controllers/autocomplete_controller.rb | 1 + app/helpers/selects_helper.rb | 30 ++++++++++++++---------- spec/controllers/autocomplete_controller_spec.rb | 13 ++++++++++ 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index 64a29d36cdf..4af2a214e12 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -189,6 +189,7 @@ _this.groupId = $(select).data('group-id'); _this.showCurrentUser = $(select).data('current-user'); _this.authorId = $(select).data('author-id'); + _this.skipUsers = $(select).data('skip-users'); showNullUser = $(select).data('null-user'); showAnyUser = $(select).data('any-user'); showEmailUser = $(select).data('email-user'); @@ -320,7 +321,8 @@ project_id: this.projectId, group_id: this.groupId, current_user: this.showCurrentUser, - author_id: this.authorId + author_id: this.authorId, + skip_users: this.skipUsers }, dataType: "json" }).done(function(users) { diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index c89678cf2d8..d828d163c28 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -5,6 +5,7 @@ class AutocompleteController < ApplicationController def users @users ||= User.none @users = @users.search(params[:search]) if params[:search].present? + @users = @users.where.not(id: params[:skip_users]) if params[:skip_users].present? @users = @users.active @users = @users.reorder(:name) @users = @users.page(params[:page]) diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb index bb395e37884..5f27e33c6ad 100644 --- a/app/helpers/selects_helper.rb +++ b/app/helpers/selects_helper.rb @@ -5,21 +5,9 @@ module SelectsHelper css_class << "skip_ldap " if opts[:skip_ldap] css_class << (opts[:class] || '') value = opts[:selected] || '' - - first_user = opts[:first_user] && current_user ? current_user.username : false - html = { class: css_class, - data: { - placeholder: opts[:placeholder] || 'Search for a user', - null_user: opts[:null_user] || false, - any_user: opts[:any_user] || false, - email_user: opts[:email_user] || false, - first_user: first_user, - current_user: opts[:current_user] || false, - "push-code-to-protected-branches" => opts[:push_code_to_protected_branches], - author_id: opts[:author_id] || '' - } + data: users_select_data_attributes(opts) } unless opts[:scope] == :all @@ -68,4 +56,20 @@ module SelectsHelper hidden_field_tag(id, value, class: css_class) end + + private + + def users_select_data_attributes(opts) + { + placeholder: opts[:placeholder] || 'Search for a user', + null_user: opts[:null_user] || false, + any_user: opts[:any_user] || false, + email_user: opts[:email_user] || false, + first_user: opts[:first_user] && current_user ? current_user.username : false, + current_user: opts[:current_user] || false, + "push-code-to-protected-branches" => opts[:push_code_to_protected_branches], + author_id: opts[:author_id] || '', + skip_users: opts[:skip_users] ? opts[:skip_users].map(&:id) : nil, + } + end end diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index 60c654f622d..ed0b7f9e240 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -163,4 +163,17 @@ describe AutocompleteController do expect(body.collect { |u| u['id'] }).not_to include(99999) end end + + context 'skip_users parameter included' do + before { sign_in(user) } + + it 'skips the user IDs passed' do + get(:users, skip_users: [user, user2].map(&:id)) + + other_user_ids = [non_member, project.owner, project.creator].map(&:id) + response_user_ids = JSON.parse(response.body).map { |user| user['id'] } + + expect(response_user_ids).to contain_exactly(*other_user_ids) + end + end end -- cgit v1.2.1 From 3e17187ff0f21b2ce030ba3cac5e13e05f0c254e Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Wed, 27 Jul 2016 15:45:18 +0200 Subject: Change requests_profiles resource constraint to catch virtually any file --- CHANGELOG | 1 + config/routes.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index ab1e96919a6..4fb9541a46a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,6 +19,7 @@ v 8.11.0 (unreleased) - Add the `sprockets-es6` gem - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) - Profile requests when a header is passed + - Change requests_profiles resource constraint to catch virtually any file v 8.10.3 (unreleased) diff --git a/config/routes.rb b/config/routes.rb index 414ba69dfae..308d83af57e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -279,7 +279,7 @@ Rails.application.routes.draw do resource :health_check, controller: 'health_check', only: [:show] resource :background_jobs, controller: 'background_jobs', only: [:show] resource :system_info, controller: 'system_info', only: [:show] - resources :requests_profiles, only: [:index, :show], param: :name + resources :requests_profiles, only: [:index, :show], param: :name, constraints: { name: /.+\.html/ } resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do root to: 'projects#index', as: :projects -- cgit v1.2.1 From 3032e977170596eb1fd738211410fb2c57e7d81a Mon Sep 17 00:00:00 2001 From: Mark Pundsack Date: Wed, 27 Jul 2016 15:44:09 +0000 Subject: Update README.md --- doc/ci/yaml/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 189ce652a66..01d71088543 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -379,7 +379,7 @@ job: - bundle exec rspec ``` -Sometimes, `script` commands will need to be wrapped in single or double quotes. For example, commands that contain a colon (`:`) need to be wrapped in quotes so that the YAML parser knows to interpret the whole thing as a string rather than a "key: value" pair. Be careful when using special characters (`:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `\``). +Sometimes, `script` commands will need to be wrapped in single or double quotes. For example, commands that contain a colon (`:`) need to be wrapped in quotes so that the YAML parser knows to interpret the whole thing as a string rather than a "key: value" pair. Be careful when using special characters (`:`, `{`, `}`, `[`, `]`, `,`, `&`, `*`, `#`, `?`, `|`, `-`, `<`, `>`, `=`, `!`, `%`, `@`, `` ` ``). ### stage -- cgit v1.2.1 From 487dfad6acf4175275352094edf418dca6c8cfd6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 26 Jul 2016 00:27:46 -0300 Subject: Fix renaming repository when name contains invalid chars under settings --- app/services/projects/update_service.rb | 12 +++++++++++- app/views/projects/edit.html.haml | 2 ++ app/views/projects/update.js.haml | 4 ++-- spec/services/projects/update_service_spec.rb | 21 +++++++++++++++++++++ 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index f06311511cc..032c3b182fb 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -3,7 +3,7 @@ module Projects def execute # check that user is allowed to set specified visibility_level new_visibility = params[:visibility_level] - + if new_visibility && new_visibility.to_i != project.visibility_level unless can?(current_user, :change_visibility_level, project) && Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) @@ -23,7 +23,17 @@ module Projects if project.previous_changes.include?('path') project.rename_repo end + else + restore_attributes + false end end + + private + + def restore_attributes + project.path = project.path_was if project.errors.include?(:path) + project.name = project.name_was if project.errors.include?(:name) + end end end diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 921155e970b..b282aa52b25 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -4,6 +4,7 @@ %h4.prepend-top-0 Project settings .col-lg-9 + .project-edit-errors = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit-project" }, authenticity_token: true do |f| %fieldset.append-bottom-0 .form-group @@ -190,6 +191,7 @@ %h4.prepend-top-0.warning-title Rename repository .col-lg-9 + = render 'projects/errors' = form_for([@project.namespace.becomes(Namespace), @project]) do |f| .form-group.project_name_holder = f.label :name, class: 'label-light' do diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml index 7d9bd08385a..938d44efe29 100644 --- a/app/views/projects/update.js.haml +++ b/app/views/projects/update.js.haml @@ -1,4 +1,4 @@ -- if @project.valid? +- if @project.errors.blank? :plain location.href = "#{edit_namespace_project_path(@project.namespace, @project)}"; - else @@ -6,4 +6,4 @@ $(".project-edit-errors").html("#{escape_javascript(render('errors'))}"); $('.save-project-loader').hide(); $('.project-edit-container').show(); - $('.project-edit-content .btn-save').enable(); + $('.edit-project .btn-save').enable(); diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index e8b9e6b9238..6337daca9c7 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -139,6 +139,27 @@ describe Projects::UpdateService, services: true do end end + context 'for invalid project path/name' do + let(:user) { create(:user, admin: true) } + let(:project) { create(:empty_project, path: 'gitlab', name: 'sample') } + let(:params) { { path: 'foo&bar', name: 'foo&bar' } } + + it 'resets to previous values to keep project in a valid state' do + update_project(project, user, params) + + expect(project.path).to eq 'gitlab' + expect(project.name).to eq 'sample' + end + + it 'keeps error messages' do + update_project(project, user, params) + + expect(project.errors).not_to be_blank + expect(project.errors[:name]).to include("can contain only letters, digits, '_', '.', dash and space. It must start with letter, digit or '_'.") + expect(project.errors[:path]).to include("can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'") + end + end + def update_project(project, user, opts) Projects::UpdateService.new(project, user, opts).execute end -- cgit v1.2.1 From 7d483f8c03b74c3954319603f01cf8a51ccd461a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 26 Jul 2016 00:28:08 -0300 Subject: Add feature specs for edit project settings --- spec/features/projects/project_settings_spec.rb | 41 +++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 spec/features/projects/project_settings_spec.rb diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb new file mode 100644 index 00000000000..5562680d6cc --- /dev/null +++ b/spec/features/projects/project_settings_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe 'Edit Project Settings', feature: true do + let(:user) { create(:user) } + let(:project) { create(:empty_project, path: 'gitlab', name: 'sample') } + + before do + login_as(user) + project.team << [user, :master] + end + + describe 'Project settings', js: true do + it 'shows errors for invalid project name' do + visit edit_namespace_project_path(project.namespace, project) + + fill_in 'project_name_edit', with: 'foo&bar' + + click_button 'Save changes' + + expect(page).to have_field 'project_name_edit', with: 'foo&bar' + expect(page).to have_content "Name can contain only letters, digits, '_', '.', dash and space. It must start with letter, digit or '_'." + expect(page).to have_button 'Save changes' + end + end + + describe 'Rename repository' do + it 'shows errors for invalid project path/name' do + visit edit_namespace_project_path(project.namespace, project) + + fill_in 'Project name', with: 'foo&bar' + fill_in 'Path', with: 'foo&bar' + + click_button 'Rename project' + + expect(page).to have_field 'Project name', with: 'sample' + expect(page).to have_field 'Path', with: 'gitlab' + expect(page).to have_content "Name can contain only letters, digits, '_', '.', dash and space. It must start with letter, digit or '_'." + expect(page).to have_content "Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'" + end + end +end -- cgit v1.2.1 From 6dfaf4fe547104a553522a1904a321dd6d015a09 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 26 Jul 2016 00:28:46 -0300 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index df54682dd10..a4dbce95268 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.11.0 (unreleased) - Clean up unused routes (Josef Strzibny) - Add green outline to New Branch button. !5447 (winniehell) - Retrieve rendered HTML from cache in one request + - Fix renaming repository when name contains invalid chararacters under project settings - Nokogiri's various parsing methods are now instrumented - Add build event color in HipChat messages (David Eisner) - Make fork counter always clickable. !5463 (winniehell) -- cgit v1.2.1 From ea4d6c87d73301ade3e49f8f865eb448ecc8a569 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 26 Jul 2016 15:27:42 -0300 Subject: Fix Project#to_param to keep invalid project suitable for use in URLs --- app/models/project.rb | 6 +++++- app/services/projects/update_service.rb | 10 ---------- app/views/projects/update.js.haml | 2 +- spec/features/projects/project_settings_spec.rb | 4 ++-- spec/models/project_spec.rb | 18 ++++++++++++++++++ spec/services/projects/update_service_spec.rb | 21 --------------------- 6 files changed, 26 insertions(+), 35 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 1616175709f..b3703d71e72 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -586,7 +586,11 @@ class Project < ActiveRecord::Base end def to_param - path + if persisted? && errors.include?(:path) + path_was + else + path + end end def to_reference(_from_project = nil) diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 032c3b182fb..921ca6748d3 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -23,17 +23,7 @@ module Projects if project.previous_changes.include?('path') project.rename_repo end - else - restore_attributes - false end end - - private - - def restore_attributes - project.path = project.path_was if project.errors.include?(:path) - project.name = project.name_was if project.errors.include?(:name) - end end end diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml index 938d44efe29..dcf1f767bf7 100644 --- a/app/views/projects/update.js.haml +++ b/app/views/projects/update.js.haml @@ -1,4 +1,4 @@ -- if @project.errors.blank? +- if @project.valid? :plain location.href = "#{edit_namespace_project_path(@project.namespace, @project)}"; - else diff --git a/spec/features/projects/project_settings_spec.rb b/spec/features/projects/project_settings_spec.rb index 5562680d6cc..3de25d7af7d 100644 --- a/spec/features/projects/project_settings_spec.rb +++ b/spec/features/projects/project_settings_spec.rb @@ -32,8 +32,8 @@ describe 'Edit Project Settings', feature: true do click_button 'Rename project' - expect(page).to have_field 'Project name', with: 'sample' - expect(page).to have_field 'Path', with: 'gitlab' + expect(page).to have_field 'Project name', with: 'foo&bar' + expect(page).to have_field 'Path', with: 'foo&bar' expect(page).to have_content "Name can contain only letters, digits, '_', '.', dash and space. It must start with letter, digit or '_'." expect(page).to have_content "Path can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'" end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index c884bb31199..be5c53c44be 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -372,6 +372,24 @@ describe Project, models: true do it { expect(@project.to_param).to eq('gitlabhq') } end + + context 'with invalid path' do + it 'returns previous path to keep project suitable for use in URLs when persisted' do + project = create(:empty_project, path: 'gitlab') + project.path = 'foo&bar' + + expect(project).not_to be_valid + expect(project.to_param).to eq 'gitlab' + end + + it 'returns current path when new record' do + project = build(:empty_project, path: 'gitlab') + project.path = 'foo&bar' + + expect(project).not_to be_valid + expect(project.to_param).to eq 'foo&bar' + end + end end describe '#repository' do diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 6337daca9c7..e8b9e6b9238 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -139,27 +139,6 @@ describe Projects::UpdateService, services: true do end end - context 'for invalid project path/name' do - let(:user) { create(:user, admin: true) } - let(:project) { create(:empty_project, path: 'gitlab', name: 'sample') } - let(:params) { { path: 'foo&bar', name: 'foo&bar' } } - - it 'resets to previous values to keep project in a valid state' do - update_project(project, user, params) - - expect(project.path).to eq 'gitlab' - expect(project.name).to eq 'sample' - end - - it 'keeps error messages' do - update_project(project, user, params) - - expect(project.errors).not_to be_blank - expect(project.errors[:name]).to include("can contain only letters, digits, '_', '.', dash and space. It must start with letter, digit or '_'.") - expect(project.errors[:path]).to include("can contain only letters, digits, '_', '-' and '.'. Cannot start with '-', end in '.git' or end in '.atom'") - end - end - def update_project(project, user, opts) Projects::UpdateService.new(project, user, opts).execute end -- cgit v1.2.1 From 6e50719ff8dde5b5d54eea5e8f6fa95c5d0c0c24 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 21 Jul 2016 17:16:23 +0100 Subject: Fixed artifacts expire date in FF --- app/assets/javascripts/build.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/build.js b/app/assets/javascripts/build.js index e135cb92a30..3d9b824d406 100644 --- a/app/assets/javascripts/build.js +++ b/app/assets/javascripts/build.js @@ -128,7 +128,7 @@ $date = $('.js-artifacts-remove'); if ($date.length) { date = $date.text(); - return $date.text($.timefor(new Date(date), ' ')); + return $date.text($.timefor(new Date(date.replace(/-/g, '/')), ' ')); } }; -- cgit v1.2.1 From b1c94754436e4c15529276a61d601f72bb53c2b2 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Wed, 27 Jul 2016 13:43:29 -0600 Subject: Replace reject_blocked with reject_blocked! in callbacks. In Rails 4.2 and below, skipping callbacks (skip_before_action, skip_after_action, etc.) that use methods which do not exist will not throw any errors. On the other hand, Rails 5 does. See https://github.com/rails/rails/pull/19029 After testing with Rails 5 I noticed there are some methods that don't actually exist (because they were renamed, usually), this fixes a few instances of those. reject_blocked! was introduced in c9def945d4222eeb8026a0311495259bf99267a1, I can't find any references to reject_blocked ever existing. --- app/controllers/explore/application_controller.rb | 2 +- app/controllers/help_controller.rb | 2 +- app/controllers/search_controller.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/explore/application_controller.rb b/app/controllers/explore/application_controller.rb index 461fc059a3c..a1ab8b99048 100644 --- a/app/controllers/explore/application_controller.rb +++ b/app/controllers/explore/application_controller.rb @@ -1,5 +1,5 @@ class Explore::ApplicationController < ApplicationController - skip_before_action :authenticate_user!, :reject_blocked + skip_before_action :authenticate_user!, :reject_blocked! layout 'explore' end diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index f7b44099b78..4eca278599f 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -1,5 +1,5 @@ class HelpController < ApplicationController - skip_before_action :authenticate_user!, :reject_blocked + skip_before_action :authenticate_user!, :reject_blocked! layout 'help' diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 69c92d2bed2..61517d21f9f 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -1,5 +1,5 @@ class SearchController < ApplicationController - skip_before_action :authenticate_user!, :reject_blocked + skip_before_action :authenticate_user!, :reject_blocked! include SearchHelper -- cgit v1.2.1 From 8a9fc2b67e5bb6aeabdffa1f91115ac7613a505a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 27 Jul 2016 15:35:53 -0700 Subject: Cache the commit author in RequestStore to avoid extra lookups in PostReceive In a PostReceive task with 697 commits (8.9 RC1 -> RC8), looking up the commit author takes about 10% of the time. Caching this information in RequestStore saves a few seconds from the overall processing time. Improves #18663 --- CHANGELOG | 1 + app/models/commit.rb | 9 ++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5ff0cb42ccc..d2ed7d8708e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.11.0 (unreleased) - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) - Fix CI status icon link underline (ClemMakesApps) + - Cache the commit author in RequestStore to avoid extra lookups in PostReceive - Fix of 'Commits being passed to custom hooks are already reachable when using the UI' - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable - Limit git rev-list output count to one in forced push check diff --git a/app/models/commit.rb b/app/models/commit.rb index f80f1063406..6a0d32d406e 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -178,7 +178,14 @@ class Commit end def author - @author ||= User.find_by_any_email(author_email.downcase) + key = "commit_author:#{author_email}" + + # nil is a valid value since no author may exist in the system + unless RequestStore.store.has_key?(key) + RequestStore.store[key] = User.find_by_any_email(author_email.downcase) + end + + @author ||= RequestStore.store[key] end def committer -- cgit v1.2.1 From d27e36f35af0c2850c5370a3348d30ce4bcf1a68 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 27 Jul 2016 16:42:38 -0700 Subject: Add specs for caching commit author --- app/models/commit.rb | 22 +++++++++++++++------- spec/models/commit_spec.rb | 20 ++++++++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) diff --git a/app/models/commit.rb b/app/models/commit.rb index 6a0d32d406e..486ad6714d9 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -178,14 +178,18 @@ class Commit end def author - key = "commit_author:#{author_email}" - - # nil is a valid value since no author may exist in the system - unless RequestStore.store.has_key?(key) - RequestStore.store[key] = User.find_by_any_email(author_email.downcase) + if RequestStore.active? + key = "commit_author:#{author_email.downcase}" + # nil is a valid value since no author may exist in the system + if RequestStore.store.has_key?(key) + @author = RequestStore.store[key] + else + @author = find_author_by_any_email + RequestStore.store[key] = @author + end + else + @author ||= find_author_by_any_email end - - @author ||= RequestStore.store[key] end def committer @@ -313,6 +317,10 @@ class Commit private + def find_author_by_any_email + User.find_by_any_email(author_email.downcase) + end + def repo_changes changes = { added: [], modified: [], removed: [] } diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index ec1544bf815..c3392ee7440 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -13,6 +13,26 @@ describe Commit, models: true do it { is_expected.to include_module(StaticModel) } end + describe '#author' do + it 'looks up the author in a case-insensitive way' do + user = create(:user, email: commit.author_email.upcase) + expect(commit.author).to eq(user) + end + + it 'caches the author' do + user = create(:user, email: commit.author_email) + expect(RequestStore).to receive(:active?).twice.and_return(true) + expect_any_instance_of(Commit).to receive(:find_author_by_any_email).and_call_original + + expect(commit.author).to eq(user) + key = "commit_author:#{commit.author_email}" + expect(RequestStore.store[key]).to eq(user) + + expect(commit.author).to eq(user) + RequestStore.store.clear + end + end + describe '#to_reference' do it 'returns a String reference to the object' do expect(commit.to_reference).to eq commit.id -- cgit v1.2.1 From f178d0c73f838b3fa83ed462c413c0c88ef9dbbc Mon Sep 17 00:00:00 2001 From: Fernando Derkoski Date: Thu, 28 Jul 2016 02:02:39 +0000 Subject: Changed grant_type=AUTHORIZATION_CODE for grant_type=authorization_code --- doc/api/oauth2.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md index 31902e145f6..7ce89adc98b 100644 --- a/doc/api/oauth2.md +++ b/doc/api/oauth2.md @@ -35,7 +35,7 @@ Where REDIRECT_URI is the URL in your app where users will be sent after authori To request the access token, you should use the returned code and exchange it for an access token. To do that you can use any HTTP client. In this case, I used rest-client: ``` -parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=AUTHORIZATION_CODE&redirect_uri=REDIRECT_URI' +parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=authorization_code&redirect_uri=REDIRECT_URI' RestClient.post 'http://localhost:3000/oauth/token', parameters # The response will be -- cgit v1.2.1 From bac99f909a57501e69df75e222888f157fc34f17 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 28 Jul 2016 13:59:37 +0800 Subject: Remove extra entries from auto-merge accident --- CHANGELOG | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3d36de4ae81..4ad348d8b86 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,8 +11,6 @@ v 8.11.0 (unreleased) - Add green outline to New Branch button. !5447 (winniehell) - Retrieve rendered HTML from cache in one request - Nokogiri's various parsing methods are now instrumented - - Make fork counter always clickable !5463 (winniehell) - - Load project invited groups and members eagerly in ProjectTeam#fetch_members - Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363 - Make fork counter always clickable. !5463 (winniehell) - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le) -- cgit v1.2.1 From a4bb9993c45b3d63a3d88f92ffc14bf849c38906 Mon Sep 17 00:00:00 2001 From: dixpac Date: Wed, 27 Jul 2016 11:02:49 +0200 Subject: Add commit stats to commit api response --- CHANGELOG | 1 + doc/api/commits.md | 5 +++++ lib/api/entities.rb | 5 +++++ spec/requests/api/commits_spec.rb | 4 ++++ 4 files changed, 15 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index f4567fc5bbd..d95dd0ec54f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 8.11.0 (unreleased) - Add the `sprockets-es6` gem - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) - Profile requests when a header is passed + - Add commit stats in commit api. !5517 (dixpac) - Make error pages responsive (Takuya Noguchi) - Change requests_profiles resource constraint to catch virtually any file diff --git a/doc/api/commits.md b/doc/api/commits.md index 57c2e1d9b87..2960c2ae428 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -81,6 +81,11 @@ Example response: "parent_ids": [ "ae1d9fb46aa2b07ee9836d49862ec4e2c46fbbba" ], + "stats": { + "additions": 15, + "deletions": 10, + "total": 25 + }, "status": "running" } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index fbf0d74663f..e76e7304674 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -149,8 +149,13 @@ module API expose :safe_message, as: :message end + class RepoCommitStats < Grape::Entity + expose :additions, :deletions, :total + end + class RepoCommitDetail < RepoCommit expose :parent_ids, :committed_date, :authored_date + expose :stats, using: Entities::RepoCommitStats expose :status end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 5219c808791..e4ea8506598 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -73,9 +73,13 @@ describe API::API, api: true do context "authorized user" do it "should return a commit by sha" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) + expect(response).to have_http_status(200) expect(json_response['id']).to eq(project.repository.commit.id) expect(json_response['title']).to eq(project.repository.commit.title) + expect(json_response['stats']['additions']).to eq(project.repository.commit.stats.additions) + expect(json_response['stats']['deletions']).to eq(project.repository.commit.stats.deletions) + expect(json_response['stats']['total']).to eq(project.repository.commit.stats.total) end it "should return a 404 error if not found" do -- cgit v1.2.1 From 5881ef49a97f48bf1b94cd75de5337f689a6b439 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 28 Jul 2016 12:57:01 +0300 Subject: Added update guide for 8.11 --- doc/update/8.10-to-8.11.md | 191 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 doc/update/8.10-to-8.11.md diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md new file mode 100644 index 00000000000..fc6262dd108 --- /dev/null +++ b/doc/update/8.10-to-8.11.md @@ -0,0 +1,191 @@ +# From 8.10 to 8.11 + +Make sure you view this update guide from the tag (version) of GitLab you would +like to install. In most cases this should be the highest numbered production +tag (without rc in it). You can select the tag in the version dropdown at the +top left corner of GitLab (below the menu bar). + +If the highest number stable branch is unclear please check the +[GitLab Blog](https://about.gitlab.com/blog/archives.html) for installation +guide links by version. + +### 1. Stop server + + sudo service gitlab stop + +### 2. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 3. Get latest code + +```bash +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +sudo -u git -H git checkout 8-11-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 8-11-stable-ee +``` + +### 4. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch --all --tags +sudo -u git -H git checkout v3.2.1 +``` + +### 5. Update gitlab-workhorse + +Install and compile gitlab-workhorse. This requires +[Go 1.5](https://golang.org/dl) which should already be on your system from +GitLab 8.1. + +```bash +cd /home/git/gitlab-workhorse +sudo -u git -H git fetch --all +sudo -u git -H git checkout v0.7.8 +sudo -u git -H make +``` + +### 6. Update MySQL permissions + +If you are using MySQL you need to grant the GitLab user the necessary +permissions on the database: + +```bash +# Login to MySQL +mysql -u root -p + +# Grant the GitLab user the REFERENCES permission on the database +GRANT REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost'; + +# Quit the database session +mysql> \q +``` + +### 7. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without postgres') +sudo -u git -H bundle install --without postgres development test --deployment + +# PostgreSQL installations (note: the line below states '--without mysql') +sudo -u git -H bundle install --without mysql development test --deployment + +# Optional: clean up old gems +sudo -u git -H bundle clean + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production + +``` + +### 8. Update configuration files + +#### New configuration options for `gitlab.yml` + +There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them manually to your current `gitlab.yml`: + +```sh +git diff origin/8-10-stable:config/gitlab.yml.example origin/8-11-stable:config/gitlab.yml.example +``` + +#### Git configuration + +Disable `git gc --auto` because GitLab runs `git gc` for us already. + +```sh +sudo -u git -H git config --global gc.auto 0 +``` + +#### Nginx configuration + +Ensure you're still up-to-date with the latest NGINX configuration changes: + +```sh +# For HTTPS configurations +git diff origin/8-10-stable:lib/support/nginx/gitlab-ssl origin/8-11-stable:lib/support/nginx/gitlab-ssl + +# For HTTP configurations +git diff origin/8-10-stable:lib/support/nginx/gitlab origin/8-11-stable:lib/support/nginx/gitlab +``` + +If you are using Apache instead of NGINX please see the updated [Apache templates]. +Also note that because Apache does not support upstreams behind Unix sockets you +will need to let gitlab-workhorse listen on a TCP port. You can do this +via [/etc/default/gitlab]. + +[Apache templates]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/web-server/apache +[/etc/default/gitlab]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-11-stable/lib/support/init.d/gitlab.default.example#L38 + +#### SMTP configuration + +If you're installing from source and use SMTP to deliver mail, you will need to add the following line +to config/initializers/smtp_settings.rb: + +```ruby +ActionMailer::Base.delivery_method = :smtp +``` + +See [smtp_settings.rb.sample] as an example. + +[smtp_settings.rb.sample]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-11-stable/config/initializers/smtp_settings.rb.sample#L13? + +#### Init script + +Ensure you're still up-to-date with the latest init script changes: + + sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab + +### 9. Start application + + sudo service gitlab start + sudo service nginx restart + +### 10. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (8.10) + +### 1. Revert the code to the previous version + +Follow the [upgrade guide from 8.9 to 8.10](8.9-to-8.10.md), except for the +database migration (the backup is already migrated to the previous version). + +### 2. Restore from the backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` + +If you have more than one backup `*.tar` file(s) please add `BACKUP=timestamp_of_backup` to the command above. -- cgit v1.2.1 From e4027e40707d8cde2c5326e99ae509b518a2190f Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Thu, 28 Jul 2016 16:51:44 +0200 Subject: Reduce number of queries made for merge_requests/:id/diffs --- CHANGELOG | 1 + app/controllers/projects/merge_requests_controller.rb | 2 +- app/models/concerns/issuable.rb | 2 +- app/models/legacy_diff_note.rb | 10 +++++++++- 4 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d95dd0ec54f..8447dff180a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -25,6 +25,7 @@ v 8.11.0 (unreleased) - Add commit stats in commit api. !5517 (dixpac) - Make error pages responsive (Takuya Noguchi) - Change requests_profiles resource constraint to catch virtually any file + - Reduce number of queries made for merge_requests/:id/diffs v 8.10.3 (unreleased) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 23252fa59cc..47c21a18b33 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -407,7 +407,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController } @use_legacy_diff_notes = !@merge_request.support_new_diff_notes? - @grouped_diff_discussions = @merge_request.notes.grouped_diff_discussions + @grouped_diff_discussions = @merge_request.notes.inc_author_project_award_emoji.grouped_diff_discussions Banzai::NoteRenderer.render( @grouped_diff_discussions.values.flat_map(&:notes), diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index acb6f5a2998..cbae1cd439b 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -17,7 +17,7 @@ module Issuable belongs_to :assignee, class_name: "User" belongs_to :updated_by, class_name: "User" belongs_to :milestone - has_many :notes, as: :noteable, dependent: :destroy do + has_many :notes, as: :noteable, inverse_of: :noteable, dependent: :destroy do def authors_loaded? # We check first if we're loaded to not load unnecessarily. loaded? && to_a.all? { |note| note.association(:author).loaded? } diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb index 04a651d50ab..865712268a0 100644 --- a/app/models/legacy_diff_note.rb +++ b/app/models/legacy_diff_note.rb @@ -25,6 +25,14 @@ class LegacyDiffNote < Note @discussion_id ||= self.class.build_discussion_id(noteable_type, noteable_id || commit_id, line_code) end + def project_repository + if RequestStore.active? + RequestStore.fetch("project:#{project_id}:repository") { self.project.repository } + else + self.project.repository + end + end + def diff_file_hash line_code.split('_')[0] if line_code end @@ -34,7 +42,7 @@ class LegacyDiffNote < Note end def diff_file - @diff_file ||= Gitlab::Diff::File.new(diff, repository: self.project.repository) if diff + @diff_file ||= Gitlab::Diff::File.new(diff, repository: project_repository) if diff end def diff_line -- cgit v1.2.1 From 905f8d763ab1184dc0b1e4bf6f18d7981753a860 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 28 Jul 2016 15:08:57 +0200 Subject: Reduce instrumentation overhead This reduces the overhead of the method instrumentation code primarily by reducing the number of method calls. There are also some other small optimisations such as not casting timing values to Floats (there's no particular need for this), using Symbols for method call metric names, and reducing the number of Hash lookups for instrumented methods. The exact impact depends on the code being executed. For example, for a method that's only called once the difference won't be very noticeable. However, for methods that are called many times the difference can be more significant. For example, the loading time of a large commit (nrclark/dummy_project@81ebdea5df2fb42e59257cb3eaad671a5c53ca36) was reduced from around 19 seconds to around 15 seconds using these changes. --- CHANGELOG | 1 + lib/gitlab/metrics.rb | 5 +++++ lib/gitlab/metrics/instrumentation.rb | 13 +++++++----- lib/gitlab/metrics/method_call.rb | 4 ++-- lib/gitlab/metrics/system.rb | 8 ++++---- lib/gitlab/metrics/transaction.rb | 27 ++++++------------------- spec/lib/gitlab/metrics/instrumentation_spec.rb | 12 +++++++---- spec/lib/gitlab/metrics/system_spec.rb | 12 +++++------ spec/lib/gitlab/metrics/transaction_spec.rb | 16 ++++----------- spec/lib/gitlab/metrics_spec.rb | 6 ++++++ 10 files changed, 50 insertions(+), 54 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f4567fc5bbd..11df072eaf7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.11.0 (unreleased) - Add build event color in HipChat messages (David Eisner) - Make fork counter always clickable. !5463 (winniehell) - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333 + - The overhead of instrumented method calls has been reduced - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le) - Load project invited groups and members eagerly in `ProjectTeam#fetch_members` - Make branches sortable without push permission !5462 (winniehell) diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 49f702f91f6..86a5b9a201a 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -124,6 +124,11 @@ module Gitlab trans.action = action if trans end + # Returns the prefix to use for the name of a series. + def self.series_prefix + @series_prefix ||= Sidekiq.server? ? 'sidekiq_' : 'rails_' + end + # When enabled this should be set before being used as the usual pattern # "@foo ||= bar" is _not_ thread-safe. if enabled? diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb index dcec7543c13..4b7a791e497 100644 --- a/lib/gitlab/metrics/instrumentation.rb +++ b/lib/gitlab/metrics/instrumentation.rb @@ -9,14 +9,17 @@ module Gitlab # # Gitlab::Metrics::Instrumentation.instrument_method(User, :by_login) module Instrumentation - SERIES = 'method_calls' - PROXY_IVAR = :@__gitlab_instrumentation_proxy def self.configure yield self end + # Returns the name of the series to use for storing method calls. + def self.series + @series ||= "#{Metrics.series_prefix}method_calls" + end + # Instruments a class method. # # mod - The module to instrument as a Module/Class. @@ -141,15 +144,15 @@ module Gitlab # generated method _only_ accepts regular arguments if the underlying # method also accepts them. if method.arity == 0 - args_signature = '&block' + args_signature = '' else - args_signature = '*args, &block' + args_signature = '*args' end proxy_module.class_eval <<-EOF, __FILE__, __LINE__ + 1 def #{name}(#{args_signature}) if trans = Gitlab::Metrics::Instrumentation.transaction - trans.measure_method(#{label.inspect}) { super } + trans.method_call_for(#{label.to_sym.inspect}).measure { super } else super end diff --git a/lib/gitlab/metrics/method_call.rb b/lib/gitlab/metrics/method_call.rb index c048fe20ba7..d3465e5ec19 100644 --- a/lib/gitlab/metrics/method_call.rb +++ b/lib/gitlab/metrics/method_call.rb @@ -11,8 +11,8 @@ module Gitlab def initialize(name, series) @name = name @series = series - @real_time = 0.0 - @cpu_time = 0.0 + @real_time = 0 + @cpu_time = 0 @call_count = 0 end diff --git a/lib/gitlab/metrics/system.rb b/lib/gitlab/metrics/system.rb index 82c18bb108b..287b7a83547 100644 --- a/lib/gitlab/metrics/system.rb +++ b/lib/gitlab/metrics/system.rb @@ -35,12 +35,12 @@ module Gitlab if Process.const_defined?(:CLOCK_THREAD_CPUTIME_ID) def self.cpu_time Process. - clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond).to_f + clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :millisecond) end else def self.cpu_time Process. - clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond).to_f + clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :millisecond) end end @@ -48,14 +48,14 @@ module Gitlab # # Returns the time as a Float. def self.real_time(precision = :millisecond) - Process.clock_gettime(Process::CLOCK_REALTIME, precision).to_f + Process.clock_gettime(Process::CLOCK_REALTIME, precision) end # Returns the current monotonic clock time in a given precision. # # Returns the time as a Float. def self.monotonic_time(precision = :millisecond) - Process.clock_gettime(Process::CLOCK_MONOTONIC, precision).to_f + Process.clock_gettime(Process::CLOCK_MONOTONIC, precision) end end end diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb index bded245da43..968f3218950 100644 --- a/lib/gitlab/metrics/transaction.rb +++ b/lib/gitlab/metrics/transaction.rb @@ -52,23 +52,16 @@ module Gitlab end def add_metric(series, values, tags = {}) - @metrics << Metric.new("#{series_prefix}#{series}", values, tags) + @metrics << Metric.new("#{Metrics.series_prefix}#{series}", values, tags) end - # Measures the time it takes to execute a method. - # - # Multiple calls to the same method add up to the total runtime of the - # method. - # - # name - The full name of the method to measure (e.g. `User#sign_in`). - def measure_method(name, &block) - unless @methods[name] - series = "#{series_prefix}#{Instrumentation::SERIES}" - - @methods[name] = MethodCall.new(name, series) + # Returns a MethodCall object for the given name. + def method_call_for(name) + unless method = @methods[name] + @methods[name] = method = MethodCall.new(name, Instrumentation.series) end - @methods[name].measure(&block) + method end def increment(name, value) @@ -115,14 +108,6 @@ module Gitlab Metrics.submit_metrics(submit_hashes) end - - def sidekiq? - Sidekiq.server? - end - - def series_prefix - sidekiq? ? 'sidekiq_' : 'rails_' - end end end end diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb index 8809b7e3f12..d88bcae41fb 100644 --- a/spec/lib/gitlab/metrics/instrumentation_spec.rb +++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb @@ -39,6 +39,12 @@ describe Gitlab::Metrics::Instrumentation do allow(@dummy).to receive(:name).and_return('Dummy') end + describe '.series' do + it 'returns a String' do + expect(described_class.series).to be_an_instance_of(String) + end + end + describe '.configure' do it 'yields self' do described_class.configure do |c| @@ -78,8 +84,7 @@ describe Gitlab::Metrics::Instrumentation do allow(described_class).to receive(:transaction). and_return(transaction) - expect(transaction).to receive(:measure_method). - with('Dummy.foo') + expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure) @dummy.foo end @@ -157,8 +162,7 @@ describe Gitlab::Metrics::Instrumentation do allow(described_class).to receive(:transaction). and_return(transaction) - expect(transaction).to receive(:measure_method). - with('Dummy#bar') + expect_any_instance_of(Gitlab::Metrics::MethodCall).to receive(:measure) @dummy.new.bar end diff --git a/spec/lib/gitlab/metrics/system_spec.rb b/spec/lib/gitlab/metrics/system_spec.rb index cf0e282c2fb..9e2ea89a712 100644 --- a/spec/lib/gitlab/metrics/system_spec.rb +++ b/spec/lib/gitlab/metrics/system_spec.rb @@ -28,20 +28,20 @@ describe Gitlab::Metrics::System do end describe '.cpu_time' do - it 'returns a Float' do - expect(described_class.cpu_time).to be_an_instance_of(Float) + it 'returns a Fixnum' do + expect(described_class.cpu_time).to be_an_instance_of(Fixnum) end end describe '.real_time' do - it 'returns a Float' do - expect(described_class.real_time).to be_an_instance_of(Float) + it 'returns a Fixnum' do + expect(described_class.real_time).to be_an_instance_of(Fixnum) end end describe '.monotonic_time' do - it 'returns a Float' do - expect(described_class.monotonic_time).to be_an_instance_of(Float) + it 'returns a Fixnum' do + expect(described_class.monotonic_time).to be_an_instance_of(Fixnum) end end end diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb index 3b1c67a2147..f1a191d9410 100644 --- a/spec/lib/gitlab/metrics/transaction_spec.rb +++ b/spec/lib/gitlab/metrics/transaction_spec.rb @@ -46,19 +46,11 @@ describe Gitlab::Metrics::Transaction do end end - describe '#measure_method' do - it 'adds a new method if it does not exist already' do - transaction.measure_method('Foo#bar') { 'foo' } + describe '#method_call_for' do + it 'returns a MethodCall' do + method = transaction.method_call_for('Foo#bar') - expect(transaction.methods['Foo#bar']). - to be_an_instance_of(Gitlab::Metrics::MethodCall) - end - - it 'adds timings to an existing method call' do - transaction.measure_method('Foo#bar') { 'foo' } - transaction.measure_method('Foo#bar') { 'foo' } - - expect(transaction.methods['Foo#bar'].call_count).to eq(2) + expect(method).to be_an_instance_of(Gitlab::Metrics::MethodCall) end end diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb index 96f7eabbca6..84f9475a0f8 100644 --- a/spec/lib/gitlab/metrics_spec.rb +++ b/spec/lib/gitlab/metrics_spec.rb @@ -147,4 +147,10 @@ describe Gitlab::Metrics do end end end + + describe '#series_prefix' do + it 'returns a String' do + expect(described_class.series_prefix).to be_an_instance_of(String) + end + end end -- cgit v1.2.1 From 94e6d51ec047fbf01cae2814003bba7ee2ff8546 Mon Sep 17 00:00:00 2001 From: Herminio Torres Date: Wed, 27 Jul 2016 00:56:17 -0300 Subject: Fix the title of the toggle dropdown button Before when you choose the way of `sort` instead it display the title correctly it was just apply the humanize helper in sort value. E.g. When you choose `Last updated` it should display the title `Last updated` instead of `Recently updated`. This fix makes this correctly displays the title. Change the implementation of the `link_to` `filter_branches_path` - Change the value of the `params[:sort]` in `link_to`. E.g. instead of using `'recently_updated'` is now using `sort_value_recently_updated`. - Change the values of the case in the `branches_sorted_by` method for the values it receives in the `params[:sort]` that are: `nil`, `'name'`, `'updated_desc'`, `'updated_asc'`. --- CHANGELOG | 1 + app/controllers/projects/branches_controller.rb | 1 + app/models/repository.rb | 4 ++-- app/views/projects/branches/index.html.haml | 11 ++++------- spec/finders/branches_finder_spec.rb | 8 ++++---- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index cf75bee4348..8f8439158df 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.11.0 (unreleased) + - Fix the title of the toggle dropdown button. !5515 (herminiotorres) - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) - Fix CI status icon link underline (ClemMakesApps) - Fix of 'Commits being passed to custom hooks are already reachable when using the UI' diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 6126acccaab..e926043f3eb 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -6,6 +6,7 @@ class Projects::BranchesController < Projects::ApplicationController before_action :authorize_push_code!, only: [:new, :create, :destroy] def index + @sort = params[:sort].presence || 'name' @branches = BranchesFinder.new(@repository, params).execute @branches = Kaminari.paginate_array(@branches).page(params[:page]) diff --git a/app/models/repository.rb b/app/models/repository.rb index d8775ecbd6c..8a6f4086518 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -615,11 +615,11 @@ class Repository case value when 'name' branches.sort_by(&:name) - when 'recently_updated' + when 'updated_desc' branches.sort do |a, b| commit(b.target).committed_date <=> commit(a.target).committed_date end - when 'last_updated' + when 'updated_asc' branches.sort do |a, b| commit(a.target).committed_date <=> commit(b.target).committed_date end diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index cb190d121c2..e889f29c816 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -14,18 +14,15 @@ .dropdown.inline %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} %span.light - - if params[:sort].present? - = params[:sort].humanize - - else - Name + = projects_sort_options_hash[@sort] %b.caret %ul.dropdown-menu.dropdown-menu-align-right %li - = link_to filter_branches_path(sort: nil) do + = link_to filter_branches_path(sort: sort_value_name) do = sort_title_name - = link_to filter_branches_path(sort: 'recently_updated') do + = link_to filter_branches_path(sort: sort_value_recently_updated) do = sort_title_recently_updated - = link_to filter_branches_path(sort: 'last_updated') do + = link_to filter_branches_path(sort: sort_value_oldest_updated) do = sort_title_oldest_updated - if can? current_user, :push_code, @project diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb index dd85203a038..6ea9a3a3ec5 100644 --- a/spec/finders/branches_finder_spec.rb +++ b/spec/finders/branches_finder_spec.rb @@ -16,7 +16,7 @@ describe BranchesFinder do end it 'sorts by recently_updated' do - branches_finder = described_class.new(repository, { sort: 'recently_updated' }) + branches_finder = described_class.new(repository, { sort: 'updated_desc' }) result = branches_finder.execute @@ -24,7 +24,7 @@ describe BranchesFinder do end it 'sorts by last_updated' do - branches_finder = described_class.new(repository, { sort: 'last_updated' }) + branches_finder = described_class.new(repository, { sort: 'updated_asc' }) result = branches_finder.execute @@ -53,7 +53,7 @@ describe BranchesFinder do context 'filter and sort' do it 'filters branches by name and sorts by recently_updated' do - params = { sort: 'recently_updated', search: 'feature' } + params = { sort: 'updated_desc', search: 'feature' } branches_finder = described_class.new(repository, params) result = branches_finder.execute @@ -63,7 +63,7 @@ describe BranchesFinder do end it 'filters branches by name and sorts by last_updated' do - params = { sort: 'last_updated', search: 'feature' } + params = { sort: 'updated_asc', search: 'feature' } branches_finder = described_class.new(repository, params) result = branches_finder.execute -- cgit v1.2.1 From 32d8aa6d5e1a6dbcd861a4e5baa24c3a873b57fe Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 28 Jul 2016 16:28:12 +0200 Subject: fix repo hooks missing on import fix spec and added changelog --- CHANGELOG | 1 + lib/gitlab/import_export/command_line_util.rb | 8 ++++++++ lib/gitlab/import_export/repo_restorer.rb | 12 +++++++++++- spec/features/projects/import_export/import_file_spec.rb | 3 ++- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 288adccbba2..b5f1b1a56b0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -28,6 +28,7 @@ v 8.11.0 (unreleased) - Change requests_profiles resource constraint to catch virtually any file v 8.10.3 (unreleased) + - Fix hooks missing on imported GitLab projects v 8.10.2 - User can now search branches by name. !5144 diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb index 5dd0e34c18e..e522a0fc8f6 100644 --- a/lib/gitlab/import_export/command_line_util.rb +++ b/lib/gitlab/import_export/command_line_util.rb @@ -17,6 +17,10 @@ module Gitlab execute(%W(#{git_bin_path} clone --bare #{bundle_path} #{repo_path})) end + def git_restore_hooks + execute(%W(#{Gitlab.config.gitlab_shell.path}/bin/create-hooks) + repository_storage_paths_args) + end + private def tar_with_options(archive:, dir:, options:) @@ -45,6 +49,10 @@ module Gitlab FileUtils.copy_entry(source, destination) true end + + def repository_storage_paths_args + Gitlab.config.repositories.storages.values + end end end end diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb index f84de652a57..6d9379acf25 100644 --- a/lib/gitlab/import_export/repo_restorer.rb +++ b/lib/gitlab/import_export/repo_restorer.rb @@ -14,7 +14,7 @@ module Gitlab FileUtils.mkdir_p(path_to_repo) - git_unbundle(repo_path: path_to_repo, bundle_path: @path_to_bundle) + git_unbundle(repo_path: path_to_repo, bundle_path: @path_to_bundle) && repo_restore_hooks rescue => e @shared.error(e) false @@ -29,6 +29,16 @@ module Gitlab def path_to_repo @project.repository.path_to_repo end + + def repo_restore_hooks + return true if wiki? + + git_restore_hooks + end + + def wiki? + @project.class.name == 'ProjectWiki' + end end end end diff --git a/spec/features/projects/import_export/import_file_spec.rb b/spec/features/projects/import_export/import_file_spec.rb index 2d1e3bbebe5..7835e1678ad 100644 --- a/spec/features/projects/import_export/import_file_spec.rb +++ b/spec/features/projects/import_export/import_file_spec.rb @@ -8,6 +8,7 @@ feature 'project import', feature: true, js: true do let(:file) { File.join(Rails.root, 'spec', 'features', 'projects', 'import_export', 'test_project_export.tar.gz') } let(:export_path) { "#{Dir::tmpdir}/import_file_spec" } let(:project) { Project.last } + let(:project_hook) { Gitlab::Git::Hook.new('post-receive', project.repository.path) } background do allow_any_instance_of(Gitlab::ImportExport).to receive(:storage_path).and_return(export_path) @@ -37,7 +38,7 @@ feature 'project import', feature: true, js: true do expect(project).not_to be_nil expect(project.issues).not_to be_empty expect(project.merge_requests).not_to be_empty - expect(project.repo_exists?).to be true + expect(project_hook).to exist expect(wiki_exists?).to be true expect(project.import_status).to eq('finished') end -- cgit v1.2.1 From 39ec67255ac84e2ab9f6657aaed8855917ba70fe Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Thu, 28 Jul 2016 10:02:49 -0600 Subject: Upgrade newrelic_rpm from 3.14.1.311 to 3.16.0.318. Bug fixes and compatibility with Rails 5. Changelog: https://github.com/newrelic/rpm/blob/master/CHANGELOG --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 071277de068..f96ab3ad0b6 100644 --- a/Gemfile +++ b/Gemfile @@ -326,7 +326,7 @@ group :production do gem 'gitlab_meta', '7.0' end -gem 'newrelic_rpm', '~> 3.14' +gem 'newrelic_rpm', '~> 3.16' gem 'octokit', '~> 4.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index 670578dec6d..c5468f5db78 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -404,7 +404,7 @@ GEM nested_form (0.3.2) net-ldap (0.12.1) net-ssh (3.0.1) - newrelic_rpm (3.14.1.311) + newrelic_rpm (3.16.0.318) nokogiri (1.6.8) mini_portile2 (~> 2.1.0) pkg-config (~> 1.1.7) @@ -902,7 +902,7 @@ DEPENDENCIES mysql2 (~> 0.3.16) nested_form (~> 0.3.2) net-ssh (~> 3.0.1) - newrelic_rpm (~> 3.14) + newrelic_rpm (~> 3.16) nokogiri (~> 1.6.7, >= 1.6.7.2) oauth2 (~> 1.2.0) octokit (~> 4.3.0) -- cgit v1.2.1 From d00679d54f2bbaab9d6963c3b871a03ab8a3d7d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Wed, 27 Jul 2016 11:26:29 -0400 Subject: Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 4 +- app/models/repository.rb | 31 ++++++++------- app/services/delete_branch_service.rb | 2 +- app/services/delete_tag_service.rb | 2 +- app/services/git_tag_push_service.rb | 4 +- app/views/projects/branches/_commit.html.haml | 2 +- .../projects/issues/_related_branches.html.haml | 4 +- spec/models/repository_spec.rb | 45 +++++++++++----------- .../issues/_related_branches.html.haml_spec.rb | 21 ++++++++++ 11 files changed, 72 insertions(+), 46 deletions(-) create mode 100644 spec/views/projects/issues/_related_branches.html.haml_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 833a55e44f9..68f813af113 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.11.0 (unreleased) - Limit git rev-list output count to one in forced push check - Clean up unused routes (Josef Strzibny) - Add green outline to New Branch button. !5447 (winniehell) + - Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects - Retrieve rendered HTML from cache in one request - Fix renaming repository when name contains invalid chararacters under project settings - Nokogiri's various parsing methods are now instrumented diff --git a/Gemfile b/Gemfile index 071277de068..a7d5e0e3e89 100644 --- a/Gemfile +++ b/Gemfile @@ -53,7 +53,7 @@ gem 'browser', '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem 'gitlab_git', '~> 10.3.2' +gem 'gitlab_git', '~> 10.4.1' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes diff --git a/Gemfile.lock b/Gemfile.lock index 670578dec6d..150a98bb7d0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -278,7 +278,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) - gitlab_git (10.3.2) + gitlab_git (10.4.1) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -870,7 +870,7 @@ DEPENDENCIES github-linguist (~> 4.7.0) github-markup (~> 1.4) gitlab-flowdock-git-hook (~> 1.0.1) - gitlab_git (~> 10.3.2) + gitlab_git (~> 10.4.1) gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) diff --git a/app/models/repository.rb b/app/models/repository.rb index af65e5b20ec..bac37483c47 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -70,7 +70,12 @@ class Repository def commit(ref = 'HEAD') return nil unless exists? - commit = Gitlab::Git::Commit.find(raw_repository, ref) + commit = + if ref.is_a?(Gitlab::Git::Commit) + ref + else + Gitlab::Git::Commit.find(raw_repository, ref) + end commit = ::Commit.new(commit, @project) if commit commit rescue Rugged::OdbError @@ -158,7 +163,7 @@ class Repository before_remove_branch branch = find_branch(branch_name) - oldrev = branch.try(:target) + oldrev = branch.try(:target).try(:id) newrev = Gitlab::Git::BLANK_SHA ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name @@ -259,10 +264,10 @@ class Repository # Rugged seems to throw a `ReferenceError` when given branch_names rather # than SHA-1 hashes number_commits_behind = raw_repository. - count_commits_between(branch.target, root_ref_hash) + count_commits_between(branch.target.sha, root_ref_hash) number_commits_ahead = raw_repository. - count_commits_between(root_ref_hash, branch.target) + count_commits_between(root_ref_hash, branch.target.sha) { behind: number_commits_behind, ahead: number_commits_ahead } end @@ -688,9 +693,7 @@ class Repository end def local_branches - @local_branches ||= rugged.branches.each(:local).map do |branch| - Gitlab::Git::Branch.new(branch.name, branch.target) - end + @local_branches ||= raw_repository.local_branches end alias_method :branches, :local_branches @@ -831,7 +834,7 @@ class Repository end def revert(user, commit, base_branch, revert_tree_id = nil) - source_sha = find_branch(base_branch).target + source_sha = find_branch(base_branch).target.sha revert_tree_id ||= check_revert_content(commit, base_branch) return false unless revert_tree_id @@ -848,7 +851,7 @@ class Repository end def cherry_pick(user, commit, base_branch, cherry_pick_tree_id = nil) - source_sha = find_branch(base_branch).target + source_sha = find_branch(base_branch).target.sha cherry_pick_tree_id ||= check_cherry_pick_content(commit, base_branch) return false unless cherry_pick_tree_id @@ -869,7 +872,7 @@ class Repository end def check_revert_content(commit, base_branch) - source_sha = find_branch(base_branch).target + source_sha = find_branch(base_branch).target.sha args = [commit.id, source_sha] args << { mainline: 1 } if commit.merge_commit? @@ -883,7 +886,7 @@ class Repository end def check_cherry_pick_content(commit, base_branch) - source_sha = find_branch(base_branch).target + source_sha = find_branch(base_branch).target.sha args = [commit.id, source_sha] args << 1 if commit.merge_commit? @@ -974,7 +977,7 @@ class Repository was_empty = empty? if !was_empty && target_branch - oldrev = target_branch.target + oldrev = target_branch.target.id end # Make commit @@ -994,7 +997,7 @@ class Repository after_create_branch else # Update head - current_head = find_branch(branch).target + current_head = find_branch(branch).target.id # Make sure target branch was not changed during pre-receive hook if current_head == oldrev @@ -1052,7 +1055,7 @@ class Repository end def tags_sorted_by_committed_date - tags.sort_by { |tag| commit(tag.target).committed_date } + tags.sort_by { |tag| tag.target.committed_date } end def keep_around_ref_name(sha) diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb index 332c55581a1..87f066edb6f 100644 --- a/app/services/delete_branch_service.rb +++ b/app/services/delete_branch_service.rb @@ -40,6 +40,6 @@ class DeleteBranchService < BaseService def build_push_data(branch) Gitlab::PushDataBuilder - .build(project, current_user, branch.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", []) + .build(project, current_user, branch.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", []) end end diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb index 1e41fbe34b6..32e0eed6b63 100644 --- a/app/services/delete_tag_service.rb +++ b/app/services/delete_tag_service.rb @@ -34,6 +34,6 @@ class DeleteTagService < BaseService def build_push_data(tag) Gitlab::PushDataBuilder - .build(project, current_user, tag.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", []) + .build(project, current_user, tag.target.sha, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", []) end end diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index 58573078048..969530c4fdc 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -26,8 +26,8 @@ class GitTagPushService < BaseService unless Gitlab::Git.blank_ref?(params[:newrev]) tag_name = Gitlab::Git.ref_name(params[:ref]) tag = project.repository.find_tag(tag_name) - - if tag && tag.target == params[:newrev] + + if tag && tag.object_sha == params[:newrev] commit = project.commit(tag.target) commits = [commit].compact message = tag.message diff --git a/app/views/projects/branches/_commit.html.haml b/app/views/projects/branches/_commit.html.haml index 9fe65cbb104..d54c76ff9c8 100644 --- a/app/views/projects/branches/_commit.html.haml +++ b/app/views/projects/branches/_commit.html.haml @@ -1,5 +1,5 @@ .branch-commit - = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit-id monospace" + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-id monospace" · %span.str-truncated = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml index c6fc499a7b8..6ea9f612d13 100644 --- a/app/views/projects/issues/_related_branches.html.haml +++ b/app/views/projects/issues/_related_branches.html.haml @@ -4,8 +4,8 @@ %ul.unstyled-list - @related_branches.each do |branch| %li - - sha = @project.repository.find_branch(branch).target - - pipeline = @project.pipeline(sha, branch) if sha + - target = @project.repository.find_branch(branch).target + - pipeline = @project.pipeline(target.sha, branch) if target - if pipeline %span.related-branch-ci-status = render_pipeline_status(pipeline) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 5bc1bd9a930..cce15538b93 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -50,8 +50,9 @@ describe Repository, models: true do double_first = double(committed_date: Time.now) double_last = double(committed_date: Time.now - 1.second) - allow(repository).to receive(:commit).with(tag_a.target).and_return(double_first) - allow(repository).to receive(:commit).with(tag_b.target).and_return(double_last) + allow(tag_a).to receive(:target).and_return(double_first) + allow(tag_b).to receive(:target).and_return(double_last) + allow(repository).to receive(:tags).and_return([tag_a, tag_b]) end it { is_expected.to eq(['v1.0.0', 'v1.1.0']) } @@ -64,8 +65,9 @@ describe Repository, models: true do double_first = double(committed_date: Time.now - 1.second) double_last = double(committed_date: Time.now) - allow(repository).to receive(:commit).with(tag_a.target).and_return(double_last) - allow(repository).to receive(:commit).with(tag_b.target).and_return(double_first) + allow(tag_a).to receive(:target).and_return(double_last) + allow(tag_b).to receive(:target).and_return(double_first) + allow(repository).to receive(:tags).and_return([tag_a, tag_b]) end it { is_expected.to eq(['v1.1.0', 'v1.0.0']) } @@ -381,9 +383,13 @@ describe Repository, models: true do end describe '#rm_branch' do + let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature + let(:blank_sha) { '0000000000000000000000000000000000000000' } + context 'when pre hooks were successful' do it 'should run without errors' do - allow_any_instance_of(Gitlab::Git::Hook).to receive(:trigger).and_return([true, nil]) + expect_any_instance_of(GitHooksService).to receive(:execute). + with(user, project.repository.path_to_repo, old_rev, blank_sha, 'refs/heads/feature') expect { repository.rm_branch(user, 'feature') }.not_to raise_error end @@ -418,10 +424,13 @@ describe Repository, models: true do end describe '#commit_with_hooks' do + let(:old_rev) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } # git rev-parse feature + context 'when pre hooks were successful' do before do expect_any_instance_of(GitHooksService).to receive(:execute). - and_return(true) + with(user, repository.path_to_repo, old_rev, sample_commit.id, 'refs/heads/feature'). + and_yield.and_return(true) end it 'should run without errors' do @@ -435,6 +444,14 @@ describe Repository, models: true do repository.commit_with_hooks(user, 'feature') { sample_commit.id } end + + context "when the branch wasn't empty" do + it 'updates the head' do + expect(repository.find_branch('feature').target.id).to eq(old_rev) + repository.commit_with_hooks(user, 'feature') { sample_commit.id } + expect(repository.find_branch('feature').target.id).to eq(sample_commit.id) + end + end end context 'when pre hooks failed' do @@ -1198,17 +1215,6 @@ describe Repository, models: true do end end - describe '#local_branches' do - it 'returns the local branches' do - masterrev = repository.find_branch('master').target - create_remote_branch('joe', 'remote_branch', masterrev) - repository.add_branch(user, 'local_branch', masterrev) - - expect(repository.local_branches.any? { |branch| branch.name == 'remote_branch' }).to eq(false) - expect(repository.local_branches.any? { |branch| branch.name == 'local_branch' }).to eq(true) - end - end - describe "#keep_around" do it "does not fail if we attempt to reference bad commit" do expect(repository.kept_around?('abc1234')).to be_falsey @@ -1236,9 +1242,4 @@ describe Repository, models: true do File.delete(path) end end - - def create_remote_branch(remote_name, branch_name, target) - rugged = repository.rugged - rugged.references.create("refs/remotes/#{remote_name}/#{branch_name}", target) - end end diff --git a/spec/views/projects/issues/_related_branches.html.haml_spec.rb b/spec/views/projects/issues/_related_branches.html.haml_spec.rb new file mode 100644 index 00000000000..78af61f15a7 --- /dev/null +++ b/spec/views/projects/issues/_related_branches.html.haml_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe 'projects/issues/_related_branches' do + include Devise::TestHelpers + + let(:project) { create(:project) } + let(:branch) { project.repository.find_branch('feature') } + let!(:pipeline) { create(:ci_pipeline, project: project, sha: branch.target.id, ref: 'feature') } + + before do + assign(:project, project) + assign(:related_branches, ['feature']) + + render + end + + it 'shows the related branches with their build status' do + expect(rendered).to match('feature') + expect(rendered).to have_css('.related-branch-ci-status') + end +end -- cgit v1.2.1 From 230f6910135f4449b5b5e83867922bf2580ba655 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 27 Jul 2016 19:36:49 -0700 Subject: Add a log message when a project is scheduled for destruction for debugging --- app/models/project.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index b3703d71e72..71c590582ca 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1157,7 +1157,10 @@ class Project < ActiveRecord::Base def schedule_delete!(user_id, params) # Queue this task for after the commit, so once we mark pending_delete it will run - run_after_commit { ProjectDestroyWorker.perform_async(id, user_id, params) } + run_after_commit do + job_id = ProjectDestroyWorker.perform_async(id, user_id, params) + Rails.logger.info("User #{user_id} scheduled destruction of project #{path_with_namespace} with job ID #{job_id}") + end update_attribute(:pending_delete, true) end -- cgit v1.2.1 From d49e6f355010a31a080da75789b46603989925ad Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 27 Jul 2016 11:51:52 -0500 Subject: Add pipeline icon to admin builds; position warning icon after sha --- app/views/admin/builds/_build.html.haml | 7 ++++--- app/views/projects/ci/builds/_build.html.haml | 13 +++++-------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml index ce818c30c30..6d1ccf32b56 100644 --- a/app/views/admin/builds/_build.html.haml +++ b/app/views/admin/builds/_build.html.haml @@ -11,16 +11,17 @@ - else %span.build-link ##{build.id} - - if build.stuck? - %i.fa.fa-warning.text-warning - - if build.ref + .icon-container + = build.tag? ? icon('tag') : icon('code-fork') = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" - else .light none = custom_icon("icon_commit") = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace commit-id" + - if build.stuck? + %i.fa.fa-warning.text-warning .label-container - if build.tags.any? diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index a3114771a42..91081435220 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -13,13 +13,6 @@ - else %span ##{build.id} - - if build.stuck? - .icon-container - = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') - - if defined?(retried) && retried - .icon-container - = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') - - if defined?(ref) && ref - if build.ref .icon-container @@ -33,6 +26,11 @@ - if defined?(commit_sha) && commit_sha = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "commit-id monospace" + - if build.stuck? + = icon('warning', class: 'text-warning has-tooltip', title: 'Build is stuck. Check runners.') + - if defined?(retried) && retried + = icon('warning', class: 'text-warning has-tooltip', title: 'Build was retried.') + .label-container - if build.tags.any? - build.tags.each do |tag| @@ -47,7 +45,6 @@ - if build.manual? %span.label.label-info manual - - if defined?(runner) && runner %td - if build.try(:runner) -- cgit v1.2.1 From d286c624defd0549642c787ccd6782ee3e5ff42f Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 27 Jul 2016 12:59:35 -0500 Subject: Adjust min-widths of pipelines and builds tables --- app/assets/stylesheets/pages/pipelines.scss | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index c58e2ffe7f5..7411c1c4499 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -18,6 +18,10 @@ .btn { margin: 4px; } + + .table.builds { + min-width: 1200px; + } } .content-list { @@ -35,7 +39,7 @@ } .table.builds { - min-width: 1200px; + min-width: 900px; &.pipeline { min-width: 650px; -- cgit v1.2.1 From 59797acd85037e9f491d298ac37743897ca2d6c2 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 27 Jul 2016 13:29:32 -0500 Subject: Add icon container --- app/views/admin/builds/_build.html.haml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/views/admin/builds/_build.html.haml b/app/views/admin/builds/_build.html.haml index 6d1ccf32b56..352adbedee4 100644 --- a/app/views/admin/builds/_build.html.haml +++ b/app/views/admin/builds/_build.html.haml @@ -17,7 +17,8 @@ = link_to build.ref, namespace_project_commits_path(build.project.namespace, build.project, build.ref), class: "monospace branch-name" - else .light none - = custom_icon("icon_commit") + .icon-container + = custom_icon("icon_commit") = link_to build.short_sha, namespace_project_commit_path(build.project.namespace, build.project, build.sha), class: "monospace commit-id" - if build.stuck? -- cgit v1.2.1 From cebda439ea88d9868902120b1d20ff304610d9fd Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Wed, 27 Jul 2016 17:31:53 -0500 Subject: Decrease icon container width to help fit all pipeline commit info on two lines --- app/assets/stylesheets/pages/pipelines.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 7411c1c4499..21919fe4d73 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -132,7 +132,7 @@ .icon-container { display: inline-block; text-align: right; - width: 20px; + width: 15px; .fa { position: relative; -- cgit v1.2.1 From 3f1422a629a6866bc4cf164c00479eedb891ef8a Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 28 Jul 2016 11:48:14 -0500 Subject: Add CI configuration button on project page --- CHANGELOG | 1 + app/views/projects/show.html.haml | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 8759bb04dec..d2112fe2156 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,6 +26,7 @@ v 8.11.0 (unreleased) - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) - Profile requests when a header is passed - Add commit stats in commit api. !5517 (dixpac) + - Add CI configuration button on project page - Make error pages responsive (Takuya Noguchi) - Change requests_profiles resource constraint to catch virtually any file diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index dd1cf680cfa..cd0cd923ddb 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -43,6 +43,10 @@ %li = link_to 'Contribution guide', contribution_guide_path(@project) + - if @repository.gitlab_ci_yml + %li + = link_to 'CI configuration', project_environments_path(@project) + - if current_user && can_push_branch?(@project, @project.default_branch) - unless @repository.changelog %li.missing -- cgit v1.2.1 From afca25ed8abddf4f9f3767fbc26a990a6e61dc8b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Thu, 28 Jul 2016 12:06:15 -0500 Subject: Link configuration button to .gitlab-ci.yml --- app/helpers/projects_helper.rb | 4 ++++ app/views/projects/show.html.haml | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index a733dff1579..505545fbabb 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -263,6 +263,10 @@ module ProjectsHelper filename_path(project, :version) end + def ci_configuration_path(project) + filename_path(project, :gitlab_ci_yml) + end + def project_wiki_path_with_version(proj, page, version, is_newest) url_params = is_newest ? {} : { version_id: version } namespace_project_wiki_path(proj.namespace, proj, page, url_params) diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index cd0cd923ddb..a666d07e9eb 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -45,7 +45,7 @@ - if @repository.gitlab_ci_yml %li - = link_to 'CI configuration', project_environments_path(@project) + = link_to 'CI configuration', ci_configuration_path(@project) - if current_user && can_push_branch?(@project, @project.default_branch) - unless @repository.changelog -- cgit v1.2.1 From 20a9bc938350a3c48377931ea839e3128d919b44 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Thu, 28 Jul 2016 19:59:50 +0200 Subject: Remove project which can't be pulled while seeding [ci skip] --- db/fixtures/development/04_project.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb index b4639999967..e3316ecdb6c 100644 --- a/db/fixtures/development/04_project.rb +++ b/db/fixtures/development/04_project.rb @@ -20,7 +20,6 @@ Sidekiq::Testing.inline! do 'https://github.com/airbnb/javascript.git', 'https://github.com/tessalt/echo-chamber-js.git', 'https://github.com/atom/atom.git', - 'https://github.com/ipselon/react-ui-builder.git', 'https://github.com/mattermost/platform.git', 'https://github.com/purifycss/purifycss.git', 'https://github.com/facebook/nuclide.git', -- cgit v1.2.1 From 08bac551494d92bc096859144a7a631b987ac35d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Thu, 28 Jul 2016 16:37:03 -0400 Subject: Fix failing CommitController spec --- spec/controllers/projects/commit_controller_spec.rb | 9 --------- 1 file changed, 9 deletions(-) diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 3001d32e719..df902da86f8 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -24,15 +24,6 @@ describe Projects::CommitController do get :show, params.merge(extra_params) end - let(:project) { create(:project) } - - before do - user = create(:user) - project.team << [user, :master] - - sign_in(user) - end - context 'with valid id' do it 'responds with 200' do go(id: commit.id) -- cgit v1.2.1 From a71db022932b92e83977f81c3d94899ba2f7baeb Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 27 Jul 2016 17:43:36 +0100 Subject: Added es6 regex to teaspoon matchers, still doesnt fix it, problem with sprockets-es6 --- spec/teaspoon_env.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/teaspoon_env.rb b/spec/teaspoon_env.rb index 69b2b9b6d5b..1497a4eb710 100644 --- a/spec/teaspoon_env.rb +++ b/spec/teaspoon_env.rb @@ -38,7 +38,7 @@ Teaspoon.configure do |config| # Specify a file matcher as a regular expression and all matching files will be loaded when the suite is run. These # files need to be within an asset path. You can add asset paths using the `config.asset_paths`. - suite.matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.coffee,coffee}" + suite.matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.coffee,coffee,js.es6,es6}" # Load additional JS files, but requiring them in your spec helper is the preferred way to do this. # suite.javascripts = [] -- cgit v1.2.1 From 42352d4b367d9d432bd92838171260d4559ba42e Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 28 Jul 2016 16:34:32 +0100 Subject: Removed coffee matchers --- spec/teaspoon_env.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/teaspoon_env.rb b/spec/teaspoon_env.rb index 1497a4eb710..1a3bbb9c8cc 100644 --- a/spec/teaspoon_env.rb +++ b/spec/teaspoon_env.rb @@ -38,7 +38,7 @@ Teaspoon.configure do |config| # Specify a file matcher as a regular expression and all matching files will be loaded when the suite is run. These # files need to be within an asset path. You can add asset paths using the `config.asset_paths`. - suite.matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.coffee,coffee,js.es6,es6}" + suite.matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.es6,es6}" # Load additional JS files, but requiring them in your spec helper is the preferred way to do this. # suite.javascripts = [] -- cgit v1.2.1 From f1e46d1e63faf63f1dc9960c5f28d5260dfc84db Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Tue, 5 Jul 2016 12:29:15 +0530 Subject: Add a series of migrations changing the model-level design of protected branch access levels. 1. Remove the `developers_can_push` and `developers_can_merge` boolean columns. 2. Add two new tables, `protected_branches_push_access`, and `protected_branches_merge_access`. Each row of these 'access' tables is linked to a protected branch, and uses a `access_level` column to figure out settings for the protected branch. 3. The `access_level` column is intended to be used with rails' `enum`, with `:masters` at index 0 and `:developers` at index 1. 4. Doing it this way has a few advantages: - Cleaner path to planned EE features where a protected branch is accessible only by certain users or groups. - Rails' `enum` doesn't allow a declaration like this due to the duplicates. This approach doesn't have this problem. enum can_be_pushed_by: [:masters, :developers] enum can_be_merged_by: [:masters, :developers] --- ...705054938_add_protected_branches_push_access.rb | 15 ++++++++++ ...05054952_add_protected_branches_merge_access.rb | 15 ++++++++++ ...can_merge_to_protected_branches_merge_access.rb | 33 ++++++++++++++++++++++ ...s_can_push_to_protected_branches_push_access.rb | 33 ++++++++++++++++++++++ ..._developers_can_push_from_protected_branches.rb | 21 ++++++++++++++ ...developers_can_merge_from_protected_branches.rb | 21 ++++++++++++++ db/schema.rb | 26 ++++++++++++++--- 7 files changed, 160 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20160705054938_add_protected_branches_push_access.rb create mode 100644 db/migrate/20160705054952_add_protected_branches_merge_access.rb create mode 100644 db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb create mode 100644 db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb create mode 100644 db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb create mode 100644 db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb diff --git a/db/migrate/20160705054938_add_protected_branches_push_access.rb b/db/migrate/20160705054938_add_protected_branches_push_access.rb new file mode 100644 index 00000000000..512d99e4823 --- /dev/null +++ b/db/migrate/20160705054938_add_protected_branches_push_access.rb @@ -0,0 +1,15 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddProtectedBranchesPushAccess < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + def change + create_table :protected_branch_push_access_levels do |t| + t.references :protected_branch, index: { name: "index_protected_branch_push_access" }, foreign_key: true, null: false + t.integer :access_level, default: 0, null: false + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20160705054952_add_protected_branches_merge_access.rb b/db/migrate/20160705054952_add_protected_branches_merge_access.rb new file mode 100644 index 00000000000..9f82c0a8aa3 --- /dev/null +++ b/db/migrate/20160705054952_add_protected_branches_merge_access.rb @@ -0,0 +1,15 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddProtectedBranchesMergeAccess < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + def change + create_table :protected_branch_merge_access_levels do |t| + t.references :protected_branch, index: { name: "index_protected_branch_merge_access" }, foreign_key: true, null: false + t.integer :access_level, default: 0, null: false + + t.timestamps null: false + end + end +end diff --git a/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb b/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb new file mode 100644 index 00000000000..20ca9c3a488 --- /dev/null +++ b/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb @@ -0,0 +1,33 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class MoveFromDevelopersCanMergeToProtectedBranchesMergeAccess < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def up + execute <<-HEREDOC + INSERT into protected_branch_merge_access_levels (protected_branch_id, access_level, created_at, updated_at) + SELECT id, (CASE WHEN developers_can_merge THEN 1 ELSE 0 END), now(), now() + FROM protected_branches + HEREDOC + end + + def down + execute <<-HEREDOC + UPDATE protected_branches SET developers_can_merge = TRUE + WHERE id IN (SELECT protected_branch_id FROM protected_branch_merge_access_levels + WHERE access_level = 1); + HEREDOC + end +end diff --git a/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb b/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb new file mode 100644 index 00000000000..498fb393d61 --- /dev/null +++ b/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb @@ -0,0 +1,33 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class MoveFromDevelopersCanPushToProtectedBranchesPushAccess < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def up + execute <<-HEREDOC + INSERT into protected_branch_push_access_levels (protected_branch_id, access_level, created_at, updated_at) + SELECT id, (CASE WHEN developers_can_push THEN 1 ELSE 0 END), now(), now() + FROM protected_branches + HEREDOC + end + + def down + execute <<-HEREDOC + UPDATE protected_branches SET developers_can_push = TRUE + WHERE id IN (SELECT protected_branch_id FROM protected_branch_push_access_levels + WHERE access_level = 1); + HEREDOC + end +end diff --git a/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb b/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb new file mode 100644 index 00000000000..1e9977cfa6e --- /dev/null +++ b/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb @@ -0,0 +1,21 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RemoveDevelopersCanPushFromProtectedBranches < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + remove_column :protected_branches, :developers_can_push, :boolean + end +end diff --git a/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb b/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb new file mode 100644 index 00000000000..43d02fbaed6 --- /dev/null +++ b/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb @@ -0,0 +1,21 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RemoveDevelopersCanMergeFromProtectedBranches < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + remove_column :protected_branches, :developers_can_merge, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index 15cee55a7bf..7a5eded8e02 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -867,13 +867,29 @@ ActiveRecord::Schema.define(version: 20160722221922) do add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree + create_table "protected_branch_merge_access_levels", force: :cascade do |t| + t.integer "protected_branch_id", null: false + t.integer "access_level", default: 0, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "protected_branch_merge_access_levels", ["protected_branch_id"], name: "index_protected_branch_merge_access", using: :btree + + create_table "protected_branch_push_access_levels", force: :cascade do |t| + t.integer "protected_branch_id", null: false + t.integer "access_level", default: 0, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "protected_branch_push_access_levels", ["protected_branch_id"], name: "index_protected_branch_push_access", using: :btree + create_table "protected_branches", force: :cascade do |t| - t.integer "project_id", null: false - t.string "name", null: false + t.integer "project_id", null: false + t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" - t.boolean "developers_can_push", default: false, null: false - t.boolean "developers_can_merge", default: false, null: false end add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree @@ -1136,5 +1152,7 @@ ActiveRecord::Schema.define(version: 20160722221922) do add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree add_foreign_key "personal_access_tokens", "users" + add_foreign_key "protected_branch_merge_access_levels", "protected_branches" + add_foreign_key "protected_branch_push_access_levels", "protected_branches" add_foreign_key "u2f_registrations", "users" end -- cgit v1.2.1 From 21bece443d5f871680a3d7649c2d16861035196d Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Tue, 5 Jul 2016 13:10:42 +0530 Subject: Add models for the protected branch access levels. - And hook up their associations. --- app/models/protected_branch.rb | 3 +++ app/models/protected_branch/merge_access_level.rb | 3 +++ app/models/protected_branch/push_access_level.rb | 3 +++ 3 files changed, 9 insertions(+) create mode 100644 app/models/protected_branch/merge_access_level.rb create mode 100644 app/models/protected_branch/push_access_level.rb diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index b7011d7afdf..a411cb417e2 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -5,6 +5,9 @@ class ProtectedBranch < ActiveRecord::Base validates :name, presence: true validates :project, presence: true + has_one :merge_access_level + has_one :push_access_level + def commit project.commit(self.name) end diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb new file mode 100644 index 00000000000..78cec5bf566 --- /dev/null +++ b/app/models/protected_branch/merge_access_level.rb @@ -0,0 +1,3 @@ +class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base + belongs_to :protected_branch +end diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb new file mode 100644 index 00000000000..d53c4c391e3 --- /dev/null +++ b/app/models/protected_branch/push_access_level.rb @@ -0,0 +1,3 @@ +class ProtectedBranch::PushAccessLevel < ActiveRecord::Base + belongs_to :protected_branch +end -- cgit v1.2.1 From 134fe5af83167f95205a080f7932452de7d77496 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Thu, 7 Jul 2016 13:06:28 +0530 Subject: Use the `{Push,Merge}AccessLevel` models in the UI. 1. Improve error handling while creating protected branches. 2. Modify coffeescript code so that the "Developers can *" checkboxes send a '1' or '0' even when using AJAX. This lets us keep the backend code simpler. 3. Use services for both creating and updating protected branches. Destruction is taken care of with `dependent: :destroy` --- .../projects/protected_branches_controller.rb | 24 +++++++++++++++------- app/models/protected_branch.rb | 12 +++++++++-- app/models/protected_branch/merge_access_level.rb | 2 ++ app/models/protected_branch/push_access_level.rb | 2 ++ app/services/protected_branches/base_service.rb | 17 +++++++++++++++ app/services/protected_branches/create_service.rb | 19 +++++++++++++++++ app/services/protected_branches/update_service.rb | 21 +++++++++++++++++++ 7 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 app/services/protected_branches/base_service.rb create mode 100644 app/services/protected_branches/create_service.rb create mode 100644 app/services/protected_branches/update_service.rb diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index 10dca47fded..fdbe0044d3c 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -3,19 +3,23 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController before_action :require_non_empty_project before_action :authorize_admin_project! before_action :load_protected_branch, only: [:show, :update, :destroy] + before_action :load_protected_branches, only: [:index, :create] layout "project_settings" def index - @protected_branches = @project.protected_branches.order(:name).page(params[:page]) @protected_branch = @project.protected_branches.new gon.push({ open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } } }) end def create - @project.protected_branches.create(protected_branch_params) - redirect_to namespace_project_protected_branches_path(@project.namespace, - @project) + service = ProtectedBranches::CreateService.new(@project, current_user, protected_branch_params) + if service.execute + redirect_to namespace_project_protected_branches_path(@project.namespace, @project) + else + @protected_branch = service.protected_branch + render :index + end end def show @@ -23,13 +27,15 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController end def update - if @protected_branch && @protected_branch.update_attributes(protected_branch_params) + service = ProtectedBranches::UpdateService.new(@project, current_user, params[:id], protected_branch_params) + + if service.execute respond_to do |format| - format.json { render json: @protected_branch, status: :ok } + format.json { render json: service.protected_branch, status: :ok } end else respond_to do |format| - format.json { render json: @protected_branch.errors, status: :unprocessable_entity } + format.json { render json: service.protected_branch.errors, status: :unprocessable_entity } end end end @@ -52,4 +58,8 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController def protected_branch_params params.require(:protected_branch).permit(:name, :developers_can_push, :developers_can_merge) end + + def load_protected_branches + @protected_branches = @project.protected_branches.order(:name).page(params[:page]) + end end diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index a411cb417e2..b0fde6c6c1b 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -5,13 +5,21 @@ class ProtectedBranch < ActiveRecord::Base validates :name, presence: true validates :project, presence: true - has_one :merge_access_level - has_one :push_access_level + has_one :merge_access_level, dependent: :destroy + has_one :push_access_level, dependent: :destroy def commit project.commit(self.name) end + def developers_can_push + self.push_access_level && self.push_access_level.developers? + end + + def developers_can_merge + self.merge_access_level && self.merge_access_level.developers? + end + # Returns all protected branches that match the given branch name. # This realizes all records from the scope built up so far, and does # _not_ return a relation. diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb index 78cec5bf566..cfaa9c166fe 100644 --- a/app/models/protected_branch/merge_access_level.rb +++ b/app/models/protected_branch/merge_access_level.rb @@ -1,3 +1,5 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base belongs_to :protected_branch + + enum access_level: [:masters, :developers] end diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index d53c4c391e3..4345dc4ede4 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -1,3 +1,5 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base belongs_to :protected_branch + + enum access_level: [:masters, :developers] end diff --git a/app/services/protected_branches/base_service.rb b/app/services/protected_branches/base_service.rb new file mode 100644 index 00000000000..d4be8698a5f --- /dev/null +++ b/app/services/protected_branches/base_service.rb @@ -0,0 +1,17 @@ +module ProtectedBranches + class BaseService < ::BaseService + def set_access_levels! + if params[:developers_can_push] == '0' + @protected_branch.push_access_level.masters! + elsif params[:developers_can_push] == '1' + @protected_branch.push_access_level.developers! + end + + if params[:developers_can_merge] == '0' + @protected_branch.merge_access_level.masters! + elsif params[:developers_can_merge] == '1' + @protected_branch.merge_access_level.developers! + end + end + end +end diff --git a/app/services/protected_branches/create_service.rb b/app/services/protected_branches/create_service.rb new file mode 100644 index 00000000000..743f7bd2ce1 --- /dev/null +++ b/app/services/protected_branches/create_service.rb @@ -0,0 +1,19 @@ +class ProtectedBranches::CreateService < BaseService + attr_reader :protected_branch + + def execute + ProtectedBranch.transaction do + @protected_branch = project.protected_branches.new(name: params[:name]) + @protected_branch.save! + + @protected_branch.create_push_access_level! + @protected_branch.create_merge_access_level! + + set_access_levels! + end + + true + rescue ActiveRecord::RecordInvalid + false + end +end diff --git a/app/services/protected_branches/update_service.rb b/app/services/protected_branches/update_service.rb new file mode 100644 index 00000000000..ed59b06b79a --- /dev/null +++ b/app/services/protected_branches/update_service.rb @@ -0,0 +1,21 @@ +module ProtectedBranches + class UpdateService < BaseService + attr_reader :protected_branch + + def initialize(project, current_user, id, params = {}) + super(project, current_user, params) + @id = id + end + + def execute + ProtectedBranch.transaction do + @protected_branch = ProtectedBranch.find(@id) + set_access_levels! + end + + true + rescue ActiveRecord::RecordInvalid + false + end + end +end -- cgit v1.2.1 From f8a04e15371815ad39e2c66056db4ab0439555f4 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Thu, 7 Jul 2016 13:21:13 +0530 Subject: Add seeds for protected branches. --- db/fixtures/development/16_protected_branches.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 db/fixtures/development/16_protected_branches.rb diff --git a/db/fixtures/development/16_protected_branches.rb b/db/fixtures/development/16_protected_branches.rb new file mode 100644 index 00000000000..103c7f9445c --- /dev/null +++ b/db/fixtures/development/16_protected_branches.rb @@ -0,0 +1,12 @@ +Gitlab::Seeder.quiet do + admin_user = User.find(1) + + Project.all.each do |project| + params = { + name: 'master' + } + + ProtectedBranches::CreateService.new(project, admin_user, params).execute + print '.' + end +end -- cgit v1.2.1 From ab6096c17261605d835a4a8edae21f31d90026df Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Thu, 7 Jul 2016 16:18:07 +0530 Subject: Add "No One Can Push" to the protected branches UI. 1. Move to dropdowns instead of checkboxes. One each for "Allowed to Push" and "Allowed to Merge" 2. Refactor the `ProtectedBranches` coffeescript class into `ProtectedBranchesAccessSelect`. 3. Modify the backend to accept the new parameters. --- .../protected_branches_access_select.js.coffee | 39 ++++++++++++++++++++++ app/assets/stylesheets/pages/projects.scss | 5 ++- .../projects/protected_branches_controller.rb | 2 +- app/models/protected_branch/push_access_level.rb | 2 +- app/services/protected_branches/base_service.rb | 20 ++++++----- app/services/protected_branches/create_service.rb | 28 ++++++++-------- .../protected_branches/_branches_list.html.haml | 37 ++++++++++---------- .../protected_branches/_protected_branch.html.haml | 6 ++-- 8 files changed, 95 insertions(+), 44 deletions(-) create mode 100644 app/assets/javascripts/protected_branches_access_select.js.coffee diff --git a/app/assets/javascripts/protected_branches_access_select.js.coffee b/app/assets/javascripts/protected_branches_access_select.js.coffee new file mode 100644 index 00000000000..b472ff7ec27 --- /dev/null +++ b/app/assets/javascripts/protected_branches_access_select.js.coffee @@ -0,0 +1,39 @@ +class @ProtectedBranchesAccessSelect + constructor: () -> + $(".allowed-to-merge").each (i, element) => + fieldName = $(element).data('field-name') + $(element).glDropdown + data: [{id: 'developers', text: 'Developers'}, {id: 'masters', text: 'Masters'}] + selectable: true + fieldName: fieldName + clicked: _.partial(@onSelect, element) + + $(".allowed-to-push").each (i, element) => + fieldName = $(element).data('field-name') + $(element).glDropdown + data: [{id: 'no_one', text: 'No one'}, + {id: 'developers', text: 'Developers'}, + {id: 'masters', text: 'Masters'}] + selectable: true + fieldName: fieldName + clicked: _.partial(@onSelect, element) + + + onSelect: (dropdown, selected, element, e) => + $(dropdown).find('.dropdown-toggle-text').text(selected.text) + $.ajax + type: "PATCH" + url: $(dropdown).data('url') + dataType: "json" + data: + id: $(dropdown).data('id') + protected_branch: + "#{$(dropdown).data('type')}": selected.id + + success: -> + row = $(e.target) + row.closest('tr').effect('highlight') + + error: -> + new Flash("Failed to update branch!", "alert") + diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index cc3aef5199e..c6e73dd1f39 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -664,11 +664,14 @@ pre.light-well { .protected-branches-list { a { color: $gl-gray; - font-weight: 600; &:hover { color: $gl-link-color; } + + &.is-active { + font-weight: 600; + } } } diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index fdbe0044d3c..126358bfe77 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -56,7 +56,7 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController end def protected_branch_params - params.require(:protected_branch).permit(:name, :developers_can_push, :developers_can_merge) + params.require(:protected_branch).permit(:name, :allowed_to_push, :allowed_to_merge) end def load_protected_branches diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index 4345dc4ede4..8861632c055 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -1,5 +1,5 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base belongs_to :protected_branch - enum access_level: [:masters, :developers] + enum access_level: [:masters, :developers, :no_one] end diff --git a/app/services/protected_branches/base_service.rb b/app/services/protected_branches/base_service.rb index d4be8698a5f..3a7c35327fe 100644 --- a/app/services/protected_branches/base_service.rb +++ b/app/services/protected_branches/base_service.rb @@ -1,17 +1,21 @@ module ProtectedBranches class BaseService < ::BaseService def set_access_levels! - if params[:developers_can_push] == '0' - @protected_branch.push_access_level.masters! - elsif params[:developers_can_push] == '1' - @protected_branch.push_access_level.developers! - end - - if params[:developers_can_merge] == '0' + case params[:allowed_to_merge] + when 'masters' @protected_branch.merge_access_level.masters! - elsif params[:developers_can_merge] == '1' + when 'developers' @protected_branch.merge_access_level.developers! end + + case params[:allowed_to_push] + when 'masters' + @protected_branch.push_access_level.masters! + when 'developers' + @protected_branch.push_access_level.developers! + when 'no_one' + @protected_branch.push_access_level.no_one! + end end end end diff --git a/app/services/protected_branches/create_service.rb b/app/services/protected_branches/create_service.rb index 743f7bd2ce1..ab462f3054e 100644 --- a/app/services/protected_branches/create_service.rb +++ b/app/services/protected_branches/create_service.rb @@ -1,19 +1,21 @@ -class ProtectedBranches::CreateService < BaseService - attr_reader :protected_branch +module ProtectedBranches + class CreateService < BaseService + attr_reader :protected_branch - def execute - ProtectedBranch.transaction do - @protected_branch = project.protected_branches.new(name: params[:name]) - @protected_branch.save! + def execute + ProtectedBranch.transaction do + @protected_branch = project.protected_branches.new(name: params[:name]) + @protected_branch.save! - @protected_branch.create_push_access_level! - @protected_branch.create_merge_access_level! + @protected_branch.create_push_access_level! + @protected_branch.create_merge_access_level! - set_access_levels! - end + set_access_levels! + end - true - rescue ActiveRecord::RecordInvalid - false + true + rescue ActiveRecord::RecordInvalid + false + end end end diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml index 720d67dff7c..5f9f2992dca 100644 --- a/app/views/projects/protected_branches/_branches_list.html.haml +++ b/app/views/projects/protected_branches/_branches_list.html.haml @@ -5,24 +5,25 @@ No branches are protected, protect a branch with the form above. - else - can_admin_project = can?(current_user, :admin_project, @project) - .table-responsive - %table.table.protected-branches-list - %colgroup - %col{ width: "20%" } - %col{ width: "30%" } - %col{ width: "25%" } - %col{ width: "25%" } + + %table.table.protected-branches-list + %colgroup + %col{ width: "20%" } + %col{ width: "30%" } + %col{ width: "25%" } + %col{ width: "25%" } + %thead + %tr + %th Branch + %th Last commit + %th Allowed to Merge + %th Allowed to Push - if can_admin_project - %col - %thead - %tr - %th Protected Branch - %th Commit - %th Developers Can Push - %th Developers Can Merge - - if can_admin_project - %th - %tbody - = render partial: @protected_branches, locals: { can_admin_project: can_admin_project } + %th + %tbody + = render partial: @protected_branches, locals: { can_admin_project: can_admin_project } = paginate @protected_branches, theme: 'gitlab' + +:javascript + new ProtectedBranchesAccessSelect(); diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml index 7fda7f96047..ceae182e82c 100644 --- a/app/views/projects/protected_branches/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_protected_branch.html.haml @@ -15,9 +15,11 @@ - else (branch was removed from repository) %td - = check_box_tag("developers_can_push", protected_branch.id, protected_branch.developers_can_push, data: { url: url }) + = hidden_field_tag "allowed_to_merge_#{branch.id}", branch.merge_access_level.access_level + = dropdown_tag(branch.merge_access_level.access_level.humanize, options: { title: "Allowed To Merge", toggle_class: 'allowed-to-merge', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "allowed_to_merge_#{branch.id}", url: @url, id: branch.id, type: "allowed_to_merge" }}) %td - = check_box_tag("developers_can_merge", protected_branch.id, protected_branch.developers_can_merge, data: { url: url }) + = hidden_field_tag "allowed_to_push_#{branch.id}", branch.push_access_level.access_level + = dropdown_tag(branch.push_access_level.access_level.humanize, options: { title: "Allowed To Push", toggle_class: 'allowed-to-push', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "allowed_to_push_#{branch.id}", url: @url, id: branch.id, type: "allowed_to_push" }}) - if can_admin_project %td = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm pull-right" -- cgit v1.2.1 From 828f6eb6e50e6193fad9dbdd95d9dd56506e4064 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 8 Jul 2016 11:45:02 +0530 Subject: Enforce "No One Can Push" during git operations. 1. The crux of this change is in `UserAccess`, which looks through all the access levels, asking each if the user has access to push/merge for the current project. 2. Update the `protected_branches` factory to create access levels as necessary. 3. Fix and augment `user_access` and `git_access` specs. --- app/models/protected_branch/merge_access_level.rb | 9 +++ app/models/protected_branch/push_access_level.rb | 11 ++++ lib/gitlab/user_access.rb | 10 +-- spec/factories/protected_branches.rb | 17 +++++ spec/lib/gitlab/git_access_spec.rb | 78 +++++++++++++---------- spec/lib/gitlab/user_access_spec.rb | 4 +- 6 files changed, 89 insertions(+), 40 deletions(-) diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb index cfaa9c166fe..2d13d8c8381 100644 --- a/app/models/protected_branch/merge_access_level.rb +++ b/app/models/protected_branch/merge_access_level.rb @@ -1,5 +1,14 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base belongs_to :protected_branch + delegate :project, to: :protected_branch enum access_level: [:masters, :developers] + + def check_access(user) + if masters? + user.can?(:push_code, project) if project.team.master?(user) + elsif developers? + user.can?(:push_code, project) if (project.team.master?(user) || project.team.developer?(user)) + end + end end diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index 8861632c055..5a4a33556ce 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -1,5 +1,16 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base belongs_to :protected_branch + delegate :project, to: :protected_branch enum access_level: [:masters, :developers, :no_one] + + def check_access(user) + if masters? + user.can?(:push_code, project) if project.team.master?(user) + elsif developers? + user.can?(:push_code, project) if (project.team.master?(user) || project.team.developer?(user)) + elsif no_one? + false + end + end end diff --git a/lib/gitlab/user_access.rb b/lib/gitlab/user_access.rb index c0f85e9b3a8..3a69027368f 100644 --- a/lib/gitlab/user_access.rb +++ b/lib/gitlab/user_access.rb @@ -29,8 +29,9 @@ module Gitlab def can_push_to_branch?(ref) return false unless user - if project.protected_branch?(ref) && !project.developers_can_push_to_protected_branch?(ref) - user.can?(:push_code_to_protected_branches, project) + if project.protected_branch?(ref) + access_levels = project.protected_branches.matching(ref).map(&:push_access_level) + access_levels.any? { |access_level| access_level.check_access(user) } else user.can?(:push_code, project) end @@ -39,8 +40,9 @@ module Gitlab def can_merge_to_branch?(ref) return false unless user - if project.protected_branch?(ref) && !project.developers_can_merge_to_protected_branch?(ref) - user.can?(:push_code_to_protected_branches, project) + if project.protected_branch?(ref) + access_levels = project.protected_branches.matching(ref).map(&:merge_access_level) + access_levels.any? { |access_level| access_level.check_access(user) } else user.can?(:push_code, project) end diff --git a/spec/factories/protected_branches.rb b/spec/factories/protected_branches.rb index 28ed8078157..24a9b78f0c2 100644 --- a/spec/factories/protected_branches.rb +++ b/spec/factories/protected_branches.rb @@ -2,5 +2,22 @@ FactoryGirl.define do factory :protected_branch do name project + + after(:create) do |protected_branch| + protected_branch.create_push_access_level!(access_level: :masters) + protected_branch.create_merge_access_level!(access_level: :masters) + end + + trait :developers_can_push do + after(:create) { |protected_branch| protected_branch.push_access_level.developers! } + end + + trait :developers_can_merge do + after(:create) { |protected_branch| protected_branch.merge_access_level.developers! } + end + + trait :no_one_can_push do + after(:create) { |protected_branch| protected_branch.push_access_level.no_one! } + end end end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index ae064a878b0..324bb500025 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -217,29 +217,32 @@ describe Gitlab::GitAccess, lib: true do run_permission_checks(permissions_matrix) end - context "when 'developers can push' is turned on for the #{protected_branch_type} protected branch" do - before { create(:protected_branch, name: protected_branch_name, developers_can_push: true, project: project) } + context "when developers are allowed to push into the #{protected_branch_type} protected branch" do + before { create(:protected_branch, :developers_can_push, name: protected_branch_name, project: project) } run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true })) end - context "when 'developers can merge' is turned on for the #{protected_branch_type} protected branch" do - before { create(:protected_branch, name: protected_branch_name, developers_can_merge: true, project: project) } + context "developers are allowed to merge into the #{protected_branch_type} protected branch" do + before { create(:protected_branch, :developers_can_merge, name: protected_branch_name, project: project) } context "when a merge request exists for the given source/target branch" do context "when the merge request is in progress" do before do - create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature', state: 'locked', in_progress_merge_commit_sha: merge_into_protected_branch) + create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature', + state: 'locked', in_progress_merge_commit_sha: merge_into_protected_branch) end - run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: true })) - end + context "when the merge request is not in progress" do + before do + create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature', in_progress_merge_commit_sha: nil) + end - context "when the merge request is not in progress" do - before do - create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature', in_progress_merge_commit_sha: nil) + run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false })) end + end + context "when a merge request does not exist for the given source/target branch" do run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false })) end end @@ -249,44 +252,51 @@ describe Gitlab::GitAccess, lib: true do end end - context "when 'developers can merge' and 'developers can push' are turned on for the #{protected_branch_type} protected branch" do - before { create(:protected_branch, name: protected_branch_name, developers_can_merge: true, developers_can_push: true, project: project) } + context "when developers are allowed to push and merge into the #{protected_branch_type} protected branch" do + before { create(:protected_branch, :developers_can_merge, :developers_can_push, name: protected_branch_name, project: project) } run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true })) end end - describe 'deploy key permissions' do - let(:key) { create(:deploy_key) } - let(:actor) { key } + context "when no one is allowed to push to the #{protected_branch_name} protected branch" do + before { create(:protected_branch, :no_one_can_push, name: protected_branch_name, project: project) } - context 'push code' do - subject { access.check('git-receive-pack') } + run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false }, + master: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false })) + end + end - context 'when project is authorized' do - before { key.projects << project } + describe 'deploy key permissions' do + let(:key) { create(:deploy_key) } + let(:actor) { key } - it { expect(subject).not_to be_allowed } - end + context 'push code' do + subject { access.check('git-receive-pack') } - context 'when unauthorized' do - context 'to public project' do - let(:project) { create(:project, :public) } + context 'when project is authorized' do + before { key.projects << project } - it { expect(subject).not_to be_allowed } - end + it { expect(subject).not_to be_allowed } + end - context 'to internal project' do - let(:project) { create(:project, :internal) } + context 'when unauthorized' do + context 'to public project' do + let(:project) { create(:project, :public) } - it { expect(subject).not_to be_allowed } - end + it { expect(subject).not_to be_allowed } + end - context 'to private project' do - let(:project) { create(:project, :internal) } + context 'to internal project' do + let(:project) { create(:project, :internal) } - it { expect(subject).not_to be_allowed } - end + it { expect(subject).not_to be_allowed } + end + + context 'to private project' do + let(:project) { create(:project, :internal) } + + it { expect(subject).not_to be_allowed } end end end diff --git a/spec/lib/gitlab/user_access_spec.rb b/spec/lib/gitlab/user_access_spec.rb index aa9ec243498..5bb095366fa 100644 --- a/spec/lib/gitlab/user_access_spec.rb +++ b/spec/lib/gitlab/user_access_spec.rb @@ -44,7 +44,7 @@ describe Gitlab::UserAccess, lib: true do describe 'push to protected branch if allowed for developers' do before do - @branch = create :protected_branch, project: project, developers_can_push: true + @branch = create :protected_branch, :developers_can_push, project: project end it 'returns true if user is a master' do @@ -65,7 +65,7 @@ describe Gitlab::UserAccess, lib: true do describe 'merge to protected branch if allowed for developers' do before do - @branch = create :protected_branch, project: project, developers_can_merge: true + @branch = create :protected_branch, :developers_can_merge, project: project end it 'returns true if user is a master' do -- cgit v1.2.1 From 12387b4d2c6abbe1de2fc6b0776207d9135c29f0 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 8 Jul 2016 13:00:38 +0530 Subject: Allow setting "Allowed To Push/Merge" while creating a protected branch. 1. Reuse the same dropdown component that we used for updating these settings (`ProtectedBranchesAccessSelect`). Have it accept options for the parent container (so we can control the elements it sees) and whether or not to save changes via AJAX (we need this for update, but not create). 2. Change the "Developers" option to "Developers + Masters", which is clearer. 3. Remove `developers_can_push` and `developers_can_merge` from the model, since they're not needed anymore. --- .../protected_branches_access_select.js.coffee | 37 +++++++++++----------- app/assets/stylesheets/pages/projects.scss | 11 +++++++ app/models/protected_branch.rb | 8 ++--- .../protected_branches/_branches_list.html.haml | 2 +- .../protected_branches/_protected_branch.html.haml | 8 ++--- .../projects/protected_branches/index.html.haml | 29 +++++++++++------ 6 files changed, 58 insertions(+), 37 deletions(-) diff --git a/app/assets/javascripts/protected_branches_access_select.js.coffee b/app/assets/javascripts/protected_branches_access_select.js.coffee index b472ff7ec27..7c6f2f9f38e 100644 --- a/app/assets/javascripts/protected_branches_access_select.js.coffee +++ b/app/assets/javascripts/protected_branches_access_select.js.coffee @@ -1,18 +1,18 @@ class @ProtectedBranchesAccessSelect - constructor: () -> - $(".allowed-to-merge").each (i, element) => + constructor: (@container, @saveOnSelect) -> + @container.find(".allowed-to-merge").each (i, element) => fieldName = $(element).data('field-name') $(element).glDropdown - data: [{id: 'developers', text: 'Developers'}, {id: 'masters', text: 'Masters'}] + data: [{id: 'developers', text: 'Developers + Masters'}, {id: 'masters', text: 'Masters'}] selectable: true fieldName: fieldName clicked: _.partial(@onSelect, element) - $(".allowed-to-push").each (i, element) => + @container.find(".allowed-to-push").each (i, element) => fieldName = $(element).data('field-name') $(element).glDropdown data: [{id: 'no_one', text: 'No one'}, - {id: 'developers', text: 'Developers'}, + {id: 'developers', text: 'Developers + Masters'}, {id: 'masters', text: 'Masters'}] selectable: true fieldName: fieldName @@ -21,19 +21,20 @@ class @ProtectedBranchesAccessSelect onSelect: (dropdown, selected, element, e) => $(dropdown).find('.dropdown-toggle-text').text(selected.text) - $.ajax - type: "PATCH" - url: $(dropdown).data('url') - dataType: "json" - data: - id: $(dropdown).data('id') - protected_branch: - "#{$(dropdown).data('type')}": selected.id + if @saveOnSelect + $.ajax + type: "PATCH" + url: $(dropdown).data('url') + dataType: "json" + data: + id: $(dropdown).data('id') + protected_branch: + "#{$(dropdown).data('type')}": selected.id - success: -> - row = $(e.target) - row.closest('tr').effect('highlight') + success: -> + row = $(e.target) + row.closest('tr').effect('highlight') - error: -> - new Flash("Failed to update branch!", "alert") + error: -> + new Flash("Failed to update branch!", "alert") diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index c6e73dd1f39..4409477916f 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -661,6 +661,17 @@ pre.light-well { } } +.new_protected_branch { + .dropdown { + display: inline; + margin-left: 15px; + } + + label { + min-width: 120px; + } +} + .protected-branches-list { a { color: $gl-gray; diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index b0fde6c6c1b..c0bee72b4d7 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -12,12 +12,12 @@ class ProtectedBranch < ActiveRecord::Base project.commit(self.name) end - def developers_can_push - self.push_access_level && self.push_access_level.developers? + def allowed_to_push + self.push_access_level && self.push_access_level.access_level end - def developers_can_merge - self.merge_access_level && self.merge_access_level.developers? + def allowed_to_merge + self.merge_access_level && self.merge_access_level.access_level end # Returns all protected branches that match the given branch name. diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml index 5f9f2992dca..9240f1cf92d 100644 --- a/app/views/projects/protected_branches/_branches_list.html.haml +++ b/app/views/projects/protected_branches/_branches_list.html.haml @@ -26,4 +26,4 @@ = paginate @protected_branches, theme: 'gitlab' :javascript - new ProtectedBranchesAccessSelect(); + new ProtectedBranchesAccessSelect($(".protected-branches-list"), true); diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml index ceae182e82c..96533b141af 100644 --- a/app/views/projects/protected_branches/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_protected_branch.html.haml @@ -15,11 +15,11 @@ - else (branch was removed from repository) %td - = hidden_field_tag "allowed_to_merge_#{branch.id}", branch.merge_access_level.access_level - = dropdown_tag(branch.merge_access_level.access_level.humanize, options: { title: "Allowed To Merge", toggle_class: 'allowed-to-merge', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "allowed_to_merge_#{branch.id}", url: @url, id: branch.id, type: "allowed_to_merge" }}) + = hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_level.access_level + = dropdown_tag(protected_branch.merge_access_level.access_level.humanize, options: { title: "Allowed To Merge", toggle_class: 'allowed-to-merge', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "allowed_to_merge_#{protected_branch.id}", url: url, id: protected_branch.id, type: "allowed_to_merge" }}) %td - = hidden_field_tag "allowed_to_push_#{branch.id}", branch.push_access_level.access_level - = dropdown_tag(branch.push_access_level.access_level.humanize, options: { title: "Allowed To Push", toggle_class: 'allowed-to-push', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "allowed_to_push_#{branch.id}", url: @url, id: branch.id, type: "allowed_to_push" }}) + = hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_level.access_level + = dropdown_tag(protected_branch.push_access_level.access_level.humanize, options: { title: "Allowed To Push", toggle_class: 'allowed-to-push', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "allowed_to_push_#{protected_branch.id}", url: url, id: protected_branch.id, type: "allowed_to_push" }}) - if can_admin_project %td = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm pull-right" diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 950df740bbc..cd87f978fb2 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -32,19 +32,28 @@ are supported. .form-group - = f.check_box :developers_can_push, class: "pull-left" - .prepend-left-20 - = f.label :developers_can_push, "Developers can push", class: "label-light append-bottom-0" - %p.light.append-bottom-0 - Allow developers to push to this branch + .prepend-left-10 + = f.hidden_field :allowed_to_merge + = f.label :allowed_to_merge, "Allowed to Merge: ", class: "label-light append-bottom-0" + = dropdown_tag("", + options: { title: "Allowed To Merge", toggle_class: 'allowed-to-merge', + dropdown_class: 'dropdown-menu-selectable', + data: { field_name: "protected_branch[allowed_to_merge]" }}) .form-group - = f.check_box :developers_can_merge, class: "pull-left" - .prepend-left-20 - = f.label :developers_can_merge, "Developers can merge", class: "label-light append-bottom-0" - %p.light.append-bottom-0 - Allow developers to accept merge requests to this branch + .prepend-left-10 + = f.hidden_field :allowed_to_push + = f.label :allowed_to_push, "Allowed to Push: ", class: "label-light append-bottom-0" + = dropdown_tag("", + options: { title: "Allowed To Push", toggle_class: 'allowed-to-push', + dropdown_class: 'dropdown-menu-selectable', + data: { field_name: "protected_branch[allowed_to_push]" }}) + + = f.submit "Protect", class: "btn-create btn protect-branch-btn", disabled: true %hr = render "branches_list" + +:javascript + new ProtectedBranchesAccessSelect($(".new_protected_branch"), false); -- cgit v1.2.1 From 9fa661472e5e1e2edc91032a6093a3516974e27e Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 8 Jul 2016 14:18:50 +0530 Subject: Update protected branches spec to work with the `select`s. 1. Get the existing spec passing. 2. Add specs for all the access control options, both while creating and updating protected branches. 3. Show a flash notice when updating protected branches, primarily so the spec knows when the update is done. --- .../protected_branches_access_select.js.coffee | 1 + .../protected_branches/_protected_branch.html.haml | 8 ++- spec/features/protected_branches_spec.rb | 75 ++++++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/protected_branches_access_select.js.coffee b/app/assets/javascripts/protected_branches_access_select.js.coffee index 7c6f2f9f38e..6df11146ba9 100644 --- a/app/assets/javascripts/protected_branches_access_select.js.coffee +++ b/app/assets/javascripts/protected_branches_access_select.js.coffee @@ -34,6 +34,7 @@ class @ProtectedBranchesAccessSelect success: -> row = $(e.target) row.closest('tr').effect('highlight') + new Flash("Updated protected branch!", "notice") error: -> new Flash("Failed to update branch!", "alert") diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml index 96533b141af..89d606d9e20 100644 --- a/app/views/projects/protected_branches/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_protected_branch.html.haml @@ -16,10 +16,14 @@ (branch was removed from repository) %td = hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_level.access_level - = dropdown_tag(protected_branch.merge_access_level.access_level.humanize, options: { title: "Allowed To Merge", toggle_class: 'allowed-to-merge', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "allowed_to_merge_#{protected_branch.id}", url: url, id: protected_branch.id, type: "allowed_to_merge" }}) + = dropdown_tag(protected_branch.merge_access_level.access_level.humanize, + options: { title: "Allowed To Merge", toggle_class: 'allowed-to-merge', dropdown_class: 'dropdown-menu-selectable merge', + data: { field_name: "allowed_to_merge_#{protected_branch.id}", url: url, id: protected_branch.id, type: "allowed_to_merge" }}) %td = hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_level.access_level - = dropdown_tag(protected_branch.push_access_level.access_level.humanize, options: { title: "Allowed To Push", toggle_class: 'allowed-to-push', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "allowed_to_push_#{protected_branch.id}", url: url, id: protected_branch.id, type: "allowed_to_push" }}) + = dropdown_tag(protected_branch.push_access_level.access_level.humanize, + options: { title: "Allowed To Push", toggle_class: 'allowed-to-push', dropdown_class: 'dropdown-menu-selectable push', + data: { field_name: "allowed_to_push_#{protected_branch.id}", url: url, id: protected_branch.id, type: "allowed_to_push" }}) - if can_admin_project %td = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm pull-right" diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index d94dee0c797..087e3677169 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -81,4 +81,79 @@ feature 'Projected Branches', feature: true, js: true do end end end + + describe "access control" do + [ + ['developers', 'Developers + Masters'], + ['masters', 'Masters'], + ['no_one', 'No one'] + ].each do |access_type_id, access_type_name| + it "allows creating protected branches that #{access_type_name} can push to" do + visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('master') + within('.new_protected_branch') do + find(".allowed-to-push").click + click_on access_type_name + end + click_on "Protect" + + expect(ProtectedBranch.count).to eq(1) + expect(ProtectedBranch.last.allowed_to_push).to eq(access_type_id) + end + + # This spec fails on PhantomJS versions below 2.0, which don't support `PATCH` requests. + # https://github.com/ariya/phantomjs/issues/11384 + it "allows updating protected branches so that #{access_type_name} can push to them" do + visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('master') + click_on "Protect" + + expect(ProtectedBranch.count).to eq(1) + + within(".protected-branches-list") do + find(".allowed-to-push").click + within('.dropdown-menu.push') { click_on access_type_name } + end + + expect(page).to have_content "Updated protected branch" + expect(ProtectedBranch.last.allowed_to_push).to eq(access_type_id) + end + end + + [ + ['developers', 'Developers + Masters'], + ['masters', 'Masters'] + ].each do |access_type_id, access_type_name| + it "allows creating protected branches that #{access_type_name} can merge to" do + visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('master') + within('.new_protected_branch') do + find(".allowed-to-merge").click + click_on access_type_name + end + click_on "Protect" + + expect(ProtectedBranch.count).to eq(1) + expect(ProtectedBranch.last.allowed_to_merge).to eq(access_type_id) + end + + # This spec fails on PhantomJS versions below 2.0, which don't support `PATCH` requests. + # https://github.com/ariya/phantomjs/issues/11384 + it "allows updating protected branches so that #{access_type_name} can merge to them" do + visit namespace_project_protected_branches_path(project.namespace, project) + set_protected_branch_name('master') + click_on "Protect" + + expect(ProtectedBranch.count).to eq(1) + + within(".protected-branches-list") do + find(".allowed-to-merge").click + within('.dropdown-menu.merge') { click_on access_type_name } + end + + expect(page).to have_content "Updated protected branch" + expect(ProtectedBranch.last.allowed_to_merge).to eq(access_type_id) + end + end + end end -- cgit v1.2.1 From a9958ddc7c9d4c455d4c5459b7b83da1fab9ccb4 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 8 Jul 2016 14:45:29 +0530 Subject: Fix default branch protection. 1. So it works with the new data model for protected branch access levels. --- app/services/git_push_service.rb | 8 +++++--- app/services/protected_branches/create_service.rb | 2 +- app/services/protected_branches/update_service.rb | 2 +- spec/services/git_push_service_spec.rb | 17 ++++++++++++----- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index e02b50ff9a2..604737e6934 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -88,9 +88,11 @@ class GitPushService < BaseService # Set protection on the default branch if configured if current_application_settings.default_branch_protection != PROTECTION_NONE - developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false - developers_can_merge = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_MERGE ? true : false - @project.protected_branches.create({ name: @project.default_branch, developers_can_push: developers_can_push, developers_can_merge: developers_can_merge }) + allowed_to_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? 'developers' : 'masters' + allowed_to_merge = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_MERGE ? 'developers' : 'masters' + + params = { name: @project.default_branch, allowed_to_push: allowed_to_push, allowed_to_merge: allowed_to_merge } + ProtectedBranches::CreateService.new(@project, current_user, params).execute end end diff --git a/app/services/protected_branches/create_service.rb b/app/services/protected_branches/create_service.rb index ab462f3054e..212c2134638 100644 --- a/app/services/protected_branches/create_service.rb +++ b/app/services/protected_branches/create_service.rb @@ -1,5 +1,5 @@ module ProtectedBranches - class CreateService < BaseService + class CreateService < ProtectedBranches::BaseService attr_reader :protected_branch def execute diff --git a/app/services/protected_branches/update_service.rb b/app/services/protected_branches/update_service.rb index ed59b06b79a..4a2b1be9c93 100644 --- a/app/services/protected_branches/update_service.rb +++ b/app/services/protected_branches/update_service.rb @@ -1,5 +1,5 @@ module ProtectedBranches - class UpdateService < BaseService + class UpdateService < ProtectedBranches::BaseService attr_reader :protected_branch def initialize(project, current_user, id, params = {}) diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 47c0580e0f0..663c270d61f 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -224,8 +224,10 @@ describe GitPushService, services: true do it "when pushing a branch for the first time" do expect(project).to receive(:execute_hooks) expect(project.default_branch).to eq("master") - expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false, developers_can_merge: false }) execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) + expect(project.protected_branches).not_to be_empty + expect(project.protected_branches.first.allowed_to_push).to eq('masters') + expect(project.protected_branches.first.allowed_to_merge).to eq('masters') end it "when pushing a branch for the first time with default branch protection disabled" do @@ -233,8 +235,8 @@ describe GitPushService, services: true do expect(project).to receive(:execute_hooks) expect(project.default_branch).to eq("master") - expect(project.protected_branches).not_to receive(:create) execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) + expect(project.protected_branches).to be_empty end it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do @@ -242,9 +244,12 @@ describe GitPushService, services: true do expect(project).to receive(:execute_hooks) expect(project.default_branch).to eq("master") - expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: true, developers_can_merge: false }) - execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master') + execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) + + expect(project.protected_branches).not_to be_empty + expect(project.protected_branches.last.allowed_to_push).to eq('developers') + expect(project.protected_branches.last.allowed_to_merge).to eq('masters') end it "when pushing a branch for the first time with default branch protection set to 'developers can merge'" do @@ -252,8 +257,10 @@ describe GitPushService, services: true do expect(project).to receive(:execute_hooks) expect(project.default_branch).to eq("master") - expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false, developers_can_merge: true }) execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) + expect(project.protected_branches).not_to be_empty + expect(project.protected_branches.first.allowed_to_push).to eq('masters') + expect(project.protected_branches.first.allowed_to_merge).to eq('developers') end it "when pushing new commits to existing branch" do -- cgit v1.2.1 From c647540c1010fd1e51bced1db90947aa00c83fa8 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 8 Jul 2016 14:46:13 +0530 Subject: Fix all specs related to changes in !5081. 1. Remove `Project#developers_can_push_to_protected_branch?` since it isn't used anymore. 2. Remove `Project#developers_can_merge_to_protected_branch?` since it isn't used anymore. --- app/models/project.rb | 8 ----- app/models/protected_branch/merge_access_level.rb | 2 +- app/models/protected_branch/push_access_level.rb | 2 +- features/steps/project/commits/branches.rb | 2 +- spec/lib/gitlab/git_access_spec.rb | 2 +- spec/models/project_spec.rb | 40 ----------------------- 6 files changed, 4 insertions(+), 52 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index dc44a757b4b..7aecd7860c5 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -874,14 +874,6 @@ class Project < ActiveRecord::Base ProtectedBranch.matching(branch_name, protected_branches: @protected_branches).present? end - def developers_can_push_to_protected_branch?(branch_name) - protected_branches.matching(branch_name).any?(&:developers_can_push) - end - - def developers_can_merge_to_protected_branch?(branch_name) - protected_branches.matching(branch_name).any?(&:developers_can_merge) - end - def forked? !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?) end diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb index 2d13d8c8381..3d2a6971702 100644 --- a/app/models/protected_branch/merge_access_level.rb +++ b/app/models/protected_branch/merge_access_level.rb @@ -8,7 +8,7 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base if masters? user.can?(:push_code, project) if project.team.master?(user) elsif developers? - user.can?(:push_code, project) if (project.team.master?(user) || project.team.developer?(user)) + user.can?(:push_code, project) if project.team.master?(user) || project.team.developer?(user) end end end diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index 5a4a33556ce..d446c1a03f0 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -8,7 +8,7 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base if masters? user.can?(:push_code, project) if project.team.master?(user) elsif developers? - user.can?(:push_code, project) if (project.team.master?(user) || project.team.developer?(user)) + user.can?(:push_code, project) if project.team.master?(user) || project.team.developer?(user) elsif no_one? false end diff --git a/features/steps/project/commits/branches.rb b/features/steps/project/commits/branches.rb index 0a42931147d..4bfb7e92e99 100644 --- a/features/steps/project/commits/branches.rb +++ b/features/steps/project/commits/branches.rb @@ -25,7 +25,7 @@ class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps step 'project "Shop" has protected branches' do project = Project.find_by(name: "Shop") - project.protected_branches.create(name: "stable") + create(:protected_branch, project: project, name: "stable") end step 'I click new branch link' do diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 324bb500025..8d7497f76f5 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -230,7 +230,7 @@ describe Gitlab::GitAccess, lib: true do context "when the merge request is in progress" do before do create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature', - state: 'locked', in_progress_merge_commit_sha: merge_into_protected_branch) + state: 'locked', in_progress_merge_commit_sha: merge_into_protected_branch) end context "when the merge request is not in progress" do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 72b8a4e25bd..e365e4e98b2 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -1095,46 +1095,6 @@ describe Project, models: true do end end - describe "#developers_can_push_to_protected_branch?" do - let(:project) { create(:empty_project) } - - context "when the branch matches a protected branch via direct match" do - it "returns true if 'Developers can Push' is turned on" do - create(:protected_branch, name: "production", project: project, developers_can_push: true) - - expect(project.developers_can_push_to_protected_branch?('production')).to be true - end - - it "returns false if 'Developers can Push' is turned off" do - create(:protected_branch, name: "production", project: project, developers_can_push: false) - - expect(project.developers_can_push_to_protected_branch?('production')).to be false - end - end - - context "when the branch matches a protected branch via wilcard match" do - it "returns true if 'Developers can Push' is turned on" do - create(:protected_branch, name: "production/*", project: project, developers_can_push: true) - - expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be true - end - - it "returns false if 'Developers can Push' is turned off" do - create(:protected_branch, name: "production/*", project: project, developers_can_push: false) - - expect(project.developers_can_push_to_protected_branch?('production/some-branch')).to be false - end - end - - context "when the branch does not match a protected branch" do - it "returns false" do - create(:protected_branch, name: "production/*", project: project, developers_can_push: true) - - expect(project.developers_can_push_to_protected_branch?('staging/some-branch')).to be false - end - end - end - describe '#container_registry_path_with_namespace' do let(:project) { create(:empty_project, path: 'PROJECT') } -- cgit v1.2.1 From f2df2966aabc601dd1d6a6f9e75ead84db8a2765 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Thu, 14 Jul 2016 09:12:16 +0530 Subject: Humanize protected branches' access levels at one location. 1. The model now contains this humanization data, which is the once source of truth. 2. Previously, this was being listed out in the dropdown component as well. --- .../javascripts/protected_branches_access_select.js.coffee | 6 ++---- app/controllers/projects/protected_branches_controller.rb | 4 +++- app/models/protected_branch/merge_access_level.rb | 11 +++++++++++ app/models/protected_branch/push_access_level.rb | 12 ++++++++++++ .../projects/protected_branches/_protected_branch.html.haml | 4 ++-- spec/features/protected_branches_spec.rb | 11 ++--------- 6 files changed, 32 insertions(+), 16 deletions(-) diff --git a/app/assets/javascripts/protected_branches_access_select.js.coffee b/app/assets/javascripts/protected_branches_access_select.js.coffee index 6df11146ba9..2c29513ae61 100644 --- a/app/assets/javascripts/protected_branches_access_select.js.coffee +++ b/app/assets/javascripts/protected_branches_access_select.js.coffee @@ -3,7 +3,7 @@ class @ProtectedBranchesAccessSelect @container.find(".allowed-to-merge").each (i, element) => fieldName = $(element).data('field-name') $(element).glDropdown - data: [{id: 'developers', text: 'Developers + Masters'}, {id: 'masters', text: 'Masters'}] + data: gon.merge_access_levels selectable: true fieldName: fieldName clicked: _.partial(@onSelect, element) @@ -11,9 +11,7 @@ class @ProtectedBranchesAccessSelect @container.find(".allowed-to-push").each (i, element) => fieldName = $(element).data('field-name') $(element).glDropdown - data: [{id: 'no_one', text: 'No one'}, - {id: 'developers', text: 'Developers + Masters'}, - {id: 'masters', text: 'Masters'}] + data: gon.push_access_levels selectable: true fieldName: fieldName clicked: _.partial(@onSelect, element) diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index 126358bfe77..ddf1824ccb9 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -9,7 +9,9 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController def index @protected_branch = @project.protected_branches.new - gon.push({ open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } } }) + gon.push({ open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } }, + push_access_levels: ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text } }, + merge_access_levels: ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text } } }) end def create diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb index 3d2a6971702..d536f816317 100644 --- a/app/models/protected_branch/merge_access_level.rb +++ b/app/models/protected_branch/merge_access_level.rb @@ -4,6 +4,13 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base enum access_level: [:masters, :developers] + def self.human_access_levels + { + "masters" => "Masters", + "developers" => "Developers + Masters" + }.with_indifferent_access + end + def check_access(user) if masters? user.can?(:push_code, project) if project.team.master?(user) @@ -11,4 +18,8 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base user.can?(:push_code, project) if project.team.master?(user) || project.team.developer?(user) end end + + def humanize + self.class.human_access_levels[self.access_level] + end end diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index d446c1a03f0..bb46b39b714 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -4,6 +4,14 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base enum access_level: [:masters, :developers, :no_one] + def self.human_access_levels + { + "masters" => "Masters", + "developers" => "Developers + Masters", + "no_one" => "No one" + }.with_indifferent_access + end + def check_access(user) if masters? user.can?(:push_code, project) if project.team.master?(user) @@ -13,4 +21,8 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base false end end + + def humanize + self.class.human_access_levels[self.access_level] + end end diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml index 89d606d9e20..e27dea8145d 100644 --- a/app/views/projects/protected_branches/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_protected_branch.html.haml @@ -16,12 +16,12 @@ (branch was removed from repository) %td = hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_level.access_level - = dropdown_tag(protected_branch.merge_access_level.access_level.humanize, + = dropdown_tag(protected_branch.merge_access_level.humanize, options: { title: "Allowed To Merge", toggle_class: 'allowed-to-merge', dropdown_class: 'dropdown-menu-selectable merge', data: { field_name: "allowed_to_merge_#{protected_branch.id}", url: url, id: protected_branch.id, type: "allowed_to_merge" }}) %td = hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_level.access_level - = dropdown_tag(protected_branch.push_access_level.access_level.humanize, + = dropdown_tag(protected_branch.push_access_level.humanize, options: { title: "Allowed To Push", toggle_class: 'allowed-to-push', dropdown_class: 'dropdown-menu-selectable push', data: { field_name: "allowed_to_push_#{protected_branch.id}", url: url, id: protected_branch.id, type: "allowed_to_push" }}) - if can_admin_project diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index 087e3677169..553d1c70461 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -83,11 +83,7 @@ feature 'Projected Branches', feature: true, js: true do end describe "access control" do - [ - ['developers', 'Developers + Masters'], - ['masters', 'Masters'], - ['no_one', 'No one'] - ].each do |access_type_id, access_type_name| + ProtectedBranch::PushAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| it "allows creating protected branches that #{access_type_name} can push to" do visit namespace_project_protected_branches_path(project.namespace, project) set_protected_branch_name('master') @@ -120,10 +116,7 @@ feature 'Projected Branches', feature: true, js: true do end end - [ - ['developers', 'Developers + Masters'], - ['masters', 'Masters'] - ].each do |access_type_id, access_type_name| + ProtectedBranch::MergeAccessLevel.human_access_levels.each do |(access_type_id, access_type_name)| it "allows creating protected branches that #{access_type_name} can merge to" do visit namespace_project_protected_branches_path(project.namespace, project) set_protected_branch_name('master') -- cgit v1.2.1 From 4d6dadc8f8af986a0792fb388775a174e76b0b7d Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Thu, 14 Jul 2016 09:24:59 +0530 Subject: Make specs compatible with PhantomJS versions < 2. 1. These versions of PhantomJS don't support `PATCH` requests, so we use a `POST` with `_method` set to `PATCH`. --- app/assets/javascripts/protected_branches_access_select.js.coffee | 3 ++- spec/features/protected_branches_spec.rb | 4 ---- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/app/assets/javascripts/protected_branches_access_select.js.coffee b/app/assets/javascripts/protected_branches_access_select.js.coffee index 2c29513ae61..a4d9b6eb616 100644 --- a/app/assets/javascripts/protected_branches_access_select.js.coffee +++ b/app/assets/javascripts/protected_branches_access_select.js.coffee @@ -21,10 +21,11 @@ class @ProtectedBranchesAccessSelect $(dropdown).find('.dropdown-toggle-text').text(selected.text) if @saveOnSelect $.ajax - type: "PATCH" + type: "POST" url: $(dropdown).data('url') dataType: "json" data: + _method: 'PATCH' id: $(dropdown).data('id') protected_branch: "#{$(dropdown).data('type')}": selected.id diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index 553d1c70461..d72b62a4962 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -97,8 +97,6 @@ feature 'Projected Branches', feature: true, js: true do expect(ProtectedBranch.last.allowed_to_push).to eq(access_type_id) end - # This spec fails on PhantomJS versions below 2.0, which don't support `PATCH` requests. - # https://github.com/ariya/phantomjs/issues/11384 it "allows updating protected branches so that #{access_type_name} can push to them" do visit namespace_project_protected_branches_path(project.namespace, project) set_protected_branch_name('master') @@ -130,8 +128,6 @@ feature 'Projected Branches', feature: true, js: true do expect(ProtectedBranch.last.allowed_to_merge).to eq(access_type_id) end - # This spec fails on PhantomJS versions below 2.0, which don't support `PATCH` requests. - # https://github.com/ariya/phantomjs/issues/11384 it "allows updating protected branches so that #{access_type_name} can merge to them" do visit namespace_project_protected_branches_path(project.namespace, project) set_protected_branch_name('master') -- cgit v1.2.1 From cc1cebdcc536244d97bdf6c767c2f1875c71cdf5 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Thu, 14 Jul 2016 11:46:13 +0530 Subject: Admins count as masters too. 1. In the context of protected branches. 2. Test this behaviour. --- app/models/project_team.rb | 8 ++++++ app/models/protected_branch/merge_access_level.rb | 4 +-- app/models/protected_branch/push_access_level.rb | 4 +-- spec/lib/gitlab/git_access_spec.rb | 30 ++++++++++++++++++----- 4 files changed, 36 insertions(+), 10 deletions(-) diff --git a/app/models/project_team.rb b/app/models/project_team.rb index fdfaf052730..436d5bd2948 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -118,6 +118,14 @@ class ProjectTeam max_member_access(user.id) == Gitlab::Access::MASTER end + def master_or_greater?(user) + master?(user) || user.is_admin? + end + + def developer_or_greater?(user) + master_or_greater?(user) || developer?(user) + end + def member?(user, min_member_access = nil) member = !!find_member(user.id) diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb index d536f816317..632e47b028f 100644 --- a/app/models/protected_branch/merge_access_level.rb +++ b/app/models/protected_branch/merge_access_level.rb @@ -13,9 +13,9 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base def check_access(user) if masters? - user.can?(:push_code, project) if project.team.master?(user) + user.can?(:push_code, project) if project.team.master_or_greater?(user) elsif developers? - user.can?(:push_code, project) if project.team.master?(user) || project.team.developer?(user) + user.can?(:push_code, project) if project.team.developer_or_greater?(user) end end diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index bb46b39b714..35d4ad93231 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -14,9 +14,9 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base def check_access(user) if masters? - user.can?(:push_code, project) if project.team.master?(user) + user.can?(:push_code, project) if project.team.master_or_greater?(user) elsif developers? - user.can?(:push_code, project) if project.team.master?(user) || project.team.developer?(user) + user.can?(:push_code, project) if project.team.developer_or_greater?(user) elsif no_one? false end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index 8d7497f76f5..c6f03525aab 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -151,7 +151,13 @@ describe Gitlab::GitAccess, lib: true do def self.run_permission_checks(permissions_matrix) permissions_matrix.keys.each do |role| describe "#{role} access" do - before { project.team << [user, role] } + before do + if role == :admin + user.update_attribute(:admin, true) + else + project.team << [user, role] + end + end permissions_matrix[role].each do |action, allowed| context action do @@ -165,6 +171,17 @@ describe Gitlab::GitAccess, lib: true do end permissions_matrix = { + admin: { + push_new_branch: true, + push_master: true, + push_protected_branch: true, + push_remove_protected_branch: false, + push_tag: true, + push_new_tag: true, + push_all: true, + merge_into_protected_branch: true + }, + master: { push_new_branch: true, push_master: true, @@ -257,13 +274,14 @@ describe Gitlab::GitAccess, lib: true do run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: true, push_all: true, merge_into_protected_branch: true })) end - end - context "when no one is allowed to push to the #{protected_branch_name} protected branch" do - before { create(:protected_branch, :no_one_can_push, name: protected_branch_name, project: project) } + context "when no one is allowed to push to the #{protected_branch_name} protected branch" do + before { create(:protected_branch, :no_one_can_push, name: protected_branch_name, project: project) } - run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false }, - master: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false })) + run_permission_checks(permissions_matrix.deep_merge(developer: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false }, + master: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false }, + admin: { push_protected_branch: false, push_all: false, merge_into_protected_branch: false })) + end end end -- cgit v1.2.1 From 8e25ddc529e41d8244c9e7b4adcf54e071b8c318 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Thu, 14 Jul 2016 11:46:58 +0530 Subject: Add changelog entry. --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index d555d23860d..c791776a891 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 8.11.0 (unreleased) - Fix of 'Commits being passed to custom hooks are already reachable when using the UI' - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable - Optimize maximum user access level lookup in loading of notes + - Add "No one can push" as an option for protected branches. !5081 - Limit git rev-list output count to one in forced push check - Clean up unused routes (Josef Strzibny) - Add green outline to New Branch button. !5447 (winniehell) @@ -126,6 +127,8 @@ v 8.10.0 - Allow to define manual actions/builds on Pipelines and Environments - Fix pagination when sorting by columns with lots of ties (like priority) - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times. !5020 + - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times !5020 + - Add "No one can push" as an option for protected branches. !5081 - Updated project header design - Issuable collapsed assignee tooltip is now the users name - Fix compare view not changing code view rendering style -- cgit v1.2.1 From b3a29b3180c4edda33d82fc3564bd4991831e06c Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Tue, 19 Jul 2016 17:46:57 +0530 Subject: Favor labels like `Allowed to push` over `Allowed To Push`. - Based on feedback from @axil - http://docs.gitlab.com/ce/development/ui_guide.html#buttons --- app/views/projects/protected_branches/_branches_list.html.haml | 4 ++-- app/views/projects/protected_branches/_protected_branch.html.haml | 4 ++-- app/views/projects/protected_branches/index.html.haml | 8 ++++---- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml index 9240f1cf92d..a6956c8e69f 100644 --- a/app/views/projects/protected_branches/_branches_list.html.haml +++ b/app/views/projects/protected_branches/_branches_list.html.haml @@ -16,8 +16,8 @@ %tr %th Branch %th Last commit - %th Allowed to Merge - %th Allowed to Push + %th Allowed to merge + %th Allowed to push - if can_admin_project %th %tbody diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml index e27dea8145d..2fc6081e448 100644 --- a/app/views/projects/protected_branches/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_protected_branch.html.haml @@ -17,12 +17,12 @@ %td = hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_level.access_level = dropdown_tag(protected_branch.merge_access_level.humanize, - options: { title: "Allowed To Merge", toggle_class: 'allowed-to-merge', dropdown_class: 'dropdown-menu-selectable merge', + options: { title: "Allowed to merge", toggle_class: 'allowed-to-merge', dropdown_class: 'dropdown-menu-selectable merge', data: { field_name: "allowed_to_merge_#{protected_branch.id}", url: url, id: protected_branch.id, type: "allowed_to_merge" }}) %td = hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_level.access_level = dropdown_tag(protected_branch.push_access_level.humanize, - options: { title: "Allowed To Push", toggle_class: 'allowed-to-push', dropdown_class: 'dropdown-menu-selectable push', + options: { title: "Allowed to push", toggle_class: 'allowed-to-push', dropdown_class: 'dropdown-menu-selectable push', data: { field_name: "allowed_to_push_#{protected_branch.id}", url: url, id: protected_branch.id, type: "allowed_to_push" }}) - if can_admin_project %td diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index cd87f978fb2..69caed7d979 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -34,18 +34,18 @@ .form-group .prepend-left-10 = f.hidden_field :allowed_to_merge - = f.label :allowed_to_merge, "Allowed to Merge: ", class: "label-light append-bottom-0" + = f.label :allowed_to_merge, "Allowed to merge: ", class: "label-light append-bottom-0" = dropdown_tag("", - options: { title: "Allowed To Merge", toggle_class: 'allowed-to-merge', + options: { title: "Allowed to merge", toggle_class: 'allowed-to-merge', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "protected_branch[allowed_to_merge]" }}) .form-group .prepend-left-10 = f.hidden_field :allowed_to_push - = f.label :allowed_to_push, "Allowed to Push: ", class: "label-light append-bottom-0" + = f.label :allowed_to_push, "Allowed to push: ", class: "label-light append-bottom-0" = dropdown_tag("", - options: { title: "Allowed To Push", toggle_class: 'allowed-to-push', + options: { title: "Allowed to push", toggle_class: 'allowed-to-push', dropdown_class: 'dropdown-menu-selectable', data: { field_name: "protected_branch[allowed_to_push]" }}) -- cgit v1.2.1 From 7b2ad2d5b99d84fc2d2c11a654085afc02a05bb1 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Mon, 25 Jul 2016 14:42:52 +0530 Subject: Implement review comments from @dbalexandre. 1. Remove `master_or_greater?` and `developer_or_greater?` in favor of `max_member_access`, which is a lot nicer. 2. Remove a number of instances of `include Gitlab::Database::MigrationHelpers` in migrations that don't need this module. Also remove comments where not necessary. 3. Remove duplicate entry in CHANGELOG. 4. Move `ProtectedBranchAccessSelect` from Coffeescript to ES6. 5. Split the `set_access_levels!` method in two - one each for `merge` and `push` access levels. --- CHANGELOG | 1 - app/assets/javascripts/protected_branches.js | 35 -------------- .../protected_branches_access_select.js.coffee | 40 ---------------- .../protected_branches_access_select.js.es6 | 53 ++++++++++++++++++++++ app/models/project_team.rb | 8 ---- app/models/protected_branch/merge_access_level.rb | 14 ++++-- app/models/protected_branch/push_access_level.rb | 17 ++++--- app/services/protected_branches/base_service.rb | 9 ++++ ...705054938_add_protected_branches_push_access.rb | 2 - ...05054952_add_protected_branches_merge_access.rb | 2 - ...can_merge_to_protected_branches_merge_access.rb | 13 ------ ...s_can_push_to_protected_branches_push_access.rb | 13 ------ ..._developers_can_push_from_protected_branches.rb | 13 ------ ...developers_can_merge_from_protected_branches.rb | 13 ------ 14 files changed, 81 insertions(+), 152 deletions(-) delete mode 100644 app/assets/javascripts/protected_branches.js delete mode 100644 app/assets/javascripts/protected_branches_access_select.js.coffee create mode 100644 app/assets/javascripts/protected_branches_access_select.js.es6 diff --git a/CHANGELOG b/CHANGELOG index c791776a891..4f1da451df0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -127,7 +127,6 @@ v 8.10.0 - Allow to define manual actions/builds on Pipelines and Environments - Fix pagination when sorting by columns with lots of ties (like priority) - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times. !5020 - - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times !5020 - Add "No one can push" as an option for protected branches. !5081 - Updated project header design - Issuable collapsed assignee tooltip is now the users name diff --git a/app/assets/javascripts/protected_branches.js b/app/assets/javascripts/protected_branches.js deleted file mode 100644 index db21a19964d..00000000000 --- a/app/assets/javascripts/protected_branches.js +++ /dev/null @@ -1,35 +0,0 @@ -(function() { - $(function() { - return $(".protected-branches-list :checkbox").change(function(e) { - var can_push, id, name, obj, url; - name = $(this).attr("name"); - if (name === "developers_can_push" || name === "developers_can_merge") { - id = $(this).val(); - can_push = $(this).is(":checked"); - url = $(this).data("url"); - return $.ajax({ - type: "PATCH", - url: url, - dataType: "json", - data: { - id: id, - protected_branch: ( - obj = {}, - obj["" + name] = can_push, - obj - ) - }, - success: function() { - var row; - row = $(e.target); - return row.closest('tr').effect('highlight'); - }, - error: function() { - return new Flash("Failed to update branch!", "alert"); - } - }); - } - }); - }); - -}).call(this); diff --git a/app/assets/javascripts/protected_branches_access_select.js.coffee b/app/assets/javascripts/protected_branches_access_select.js.coffee deleted file mode 100644 index a4d9b6eb616..00000000000 --- a/app/assets/javascripts/protected_branches_access_select.js.coffee +++ /dev/null @@ -1,40 +0,0 @@ -class @ProtectedBranchesAccessSelect - constructor: (@container, @saveOnSelect) -> - @container.find(".allowed-to-merge").each (i, element) => - fieldName = $(element).data('field-name') - $(element).glDropdown - data: gon.merge_access_levels - selectable: true - fieldName: fieldName - clicked: _.partial(@onSelect, element) - - @container.find(".allowed-to-push").each (i, element) => - fieldName = $(element).data('field-name') - $(element).glDropdown - data: gon.push_access_levels - selectable: true - fieldName: fieldName - clicked: _.partial(@onSelect, element) - - - onSelect: (dropdown, selected, element, e) => - $(dropdown).find('.dropdown-toggle-text').text(selected.text) - if @saveOnSelect - $.ajax - type: "POST" - url: $(dropdown).data('url') - dataType: "json" - data: - _method: 'PATCH' - id: $(dropdown).data('id') - protected_branch: - "#{$(dropdown).data('type')}": selected.id - - success: -> - row = $(e.target) - row.closest('tr').effect('highlight') - new Flash("Updated protected branch!", "notice") - - error: -> - new Flash("Failed to update branch!", "alert") - diff --git a/app/assets/javascripts/protected_branches_access_select.js.es6 b/app/assets/javascripts/protected_branches_access_select.js.es6 new file mode 100644 index 00000000000..93b7d7755a7 --- /dev/null +++ b/app/assets/javascripts/protected_branches_access_select.js.es6 @@ -0,0 +1,53 @@ +class ProtectedBranchesAccessSelect { + constructor(container, saveOnSelect) { + this.container = container; + this.saveOnSelect = saveOnSelect; + + this.container.find(".allowed-to-merge").each((i, element) => { + var fieldName = $(element).data('field-name'); + return $(element).glDropdown({ + data: gon.merge_access_levels, + selectable: true, + fieldName: fieldName, + clicked: _.chain(this.onSelect).partial(element).bind(this).value() + }); + }); + + + this.container.find(".allowed-to-push").each((i, element) => { + var fieldName = $(element).data('field-name'); + return $(element).glDropdown({ + data: gon.push_access_levels, + selectable: true, + fieldName: fieldName, + clicked: _.chain(this.onSelect).partial(element).bind(this).value() + }); + }); + } + + onSelect(dropdown, selected, element, e) { + $(dropdown).find('.dropdown-toggle-text').text(selected.text); + if (this.saveOnSelect) { + return $.ajax({ + type: "POST", + url: $(dropdown).data('url'), + dataType: "json", + data: { + _method: 'PATCH', + id: $(dropdown).data('id'), + protected_branch: { + ["" + ($(dropdown).data('type'))]: selected.id + } + }, + success: function() { + var row; + row = $(e.target); + return row.closest('tr').effect('highlight'); + }, + error: function() { + return new Flash("Failed to update branch!", "alert"); + } + }); + } + } +} diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 436d5bd2948..fdfaf052730 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -118,14 +118,6 @@ class ProjectTeam max_member_access(user.id) == Gitlab::Access::MASTER end - def master_or_greater?(user) - master?(user) || user.is_admin? - end - - def developer_or_greater?(user) - master_or_greater?(user) || developer?(user) - end - def member?(user, min_member_access = nil) member = !!find_member(user.id) diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb index 632e47b028f..17a3a86c3e1 100644 --- a/app/models/protected_branch/merge_access_level.rb +++ b/app/models/protected_branch/merge_access_level.rb @@ -12,11 +12,15 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base end def check_access(user) - if masters? - user.can?(:push_code, project) if project.team.master_or_greater?(user) - elsif developers? - user.can?(:push_code, project) if project.team.developer_or_greater?(user) - end + return true if user.is_admin? + + min_member_access = if masters? + Gitlab::Access::MASTER + elsif developers? + Gitlab::Access::DEVELOPER + end + + project.team.max_member_access(user.id) >= min_member_access end def humanize diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index 35d4ad93231..22096b13300 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -13,13 +13,16 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base end def check_access(user) - if masters? - user.can?(:push_code, project) if project.team.master_or_greater?(user) - elsif developers? - user.can?(:push_code, project) if project.team.developer_or_greater?(user) - elsif no_one? - false - end + return false if no_one? + return true if user.is_admin? + + min_member_access = if masters? + Gitlab::Access::MASTER + elsif developers? + Gitlab::Access::DEVELOPER + end + + project.team.max_member_access(user.id) >= min_member_access end def humanize diff --git a/app/services/protected_branches/base_service.rb b/app/services/protected_branches/base_service.rb index 3a7c35327fe..f8741fcb3d5 100644 --- a/app/services/protected_branches/base_service.rb +++ b/app/services/protected_branches/base_service.rb @@ -1,13 +1,22 @@ module ProtectedBranches class BaseService < ::BaseService def set_access_levels! + set_merge_access_levels! + set_push_access_levels! + end + + protected + + def set_merge_access_levels! case params[:allowed_to_merge] when 'masters' @protected_branch.merge_access_level.masters! when 'developers' @protected_branch.merge_access_level.developers! end + end + def set_push_access_levels! case params[:allowed_to_push] when 'masters' @protected_branch.push_access_level.masters! diff --git a/db/migrate/20160705054938_add_protected_branches_push_access.rb b/db/migrate/20160705054938_add_protected_branches_push_access.rb index 512d99e4823..3031574fe2a 100644 --- a/db/migrate/20160705054938_add_protected_branches_push_access.rb +++ b/db/migrate/20160705054938_add_protected_branches_push_access.rb @@ -2,8 +2,6 @@ # for more information on how to write migrations for GitLab. class AddProtectedBranchesPushAccess < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - def change create_table :protected_branch_push_access_levels do |t| t.references :protected_branch, index: { name: "index_protected_branch_push_access" }, foreign_key: true, null: false diff --git a/db/migrate/20160705054952_add_protected_branches_merge_access.rb b/db/migrate/20160705054952_add_protected_branches_merge_access.rb index 9f82c0a8aa3..cf1cdb8b3b6 100644 --- a/db/migrate/20160705054952_add_protected_branches_merge_access.rb +++ b/db/migrate/20160705054952_add_protected_branches_merge_access.rb @@ -2,8 +2,6 @@ # for more information on how to write migrations for GitLab. class AddProtectedBranchesMergeAccess < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - def change create_table :protected_branch_merge_access_levels do |t| t.references :protected_branch, index: { name: "index_protected_branch_merge_access" }, foreign_key: true, null: false diff --git a/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb b/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb index 20ca9c3a488..c2b278ce673 100644 --- a/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb +++ b/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb @@ -2,19 +2,6 @@ # for more information on how to write migrations for GitLab. class MoveFromDevelopersCanMergeToProtectedBranchesMergeAccess < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - def up execute <<-HEREDOC INSERT into protected_branch_merge_access_levels (protected_branch_id, access_level, created_at, updated_at) diff --git a/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb b/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb index 498fb393d61..5bc70283f60 100644 --- a/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb +++ b/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb @@ -2,19 +2,6 @@ # for more information on how to write migrations for GitLab. class MoveFromDevelopersCanPushToProtectedBranchesPushAccess < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - def up execute <<-HEREDOC INSERT into protected_branch_push_access_levels (protected_branch_id, access_level, created_at, updated_at) diff --git a/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb b/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb index 1e9977cfa6e..ad6ad43686d 100644 --- a/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb +++ b/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb @@ -2,19 +2,6 @@ # for more information on how to write migrations for GitLab. class RemoveDevelopersCanPushFromProtectedBranches < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - def change remove_column :protected_branches, :developers_can_push, :boolean end diff --git a/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb b/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb index 43d02fbaed6..084914e423a 100644 --- a/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb +++ b/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb @@ -2,19 +2,6 @@ # for more information on how to write migrations for GitLab. class RemoveDevelopersCanMergeFromProtectedBranches < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - def change remove_column :protected_branches, :developers_can_merge, :boolean end -- cgit v1.2.1 From a72d4491903ad4f6730565df6391667e8ba8b71f Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Mon, 25 Jul 2016 14:59:05 +0530 Subject: Remove duplicate specs from `git_access_spec` - Likely introduced during an improper conflict resolution. --- spec/lib/gitlab/git_access_spec.rb | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb index c6f03525aab..8447305a316 100644 --- a/spec/lib/gitlab/git_access_spec.rb +++ b/spec/lib/gitlab/git_access_spec.rb @@ -250,23 +250,21 @@ describe Gitlab::GitAccess, lib: true do state: 'locked', in_progress_merge_commit_sha: merge_into_protected_branch) end - context "when the merge request is not in progress" do - before do - create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature', in_progress_merge_commit_sha: nil) - end + run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: true })) + end - run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false })) + context "when the merge request is not in progress" do + before do + create(:merge_request, source_project: project, source_branch: unprotected_branch, target_branch: 'feature', in_progress_merge_commit_sha: nil) end + + run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false })) end context "when a merge request does not exist for the given source/target branch" do run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false })) end end - - context "when a merge request does not exist for the given source/target branch" do - run_permission_checks(permissions_matrix.deep_merge(developer: { merge_into_protected_branch: false })) - end end context "when developers are allowed to push and merge into the #{protected_branch_type} protected branch" do -- cgit v1.2.1 From 88fd401d599f94bc4ea572cd47afa7d12c484db2 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Mon, 25 Jul 2016 15:39:15 +0530 Subject: Implement review comments from @axil. 1. Align "Allowed to Merge" and "Allowed to Push" dropdowns. 2. Don't display a flash every time a protected branch is updated. Previously, we were using this so the test has something to hook onto before the assertion. Now we're using `wait_for_ajax` instead. --- .../projects/protected_branches/index.html.haml | 26 ++++++++++------------ spec/features/protected_branches_spec.rb | 6 +++-- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 69caed7d979..75c2063027a 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -32,22 +32,20 @@ are supported. .form-group - .prepend-left-10 - = f.hidden_field :allowed_to_merge - = f.label :allowed_to_merge, "Allowed to merge: ", class: "label-light append-bottom-0" - = dropdown_tag("", - options: { title: "Allowed to merge", toggle_class: 'allowed-to-merge', - dropdown_class: 'dropdown-menu-selectable', - data: { field_name: "protected_branch[allowed_to_merge]" }}) + = f.hidden_field :allowed_to_merge + = f.label :allowed_to_merge, "Allowed to merge: ", class: "label-light append-bottom-0" + = dropdown_tag("", + options: { title: "Allowed to merge", toggle_class: 'allowed-to-merge', + dropdown_class: 'dropdown-menu-selectable', + data: { field_name: "protected_branch[allowed_to_merge]" }}) .form-group - .prepend-left-10 - = f.hidden_field :allowed_to_push - = f.label :allowed_to_push, "Allowed to push: ", class: "label-light append-bottom-0" - = dropdown_tag("", - options: { title: "Allowed to push", toggle_class: 'allowed-to-push', - dropdown_class: 'dropdown-menu-selectable', - data: { field_name: "protected_branch[allowed_to_push]" }}) + = f.hidden_field :allowed_to_push + = f.label :allowed_to_push, "Allowed to push: ", class: "label-light append-bottom-0" + = dropdown_tag("", + options: { title: "Allowed to push", toggle_class: 'allowed-to-push', + dropdown_class: 'dropdown-menu-selectable', + data: { field_name: "protected_branch[allowed_to_push]" }}) = f.submit "Protect", class: "btn-create btn protect-branch-btn", disabled: true diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index d72b62a4962..dac2bcf9efd 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' feature 'Projected Branches', feature: true, js: true do + include WaitForAjax + let(:user) { create(:user, :admin) } let(:project) { create(:project) } @@ -109,7 +111,7 @@ feature 'Projected Branches', feature: true, js: true do within('.dropdown-menu.push') { click_on access_type_name } end - expect(page).to have_content "Updated protected branch" + wait_for_ajax expect(ProtectedBranch.last.allowed_to_push).to eq(access_type_id) end end @@ -140,7 +142,7 @@ feature 'Projected Branches', feature: true, js: true do within('.dropdown-menu.merge') { click_on access_type_name } end - expect(page).to have_content "Updated protected branch" + wait_for_ajax expect(ProtectedBranch.last.allowed_to_merge).to eq(access_type_id) end end -- cgit v1.2.1 From 01d190a84ad9b8e4a40cbdec8a55946bac38ab76 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Mon, 25 Jul 2016 20:14:53 +0530 Subject: Have the `branches` API work with the new protected branches data model. 1. The new data model moves from `developers_can_{push,merge}` to `allowed_to_{push,merge}`. 2. The API interface has not been changed. It still accepts `developers_can_push` and `developers_can_merge` as options. These attributes are inferred from the new data model. 3. Modify the protected branch create/update services to translate from the API interface to our current data model. --- app/services/protected_branches/base_service.rb | 34 +++++++++++++++++++++++-- lib/api/branches.rb | 29 ++++++++++++--------- lib/api/entities.rb | 6 +++-- spec/requests/api/branches_spec.rb | 2 +- 4 files changed, 54 insertions(+), 17 deletions(-) diff --git a/app/services/protected_branches/base_service.rb b/app/services/protected_branches/base_service.rb index f8741fcb3d5..a5896587ded 100644 --- a/app/services/protected_branches/base_service.rb +++ b/app/services/protected_branches/base_service.rb @@ -1,6 +1,15 @@ module ProtectedBranches class BaseService < ::BaseService + include API::Helpers + + def initialize(project, current_user, params = {}) + super(project, current_user, params) + @allowed_to_push = params[:allowed_to_push] + @allowed_to_merge = params[:allowed_to_merge] + end + def set_access_levels! + translate_api_params! set_merge_access_levels! set_push_access_levels! end @@ -8,7 +17,7 @@ module ProtectedBranches protected def set_merge_access_levels! - case params[:allowed_to_merge] + case @allowed_to_merge when 'masters' @protected_branch.merge_access_level.masters! when 'developers' @@ -17,7 +26,7 @@ module ProtectedBranches end def set_push_access_levels! - case params[:allowed_to_push] + case @allowed_to_push when 'masters' @protected_branch.push_access_level.masters! when 'developers' @@ -26,5 +35,26 @@ module ProtectedBranches @protected_branch.push_access_level.no_one! end end + + # The `branches` API still uses `developers_can_push` and `developers_can_merge`, + # which need to be translated to `allowed_to_push` and `allowed_to_merge`, + # respectively. + def translate_api_params! + @allowed_to_push ||= + case to_boolean(params[:developers_can_push]) + when true + 'developers' + when false + 'masters' + end + + @allowed_to_merge ||= + case to_boolean(params[:developers_can_merge]) + when true + 'developers' + when false + 'masters' + end + end end end diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 66b853eb342..4133a1f7a6b 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -35,6 +35,10 @@ module API # Protect a single branch # + # Note: The internal data model moved from `developers_can_{merge,push}` to `allowed_to_{merge,push}` + # in `gitlab-org/gitlab-ce!5081`. The API interface has not been changed (to maintain compatibility), + # but it works with the changed data model to infer `developers_can_merge` and `developers_can_push`. + # # Parameters: # id (required) - The ID of a project # branch (required) - The name of the branch @@ -49,18 +53,19 @@ module API @branch = user_project.repository.find_branch(params[:branch]) not_found!('Branch') unless @branch protected_branch = user_project.protected_branches.find_by(name: @branch.name) - developers_can_push = to_boolean(params[:developers_can_push]) - developers_can_merge = to_boolean(params[:developers_can_merge]) - - if protected_branch - protected_branch.developers_can_push = developers_can_push unless developers_can_push.nil? - protected_branch.developers_can_merge = developers_can_merge unless developers_can_merge.nil? - protected_branch.save - else - user_project.protected_branches.create(name: @branch.name, - developers_can_push: developers_can_push || false, - developers_can_merge: developers_can_merge || false) - end + protected_branch_params = { + name: @branch.name, + developers_can_push: params[:developers_can_push], + developers_can_merge: params[:developers_can_merge] + } + + service = if protected_branch + ProtectedBranches::UpdateService.new(user_project, current_user, protected_branch.id, protected_branch_params) + else + ProtectedBranches::CreateService.new(user_project, current_user, protected_branch_params) + end + + service.execute present @branch, with: Entities::RepoBranch, project: user_project end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index e76e7304674..e51bee5c846 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -126,11 +126,13 @@ module API end expose :developers_can_push do |repo_branch, options| - options[:project].developers_can_push_to_protected_branch? repo_branch.name + project = options[:project] + project.protected_branches.matching(repo_branch.name).any? { |protected_branch| protected_branch.push_access_level.developers? } end expose :developers_can_merge do |repo_branch, options| - options[:project].developers_can_merge_to_protected_branch? repo_branch.name + project = options[:project] + project.protected_branches.matching(repo_branch.name).any? { |protected_branch| protected_branch.merge_access_level.developers? } end end diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index 719da27f919..e8fd697965f 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -112,7 +112,7 @@ describe API::API, api: true do before do project.repository.add_branch(user, protected_branch, 'master') - create(:protected_branch, project: project, name: protected_branch, developers_can_push: true, developers_can_merge: true) + create(:protected_branch, :developers_can_push, :developers_can_merge, project: project, name: protected_branch) end it 'updates that a developer can push' do -- cgit v1.2.1 From 6d841eaadcbccfa4527bd892bf86fc8dbba19455 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Wed, 27 Jul 2016 10:14:38 +0530 Subject: Authorize user before creating/updating a protected branch. 1. This is a third line of defence (first in the view, second in the controller). 2. Duplicate the `API::Helpers.to_boolean` method in `BaseService`. The other alternative is to `include API::Helpers`, but this brings with it a number of other methods that might cause conflicts. 3. Return a 403 if authorization fails. --- app/services/protected_branches/base_service.rb | 13 ++++++++++--- app/services/protected_branches/create_service.rb | 2 ++ app/services/protected_branches/update_service.rb | 5 +++-- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/app/services/protected_branches/base_service.rb b/app/services/protected_branches/base_service.rb index a5896587ded..bdd175e8552 100644 --- a/app/services/protected_branches/base_service.rb +++ b/app/services/protected_branches/base_service.rb @@ -1,7 +1,5 @@ module ProtectedBranches class BaseService < ::BaseService - include API::Helpers - def initialize(project, current_user, params = {}) super(project, current_user, params) @allowed_to_push = params[:allowed_to_push] @@ -14,7 +12,7 @@ module ProtectedBranches set_push_access_levels! end - protected + private def set_merge_access_levels! case @allowed_to_merge @@ -56,5 +54,14 @@ module ProtectedBranches 'masters' end end + + protected + + def to_boolean(value) + return true if value =~ /^(true|t|yes|y|1|on)$/i + return false if value =~ /^(false|f|no|n|0|off)$/i + + nil + end end end diff --git a/app/services/protected_branches/create_service.rb b/app/services/protected_branches/create_service.rb index 212c2134638..36019906416 100644 --- a/app/services/protected_branches/create_service.rb +++ b/app/services/protected_branches/create_service.rb @@ -3,6 +3,8 @@ module ProtectedBranches attr_reader :protected_branch def execute + raise Gitlab::Access::AccessDeniedError unless current_user.can?(:admin_project, project) + ProtectedBranch.transaction do @protected_branch = project.protected_branches.new(name: params[:name]) @protected_branch.save! diff --git a/app/services/protected_branches/update_service.rb b/app/services/protected_branches/update_service.rb index 4a2b1be9c93..58f2f774bae 100644 --- a/app/services/protected_branches/update_service.rb +++ b/app/services/protected_branches/update_service.rb @@ -4,12 +4,13 @@ module ProtectedBranches def initialize(project, current_user, id, params = {}) super(project, current_user, params) - @id = id + @protected_branch = ProtectedBranch.find(id) end def execute + raise Gitlab::Access::AccessDeniedError unless current_user.can?(:admin_project, project) + ProtectedBranch.transaction do - @protected_branch = ProtectedBranch.find(@id) set_access_levels! end -- cgit v1.2.1 From c93a895abc434b9b78aa7cf4d285ce309cfd868a Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Wed, 27 Jul 2016 12:33:50 +0530 Subject: Fix `git_push_service_spec` 1. Caused by incorrect test setup. The user wasn't added to the project, so protected branch creation failed authorization. 2. Change setup for a different test (`Event.last` to `Event.find_by_action`) because our `project.team << ...` addition was causing a conflict. --- spec/services/git_push_service_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 663c270d61f..621eced83f6 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -7,6 +7,7 @@ describe GitPushService, services: true do let(:project) { create :project } before do + project.team << [user, :master] @blankrev = Gitlab::Git::BLANK_SHA @oldrev = sample_commit.parent_id @newrev = sample_commit.id @@ -172,7 +173,7 @@ describe GitPushService, services: true do describe "Push Event" do before do service = execute_service(project, user, @oldrev, @newrev, @ref ) - @event = Event.last + @event = Event.find_by_action(Event::PUSHED) @push_data = service.push_data end -- cgit v1.2.1 From 0a8aeb46dc187cc309ddbe23d8624f5d24b6218c Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 29 Jul 2016 11:43:07 +0530 Subject: Use `Gitlab::Access` to protected branch access levels. 1. It makes sense to reuse these constants since we had them duplicated in the previous enum implementation. This also simplifies our `check_access` implementation, because we can use `project.team.max_member_access` directly. 2. Use `accepts_nested_attributes_for` to create push/merge access levels. This was a bit fiddly to set up, but this simplifies our code by quite a large amount. We can even get rid of `ProtectedBranches::BaseService`. 3. Move API handling back into the API (previously in `ProtectedBranches::BaseService#translate_api_params`. 4. The protected branch services now return a `ProtectedBranch` rather than `true/false`. 5. Run `load_protected_branches` on-demand in the `create` action, to prevent it being called unneccessarily. 6. "Masters" is pre-selected as the default option for "Allowed to Push" and "Allowed to Merge". 7. These changes were based on a review from @rymai in !5081. --- .../protected_branches_access_select.js.es6 | 18 ++++-- .../projects/protected_branches_controller.rb | 31 ++++++---- app/models/protected_branch.rb | 11 +--- app/models/protected_branch/merge_access_level.rb | 16 ++---- app/models/protected_branch/push_access_level.rb | 21 +++---- app/services/git_push_service.rb | 13 ++++- app/services/protected_branches/base_service.rb | 67 ---------------------- app/services/protected_branches/create_service.rb | 20 ++++--- app/services/protected_branches/update_service.rb | 19 ++---- .../protected_branches/_branches_list.html.haml | 2 +- .../protected_branches/_protected_branch.html.haml | 4 +- .../projects/protected_branches/index.html.haml | 14 ++--- ...705054938_add_protected_branches_push_access.rb | 4 +- ...05054952_add_protected_branches_merge_access.rb | 4 +- db/schema.rb | 16 +++--- lib/api/branches.rb | 36 +++++++++--- lib/api/entities.rb | 4 +- spec/factories/protected_branches.rb | 16 ++++-- spec/features/protected_branches_spec.rb | 12 ++-- spec/services/git_push_service_spec.rb | 12 ++-- 20 files changed, 152 insertions(+), 188 deletions(-) delete mode 100644 app/services/protected_branches/base_service.rb diff --git a/app/assets/javascripts/protected_branches_access_select.js.es6 b/app/assets/javascripts/protected_branches_access_select.js.es6 index 93b7d7755a7..e98312bbf37 100644 --- a/app/assets/javascripts/protected_branches_access_select.js.es6 +++ b/app/assets/javascripts/protected_branches_access_select.js.es6 @@ -1,27 +1,35 @@ class ProtectedBranchesAccessSelect { - constructor(container, saveOnSelect) { + constructor(container, saveOnSelect, selectDefault) { this.container = container; this.saveOnSelect = saveOnSelect; this.container.find(".allowed-to-merge").each((i, element) => { var fieldName = $(element).data('field-name'); - return $(element).glDropdown({ + var dropdown = $(element).glDropdown({ data: gon.merge_access_levels, selectable: true, fieldName: fieldName, clicked: _.chain(this.onSelect).partial(element).bind(this).value() }); + + if (selectDefault) { + dropdown.data('glDropdown').selectRowAtIndex(document.createEvent("Event"), 0); + } }); this.container.find(".allowed-to-push").each((i, element) => { var fieldName = $(element).data('field-name'); - return $(element).glDropdown({ + var dropdown = $(element).glDropdown({ data: gon.push_access_levels, selectable: true, fieldName: fieldName, clicked: _.chain(this.onSelect).partial(element).bind(this).value() }); + + if (selectDefault) { + dropdown.data('glDropdown').selectRowAtIndex(document.createEvent("Event"), 0); + } }); } @@ -36,7 +44,9 @@ class ProtectedBranchesAccessSelect { _method: 'PATCH', id: $(dropdown).data('id'), protected_branch: { - ["" + ($(dropdown).data('type'))]: selected.id + ["" + ($(dropdown).data('type')) + "_attributes"]: { + "access_level": selected.id + } } }, success: function() { diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index ddf1824ccb9..d28ec6e2eac 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -3,23 +3,22 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController before_action :require_non_empty_project before_action :authorize_admin_project! before_action :load_protected_branch, only: [:show, :update, :destroy] - before_action :load_protected_branches, only: [:index, :create] + before_action :load_protected_branches, only: [:index] layout "project_settings" def index @protected_branch = @project.protected_branches.new - gon.push({ open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } }, - push_access_levels: ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text } }, - merge_access_levels: ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text } } }) + load_protected_branches_gon_variables end def create - service = ProtectedBranches::CreateService.new(@project, current_user, protected_branch_params) - if service.execute + @protected_branch = ProtectedBranches::CreateService.new(@project, current_user, protected_branch_params).execute + if @protected_branch.persisted? redirect_to namespace_project_protected_branches_path(@project.namespace, @project) else - @protected_branch = service.protected_branch + load_protected_branches + load_protected_branches_gon_variables render :index end end @@ -29,15 +28,15 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController end def update - service = ProtectedBranches::UpdateService.new(@project, current_user, params[:id], protected_branch_params) + @protected_branch = ProtectedBranches::UpdateService.new(@project, current_user, protected_branch_params).execute(@protected_branch) - if service.execute + if @protected_branch.valid? respond_to do |format| - format.json { render json: service.protected_branch, status: :ok } + format.json { render json: @protected_branch, status: :ok } end else respond_to do |format| - format.json { render json: service.protected_branch.errors, status: :unprocessable_entity } + format.json { render json: @protected_branch.errors, status: :unprocessable_entity } end end end @@ -58,10 +57,18 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController end def protected_branch_params - params.require(:protected_branch).permit(:name, :allowed_to_push, :allowed_to_merge) + params.require(:protected_branch).permit(:name, + merge_access_level_attributes: [:access_level], + push_access_level_attributes: [:access_level]) end def load_protected_branches @protected_branches = @project.protected_branches.order(:name).page(params[:page]) end + + def load_protected_branches_gon_variables + gon.push({ open_branches: @project.open_branches.map { |br| { text: br.name, id: br.name, title: br.name } }, + push_access_levels: ProtectedBranch::PushAccessLevel.human_access_levels.map { |id, text| { id: id, text: text } }, + merge_access_levels: ProtectedBranch::MergeAccessLevel.human_access_levels.map { |id, text| { id: id, text: text } } }) + end end diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index c0bee72b4d7..226b3f54342 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -8,18 +8,13 @@ class ProtectedBranch < ActiveRecord::Base has_one :merge_access_level, dependent: :destroy has_one :push_access_level, dependent: :destroy + accepts_nested_attributes_for :push_access_level + accepts_nested_attributes_for :merge_access_level + def commit project.commit(self.name) end - def allowed_to_push - self.push_access_level && self.push_access_level.access_level - end - - def allowed_to_merge - self.merge_access_level && self.merge_access_level.access_level - end - # Returns all protected branches that match the given branch name. # This realizes all records from the scope built up so far, and does # _not_ return a relation. diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb index 17a3a86c3e1..25a6ca6a8ee 100644 --- a/app/models/protected_branch/merge_access_level.rb +++ b/app/models/protected_branch/merge_access_level.rb @@ -2,25 +2,19 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base belongs_to :protected_branch delegate :project, to: :protected_branch - enum access_level: [:masters, :developers] + validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER, + Gitlab::Access::DEVELOPER] } def self.human_access_levels { - "masters" => "Masters", - "developers" => "Developers + Masters" + Gitlab::Access::MASTER => "Masters", + Gitlab::Access::DEVELOPER => "Developers + Masters" }.with_indifferent_access end def check_access(user) return true if user.is_admin? - - min_member_access = if masters? - Gitlab::Access::MASTER - elsif developers? - Gitlab::Access::DEVELOPER - end - - project.team.max_member_access(user.id) >= min_member_access + project.team.max_member_access(user.id) >= access_level end def humanize diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index 22096b13300..1999316aa26 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -2,27 +2,22 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base belongs_to :protected_branch delegate :project, to: :protected_branch - enum access_level: [:masters, :developers, :no_one] + validates :access_level, presence: true, inclusion: { in: [Gitlab::Access::MASTER, + Gitlab::Access::DEVELOPER, + Gitlab::Access::NO_ACCESS] } def self.human_access_levels { - "masters" => "Masters", - "developers" => "Developers + Masters", - "no_one" => "No one" + Gitlab::Access::MASTER => "Masters", + Gitlab::Access::DEVELOPER => "Developers + Masters", + Gitlab::Access::NO_ACCESS => "No one" }.with_indifferent_access end def check_access(user) - return false if no_one? + return false if access_level == Gitlab::Access::NO_ACCESS return true if user.is_admin? - - min_member_access = if masters? - Gitlab::Access::MASTER - elsif developers? - Gitlab::Access::DEVELOPER - end - - project.team.max_member_access(user.id) >= min_member_access + project.team.max_member_access(user.id) >= access_level end def humanize diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 604737e6934..3f6a177bf3a 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -88,10 +88,17 @@ class GitPushService < BaseService # Set protection on the default branch if configured if current_application_settings.default_branch_protection != PROTECTION_NONE - allowed_to_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? 'developers' : 'masters' - allowed_to_merge = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_MERGE ? 'developers' : 'masters' - params = { name: @project.default_branch, allowed_to_push: allowed_to_push, allowed_to_merge: allowed_to_merge } + params = { + name: @project.default_branch, + push_access_level_attributes: { + access_level: current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER + }, + merge_access_level_attributes: { + access_level: current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_MERGE ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER + } + } + ProtectedBranches::CreateService.new(@project, current_user, params).execute end end diff --git a/app/services/protected_branches/base_service.rb b/app/services/protected_branches/base_service.rb deleted file mode 100644 index bdd175e8552..00000000000 --- a/app/services/protected_branches/base_service.rb +++ /dev/null @@ -1,67 +0,0 @@ -module ProtectedBranches - class BaseService < ::BaseService - def initialize(project, current_user, params = {}) - super(project, current_user, params) - @allowed_to_push = params[:allowed_to_push] - @allowed_to_merge = params[:allowed_to_merge] - end - - def set_access_levels! - translate_api_params! - set_merge_access_levels! - set_push_access_levels! - end - - private - - def set_merge_access_levels! - case @allowed_to_merge - when 'masters' - @protected_branch.merge_access_level.masters! - when 'developers' - @protected_branch.merge_access_level.developers! - end - end - - def set_push_access_levels! - case @allowed_to_push - when 'masters' - @protected_branch.push_access_level.masters! - when 'developers' - @protected_branch.push_access_level.developers! - when 'no_one' - @protected_branch.push_access_level.no_one! - end - end - - # The `branches` API still uses `developers_can_push` and `developers_can_merge`, - # which need to be translated to `allowed_to_push` and `allowed_to_merge`, - # respectively. - def translate_api_params! - @allowed_to_push ||= - case to_boolean(params[:developers_can_push]) - when true - 'developers' - when false - 'masters' - end - - @allowed_to_merge ||= - case to_boolean(params[:developers_can_merge]) - when true - 'developers' - when false - 'masters' - end - end - - protected - - def to_boolean(value) - return true if value =~ /^(true|t|yes|y|1|on)$/i - return false if value =~ /^(false|f|no|n|0|off)$/i - - nil - end - end -end diff --git a/app/services/protected_branches/create_service.rb b/app/services/protected_branches/create_service.rb index 36019906416..50f79f491ce 100644 --- a/app/services/protected_branches/create_service.rb +++ b/app/services/protected_branches/create_service.rb @@ -1,23 +1,27 @@ module ProtectedBranches - class CreateService < ProtectedBranches::BaseService + class CreateService < BaseService attr_reader :protected_branch def execute raise Gitlab::Access::AccessDeniedError unless current_user.can?(:admin_project, project) + protected_branch = project.protected_branches.new(params) + ProtectedBranch.transaction do - @protected_branch = project.protected_branches.new(name: params[:name]) - @protected_branch.save! + protected_branch.save! - @protected_branch.create_push_access_level! - @protected_branch.create_merge_access_level! + if protected_branch.push_access_level.blank? + protected_branch.create_push_access_level!(access_level: Gitlab::Access::MASTER) + end - set_access_levels! + if protected_branch.merge_access_level.blank? + protected_branch.create_merge_access_level!(access_level: Gitlab::Access::MASTER) + end end - true + protected_branch rescue ActiveRecord::RecordInvalid - false + protected_branch end end end diff --git a/app/services/protected_branches/update_service.rb b/app/services/protected_branches/update_service.rb index 58f2f774bae..da4c96b3e5f 100644 --- a/app/services/protected_branches/update_service.rb +++ b/app/services/protected_branches/update_service.rb @@ -1,22 +1,13 @@ module ProtectedBranches - class UpdateService < ProtectedBranches::BaseService + class UpdateService < BaseService attr_reader :protected_branch - def initialize(project, current_user, id, params = {}) - super(project, current_user, params) - @protected_branch = ProtectedBranch.find(id) - end - - def execute + def execute(protected_branch) raise Gitlab::Access::AccessDeniedError unless current_user.can?(:admin_project, project) - ProtectedBranch.transaction do - set_access_levels! - end - - true - rescue ActiveRecord::RecordInvalid - false + @protected_branch = protected_branch + @protected_branch.update(params) + @protected_branch end end end diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml index a6956c8e69f..2498b57afb4 100644 --- a/app/views/projects/protected_branches/_branches_list.html.haml +++ b/app/views/projects/protected_branches/_branches_list.html.haml @@ -26,4 +26,4 @@ = paginate @protected_branches, theme: 'gitlab' :javascript - new ProtectedBranchesAccessSelect($(".protected-branches-list"), true); + new ProtectedBranchesAccessSelect($(".protected-branches-list"), true, false); diff --git a/app/views/projects/protected_branches/_protected_branch.html.haml b/app/views/projects/protected_branches/_protected_branch.html.haml index 2fc6081e448..498e412235e 100644 --- a/app/views/projects/protected_branches/_protected_branch.html.haml +++ b/app/views/projects/protected_branches/_protected_branch.html.haml @@ -18,12 +18,12 @@ = hidden_field_tag "allowed_to_merge_#{protected_branch.id}", protected_branch.merge_access_level.access_level = dropdown_tag(protected_branch.merge_access_level.humanize, options: { title: "Allowed to merge", toggle_class: 'allowed-to-merge', dropdown_class: 'dropdown-menu-selectable merge', - data: { field_name: "allowed_to_merge_#{protected_branch.id}", url: url, id: protected_branch.id, type: "allowed_to_merge" }}) + data: { field_name: "allowed_to_merge_#{protected_branch.id}", url: url, id: protected_branch.id, type: "merge_access_level" }}) %td = hidden_field_tag "allowed_to_push_#{protected_branch.id}", protected_branch.push_access_level.access_level = dropdown_tag(protected_branch.push_access_level.humanize, options: { title: "Allowed to push", toggle_class: 'allowed-to-push', dropdown_class: 'dropdown-menu-selectable push', - data: { field_name: "allowed_to_push_#{protected_branch.id}", url: url, id: protected_branch.id, type: "allowed_to_push" }}) + data: { field_name: "allowed_to_push_#{protected_branch.id}", url: url, id: protected_branch.id, type: "push_access_level" }}) - if can_admin_project %td = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, protected_branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-warning btn-sm pull-right" diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 75c2063027a..8270da6cd27 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -32,20 +32,20 @@ are supported. .form-group - = f.hidden_field :allowed_to_merge - = f.label :allowed_to_merge, "Allowed to merge: ", class: "label-light append-bottom-0" + = hidden_field_tag 'protected_branch[merge_access_level_attributes][access_level]' + = label_tag "Allowed to merge: ", nil, class: "label-light append-bottom-0" = dropdown_tag("", options: { title: "Allowed to merge", toggle_class: 'allowed-to-merge', dropdown_class: 'dropdown-menu-selectable', - data: { field_name: "protected_branch[allowed_to_merge]" }}) + data: { field_name: "protected_branch[merge_access_level_attributes][access_level]" }}) .form-group - = f.hidden_field :allowed_to_push - = f.label :allowed_to_push, "Allowed to push: ", class: "label-light append-bottom-0" + = hidden_field_tag 'protected_branch[push_access_level_attributes][access_level]' + = label_tag "Allowed to push: ", nil, class: "label-light append-bottom-0" = dropdown_tag("", options: { title: "Allowed to push", toggle_class: 'allowed-to-push', dropdown_class: 'dropdown-menu-selectable', - data: { field_name: "protected_branch[allowed_to_push]" }}) + data: { field_name: "protected_branch[push_access_level_attributes][access_level]" }}) = f.submit "Protect", class: "btn-create btn protect-branch-btn", disabled: true @@ -54,4 +54,4 @@ = render "branches_list" :javascript - new ProtectedBranchesAccessSelect($(".new_protected_branch"), false); + new ProtectedBranchesAccessSelect($(".new_protected_branch"), false, true); diff --git a/db/migrate/20160705054938_add_protected_branches_push_access.rb b/db/migrate/20160705054938_add_protected_branches_push_access.rb index 3031574fe2a..5c14d449e71 100644 --- a/db/migrate/20160705054938_add_protected_branches_push_access.rb +++ b/db/migrate/20160705054938_add_protected_branches_push_access.rb @@ -5,7 +5,9 @@ class AddProtectedBranchesPushAccess < ActiveRecord::Migration def change create_table :protected_branch_push_access_levels do |t| t.references :protected_branch, index: { name: "index_protected_branch_push_access" }, foreign_key: true, null: false - t.integer :access_level, default: 0, null: false + + # Gitlab::Access::MASTER == 40 + t.integer :access_level, default: 40, null: false t.timestamps null: false end diff --git a/db/migrate/20160705054952_add_protected_branches_merge_access.rb b/db/migrate/20160705054952_add_protected_branches_merge_access.rb index cf1cdb8b3b6..789e3e04220 100644 --- a/db/migrate/20160705054952_add_protected_branches_merge_access.rb +++ b/db/migrate/20160705054952_add_protected_branches_merge_access.rb @@ -5,7 +5,9 @@ class AddProtectedBranchesMergeAccess < ActiveRecord::Migration def change create_table :protected_branch_merge_access_levels do |t| t.references :protected_branch, index: { name: "index_protected_branch_merge_access" }, foreign_key: true, null: false - t.integer :access_level, default: 0, null: false + + # Gitlab::Access::MASTER == 40 + t.integer :access_level, default: 40, null: false t.timestamps null: false end diff --git a/db/schema.rb b/db/schema.rb index 7a5eded8e02..2d2ae5fd840 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -868,19 +868,19 @@ ActiveRecord::Schema.define(version: 20160722221922) do add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree create_table "protected_branch_merge_access_levels", force: :cascade do |t| - t.integer "protected_branch_id", null: false - t.integer "access_level", default: 0, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "protected_branch_id", null: false + t.integer "access_level", default: 40, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "protected_branch_merge_access_levels", ["protected_branch_id"], name: "index_protected_branch_merge_access", using: :btree create_table "protected_branch_push_access_levels", force: :cascade do |t| - t.integer "protected_branch_id", null: false - t.integer "access_level", default: 0, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "protected_branch_id", null: false + t.integer "access_level", default: 40, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "protected_branch_push_access_levels", ["protected_branch_id"], name: "index_protected_branch_push_access", using: :btree diff --git a/lib/api/branches.rb b/lib/api/branches.rb index 4133a1f7a6b..a77afe634f6 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -53,19 +53,37 @@ module API @branch = user_project.repository.find_branch(params[:branch]) not_found!('Branch') unless @branch protected_branch = user_project.protected_branches.find_by(name: @branch.name) + + developers_can_merge = to_boolean(params[:developers_can_merge]) + developers_can_push = to_boolean(params[:developers_can_push]) + protected_branch_params = { - name: @branch.name, - developers_can_push: params[:developers_can_push], - developers_can_merge: params[:developers_can_merge] + name: @branch.name } - service = if protected_branch - ProtectedBranches::UpdateService.new(user_project, current_user, protected_branch.id, protected_branch_params) - else - ProtectedBranches::CreateService.new(user_project, current_user, protected_branch_params) - end + unless developers_can_merge.nil? + protected_branch_params.merge!({ + merge_access_level_attributes: { + access_level: developers_can_merge ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER + } + }) + end - service.execute + unless developers_can_push.nil? + protected_branch_params.merge!({ + push_access_level_attributes: { + access_level: developers_can_push ? Gitlab::Access::DEVELOPER : Gitlab::Access::MASTER + } + }) + end + + if protected_branch + service = ProtectedBranches::UpdateService.new(user_project, current_user, protected_branch_params) + service.execute(protected_branch) + else + service = ProtectedBranches::CreateService.new(user_project, current_user, protected_branch_params) + service.execute + end present @branch, with: Entities::RepoBranch, project: user_project end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index e51bee5c846..4eb95d8a215 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -127,12 +127,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.developers? } + project.protected_branches.matching(repo_branch.name).any? { |protected_branch| protected_branch.push_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_level.developers? } + project.protected_branches.matching(repo_branch.name).any? { |protected_branch| protected_branch.merge_access_level.access_level == Gitlab::Access::DEVELOPER } end end diff --git a/spec/factories/protected_branches.rb b/spec/factories/protected_branches.rb index 24a9b78f0c2..5575852c2d7 100644 --- a/spec/factories/protected_branches.rb +++ b/spec/factories/protected_branches.rb @@ -4,20 +4,26 @@ FactoryGirl.define do project after(:create) do |protected_branch| - protected_branch.create_push_access_level!(access_level: :masters) - protected_branch.create_merge_access_level!(access_level: :masters) + protected_branch.create_push_access_level!(access_level: Gitlab::Access::MASTER) + protected_branch.create_merge_access_level!(access_level: Gitlab::Access::MASTER) end trait :developers_can_push do - after(:create) { |protected_branch| protected_branch.push_access_level.developers! } + after(:create) do |protected_branch| + protected_branch.push_access_level.update!(access_level: Gitlab::Access::DEVELOPER) + end end trait :developers_can_merge do - after(:create) { |protected_branch| protected_branch.merge_access_level.developers! } + after(:create) do |protected_branch| + protected_branch.merge_access_level.update!(access_level: Gitlab::Access::DEVELOPER) + end end trait :no_one_can_push do - after(:create) { |protected_branch| protected_branch.push_access_level.no_one! } + after(:create) do |protected_branch| + protected_branch.push_access_level.update!(access_level: Gitlab::Access::NO_ACCESS) + end end end end diff --git a/spec/features/protected_branches_spec.rb b/spec/features/protected_branches_spec.rb index dac2bcf9efd..57734b33a44 100644 --- a/spec/features/protected_branches_spec.rb +++ b/spec/features/protected_branches_spec.rb @@ -91,12 +91,12 @@ feature 'Projected Branches', feature: true, js: true do set_protected_branch_name('master') within('.new_protected_branch') do find(".allowed-to-push").click - click_on access_type_name + within(".dropdown.open .dropdown-menu") { click_on access_type_name } end click_on "Protect" expect(ProtectedBranch.count).to eq(1) - expect(ProtectedBranch.last.allowed_to_push).to eq(access_type_id) + expect(ProtectedBranch.last.push_access_level.access_level).to eq(access_type_id) end it "allows updating protected branches so that #{access_type_name} can push to them" do @@ -112,7 +112,7 @@ feature 'Projected Branches', feature: true, js: true do end wait_for_ajax - expect(ProtectedBranch.last.allowed_to_push).to eq(access_type_id) + expect(ProtectedBranch.last.push_access_level.access_level).to eq(access_type_id) end end @@ -122,12 +122,12 @@ feature 'Projected Branches', feature: true, js: true do set_protected_branch_name('master') within('.new_protected_branch') do find(".allowed-to-merge").click - click_on access_type_name + within(".dropdown.open .dropdown-menu") { click_on access_type_name } end click_on "Protect" expect(ProtectedBranch.count).to eq(1) - expect(ProtectedBranch.last.allowed_to_merge).to eq(access_type_id) + expect(ProtectedBranch.last.merge_access_level.access_level).to eq(access_type_id) end it "allows updating protected branches so that #{access_type_name} can merge to them" do @@ -143,7 +143,7 @@ feature 'Projected Branches', feature: true, js: true do end wait_for_ajax - expect(ProtectedBranch.last.allowed_to_merge).to eq(access_type_id) + expect(ProtectedBranch.last.merge_access_level.access_level).to eq(access_type_id) end end end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 621eced83f6..ffa998dffc3 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -227,8 +227,8 @@ describe GitPushService, services: true do expect(project.default_branch).to eq("master") execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) expect(project.protected_branches).not_to be_empty - expect(project.protected_branches.first.allowed_to_push).to eq('masters') - expect(project.protected_branches.first.allowed_to_merge).to eq('masters') + expect(project.protected_branches.first.push_access_level.access_level).to eq(Gitlab::Access::MASTER) + expect(project.protected_branches.first.merge_access_level.access_level).to eq(Gitlab::Access::MASTER) end it "when pushing a branch for the first time with default branch protection disabled" do @@ -249,8 +249,8 @@ describe GitPushService, services: true do execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) expect(project.protected_branches).not_to be_empty - expect(project.protected_branches.last.allowed_to_push).to eq('developers') - expect(project.protected_branches.last.allowed_to_merge).to eq('masters') + expect(project.protected_branches.last.push_access_level.access_level).to eq(Gitlab::Access::DEVELOPER) + expect(project.protected_branches.last.merge_access_level.access_level).to eq(Gitlab::Access::MASTER) end it "when pushing a branch for the first time with default branch protection set to 'developers can merge'" do @@ -260,8 +260,8 @@ describe GitPushService, services: true do expect(project.default_branch).to eq("master") execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) expect(project.protected_branches).not_to be_empty - expect(project.protected_branches.first.allowed_to_push).to eq('masters') - expect(project.protected_branches.first.allowed_to_merge).to eq('developers') + expect(project.protected_branches.first.push_access_level.access_level).to eq(Gitlab::Access::MASTER) + expect(project.protected_branches.first.merge_access_level.access_level).to eq(Gitlab::Access::DEVELOPER) end it "when pushing new commits to existing branch" do -- cgit v1.2.1 From cebcc417eda08711ad17a433d6d9b4f49830c04c Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Fri, 29 Jul 2016 12:31:15 +0530 Subject: Implement final review comments from @rymai. 1. Instantiate `ProtectedBranchesAccessSelect` from `dispatcher` 2. Use `can?(user, ...)` instead of `user.can?(...)` 3. Add `DOWNTIME` notes to all migrations added in !5081. 4. Add an explicit `down` method for migrations removing the `developers_can_push` and `developers_can_merge` columns, ensuring that the columns created (on rollback) have the appropriate defaults. 5. Remove duplicate CHANGELOG entries. 6. Blank lines after guard clauses. --- CHANGELOG | 1 - app/assets/javascripts/dispatcher.js | 5 +++++ app/models/protected_branch/merge_access_level.rb | 1 + app/models/protected_branch/push_access_level.rb | 1 + app/services/protected_branches/create_service.rb | 2 +- app/services/protected_branches/update_service.rb | 2 +- .../projects/protected_branches/_branches_list.html.haml | 3 --- app/views/projects/protected_branches/index.html.haml | 3 --- .../20160705054938_add_protected_branches_push_access.rb | 2 ++ .../20160705054952_add_protected_branches_merge_access.rb | 2 ++ ...velopers_can_merge_to_protected_branches_merge_access.rb | 9 +++++++++ ...developers_can_push_to_protected_branches_push_access.rb | 9 +++++++++ ...09_remove_developers_can_push_from_protected_branches.rb | 13 ++++++++++++- ...3_remove_developers_can_merge_from_protected_branches.rb | 13 ++++++++++++- 14 files changed, 55 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4f1da451df0..2b04c15b827 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -127,7 +127,6 @@ v 8.10.0 - Allow to define manual actions/builds on Pipelines and Environments - Fix pagination when sorting by columns with lots of ties (like priority) - The Markdown reference parsers now re-use query results to prevent running the same queries multiple times. !5020 - - Add "No one can push" as an option for protected branches. !5081 - Updated project header design - Issuable collapsed assignee tooltip is now the users name - Fix compare view not changing code view rendering style diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index d212d66da1b..9e6901962c6 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -171,6 +171,11 @@ break; case 'search:show': new Search(); + break; + case 'projects:protected_branches:index': + new ProtectedBranchesAccessSelect($(".new_protected_branch"), false, true); + new ProtectedBranchesAccessSelect($(".protected-branches-list"), true, false); + break; } switch (path.first()) { case 'admin': diff --git a/app/models/protected_branch/merge_access_level.rb b/app/models/protected_branch/merge_access_level.rb index 25a6ca6a8ee..b1112ee737d 100644 --- a/app/models/protected_branch/merge_access_level.rb +++ b/app/models/protected_branch/merge_access_level.rb @@ -14,6 +14,7 @@ class ProtectedBranch::MergeAccessLevel < ActiveRecord::Base def check_access(user) return true if user.is_admin? + project.team.max_member_access(user.id) >= access_level end diff --git a/app/models/protected_branch/push_access_level.rb b/app/models/protected_branch/push_access_level.rb index 1999316aa26..6a5e49cf453 100644 --- a/app/models/protected_branch/push_access_level.rb +++ b/app/models/protected_branch/push_access_level.rb @@ -17,6 +17,7 @@ class ProtectedBranch::PushAccessLevel < ActiveRecord::Base def check_access(user) return false if access_level == Gitlab::Access::NO_ACCESS return true if user.is_admin? + project.team.max_member_access(user.id) >= access_level end diff --git a/app/services/protected_branches/create_service.rb b/app/services/protected_branches/create_service.rb index 50f79f491ce..6150a2a83c9 100644 --- a/app/services/protected_branches/create_service.rb +++ b/app/services/protected_branches/create_service.rb @@ -3,7 +3,7 @@ module ProtectedBranches attr_reader :protected_branch def execute - raise Gitlab::Access::AccessDeniedError unless current_user.can?(:admin_project, project) + raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project) protected_branch = project.protected_branches.new(params) diff --git a/app/services/protected_branches/update_service.rb b/app/services/protected_branches/update_service.rb index da4c96b3e5f..89d8ba60134 100644 --- a/app/services/protected_branches/update_service.rb +++ b/app/services/protected_branches/update_service.rb @@ -3,7 +3,7 @@ module ProtectedBranches attr_reader :protected_branch def execute(protected_branch) - raise Gitlab::Access::AccessDeniedError unless current_user.can?(:admin_project, project) + raise Gitlab::Access::AccessDeniedError unless can?(current_user, :admin_project, project) @protected_branch = protected_branch @protected_branch.update(params) diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml index 2498b57afb4..0603a014008 100644 --- a/app/views/projects/protected_branches/_branches_list.html.haml +++ b/app/views/projects/protected_branches/_branches_list.html.haml @@ -24,6 +24,3 @@ = render partial: @protected_branches, locals: { can_admin_project: can_admin_project } = paginate @protected_branches, theme: 'gitlab' - -:javascript - new ProtectedBranchesAccessSelect($(".protected-branches-list"), true, false); diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 8270da6cd27..4efe44c7233 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -52,6 +52,3 @@ %hr = render "branches_list" - -:javascript - new ProtectedBranchesAccessSelect($(".new_protected_branch"), false, true); diff --git a/db/migrate/20160705054938_add_protected_branches_push_access.rb b/db/migrate/20160705054938_add_protected_branches_push_access.rb index 5c14d449e71..f27295524e1 100644 --- a/db/migrate/20160705054938_add_protected_branches_push_access.rb +++ b/db/migrate/20160705054938_add_protected_branches_push_access.rb @@ -2,6 +2,8 @@ # for more information on how to write migrations for GitLab. class AddProtectedBranchesPushAccess < ActiveRecord::Migration + DOWNTIME = false + def change create_table :protected_branch_push_access_levels do |t| t.references :protected_branch, index: { name: "index_protected_branch_push_access" }, foreign_key: true, null: false diff --git a/db/migrate/20160705054952_add_protected_branches_merge_access.rb b/db/migrate/20160705054952_add_protected_branches_merge_access.rb index 789e3e04220..32adfa266cd 100644 --- a/db/migrate/20160705054952_add_protected_branches_merge_access.rb +++ b/db/migrate/20160705054952_add_protected_branches_merge_access.rb @@ -2,6 +2,8 @@ # for more information on how to write migrations for GitLab. class AddProtectedBranchesMergeAccess < ActiveRecord::Migration + DOWNTIME = false + def change create_table :protected_branch_merge_access_levels do |t| t.references :protected_branch, index: { name: "index_protected_branch_merge_access" }, foreign_key: true, null: false diff --git a/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb b/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb index c2b278ce673..fa93936ced7 100644 --- a/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb +++ b/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb @@ -2,6 +2,15 @@ # for more information on how to write migrations for GitLab. class MoveFromDevelopersCanMergeToProtectedBranchesMergeAccess < ActiveRecord::Migration + DOWNTIME = true + DOWNTIME_REASON = <<-HEREDOC + We're creating a `merge_access_level` for each `protected_branch`. If a user creates a `protected_branch` while this + is running, we might be left with a `protected_branch` _without_ an associated `merge_access_level`. The `protected_branches` + table must not change while this is running, so downtime is required. + + https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5081#note_13247410 + HEREDOC + def up execute <<-HEREDOC INSERT into protected_branch_merge_access_levels (protected_branch_id, access_level, created_at, updated_at) diff --git a/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb b/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb index 5bc70283f60..56f6159d1d8 100644 --- a/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb +++ b/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb @@ -2,6 +2,15 @@ # for more information on how to write migrations for GitLab. class MoveFromDevelopersCanPushToProtectedBranchesPushAccess < ActiveRecord::Migration + DOWNTIME = true + DOWNTIME_REASON = <<-HEREDOC + We're creating a `push_access_level` for each `protected_branch`. If a user creates a `protected_branch` while this + is running, we might be left with a `protected_branch` _without_ an associated `push_access_level`. The `protected_branches` + table must not change while this is running, so downtime is required. + + https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5081#note_13247410 + HEREDOC + def up execute <<-HEREDOC INSERT into protected_branch_push_access_levels (protected_branch_id, access_level, created_at, updated_at) diff --git a/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb b/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb index ad6ad43686d..f563f660ddf 100644 --- a/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb +++ b/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb @@ -2,7 +2,18 @@ # for more information on how to write migrations for GitLab. class RemoveDevelopersCanPushFromProtectedBranches < ActiveRecord::Migration - def change + include Gitlab::Database::MigrationHelpers + + # This is only required for `#down` + disable_ddl_transaction! + + DOWNTIME = false + + def up remove_column :protected_branches, :developers_can_push, :boolean end + + def down + add_column_with_default(:protected_branches, :developers_can_push, :boolean, default: false, null: false) + end end diff --git a/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb b/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb index 084914e423a..aa71e06d36e 100644 --- a/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb +++ b/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb @@ -2,7 +2,18 @@ # for more information on how to write migrations for GitLab. class RemoveDevelopersCanMergeFromProtectedBranches < ActiveRecord::Migration - def change + include Gitlab::Database::MigrationHelpers + + # This is only required for `#down` + disable_ddl_transaction! + + DOWNTIME = false + + def up remove_column :protected_branches, :developers_can_merge, :boolean end + + def down + add_column_with_default(:protected_branches, :developers_can_merge, :boolean, default: false, null: false) + end end -- cgit v1.2.1 From 002ad215818450d2cbbc5fa065850a953dc7ada8 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 20 Jul 2016 20:13:02 +0200 Subject: Method for returning issues readable by a user The method Ability.issues_readable_by_user takes a list of users and an optional user and returns an Array of issues readable by said user. This method in turn is used by Banzai::ReferenceParser::IssueParser#nodes_visible_to_user so this method no longer needs to get all the available abilities just to check if a user has the "read_issue" ability. To test this I benchmarked an issue with 222 comments on my development environment. Using these changes the time spent in nodes_visible_to_user was reduced from around 120 ms to around 40 ms. --- CHANGELOG | 1 + app/models/ability.rb | 10 + app/models/issue.rb | 28 +++ lib/banzai/reference_parser/issue_parser.rb | 7 +- .../banzai/reference_parser/issue_parser_spec.rb | 12 +- spec/models/ability_spec.rb | 48 ++++ spec/models/concerns/mentionable_spec.rb | 6 +- spec/models/issue_spec.rb | 253 +++++++++++++++++++++ 8 files changed, 355 insertions(+), 10 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d555d23860d..e18a133cfb0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.11.0 (unreleased) - Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects - Retrieve rendered HTML from cache in one request - Fix renaming repository when name contains invalid chararacters under project settings + - Optimize checking if a user has read access to a list of issues !5370 - Nokogiri's various parsing methods are now instrumented - Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363 - Add build event color in HipChat messages (David Eisner) diff --git a/app/models/ability.rb b/app/models/ability.rb index e47c5539f60..d95a2507199 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -47,6 +47,16 @@ class Ability end end + # Returns an Array of Issues that can be read by the given user. + # + # issues - The issues to reduce down to those readable by the user. + # user - The User for which to check the issues + def issues_readable_by_user(issues, user = nil) + return issues if user && user.admin? + + issues.select { |issue| issue.visible_to_user?(user) } + end + # List of possible abilities for anonymous user def anonymous_abilities(user, subject) if subject.is_a?(PersonalSnippet) diff --git a/app/models/issue.rb b/app/models/issue.rb index d9428ebc9fb..11f734cfc6d 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -230,6 +230,34 @@ class Issue < ActiveRecord::Base self.closed_by_merge_requests(current_user).empty? end + # Returns `true` if the current issue can be viewed by either a logged in User + # or an anonymous user. + def visible_to_user?(user = nil) + user ? readable_by?(user) : publicly_visible? + end + + # Returns `true` if the given User can read the current Issue. + def readable_by?(user) + if user.admin? + true + elsif project.owner == user + true + elsif confidential? + author == user || + assignee == user || + project.team.member?(user, Gitlab::Access::REPORTER) + else + project.public? || + project.internal? && !user.external? || + project.team.member?(user) + end + end + + # Returns `true` if this Issue is visible to everybody. + def publicly_visible? + project.public? && !confidential? + end + def overdue? due_date.try(:past?) || false end diff --git a/lib/banzai/reference_parser/issue_parser.rb b/lib/banzai/reference_parser/issue_parser.rb index f306079d833..6c20dec5734 100644 --- a/lib/banzai/reference_parser/issue_parser.rb +++ b/lib/banzai/reference_parser/issue_parser.rb @@ -9,10 +9,11 @@ module Banzai issues = issues_for_nodes(nodes) - nodes.select do |node| - issue = issue_for_node(issues, node) + readable_issues = Ability. + issues_readable_by_user(issues.values, user).to_set - issue ? can?(user, :read_issue, issue) : false + nodes.select do |node| + readable_issues.include?(issue_for_node(issues, node)) end end diff --git a/spec/lib/banzai/reference_parser/issue_parser_spec.rb b/spec/lib/banzai/reference_parser/issue_parser_spec.rb index 514c752546d..85cfe728b6a 100644 --- a/spec/lib/banzai/reference_parser/issue_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/issue_parser_spec.rb @@ -16,17 +16,17 @@ describe Banzai::ReferenceParser::IssueParser, lib: true do end it 'returns the nodes when the user can read the issue' do - expect(Ability.abilities).to receive(:allowed?). - with(user, :read_issue, issue). - and_return(true) + expect(Ability).to receive(:issues_readable_by_user). + with([issue], user). + and_return([issue]) expect(subject.nodes_visible_to_user(user, [link])).to eq([link]) end it 'returns an empty Array when the user can not read the issue' do - expect(Ability.abilities).to receive(:allowed?). - with(user, :read_issue, issue). - and_return(false) + expect(Ability).to receive(:issues_readable_by_user). + with([issue], user). + and_return([]) expect(subject.nodes_visible_to_user(user, [link])).to eq([]) end diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb index cd5f40fe3d2..853f6943cef 100644 --- a/spec/models/ability_spec.rb +++ b/spec/models/ability_spec.rb @@ -170,4 +170,52 @@ describe Ability, lib: true do end end end + + describe '.issues_readable_by_user' do + context 'with an admin user' do + it 'returns all given issues' do + user = build(:user, admin: true) + issue = build(:issue) + + expect(described_class.issues_readable_by_user([issue], user)). + to eq([issue]) + end + end + + context 'with a regular user' do + it 'returns the issues readable by the user' do + user = build(:user) + issue = build(:issue) + + expect(issue).to receive(:readable_by?).with(user).and_return(true) + + expect(described_class.issues_readable_by_user([issue], user)). + to eq([issue]) + end + + it 'returns an empty Array when no issues are readable' do + user = build(:user) + issue = build(:issue) + + expect(issue).to receive(:readable_by?).with(user).and_return(false) + + expect(described_class.issues_readable_by_user([issue], user)).to eq([]) + end + end + + context 'without a regular user' do + it 'returns issues that are publicly visible' do + hidden_issue = build(:issue) + visible_issue = build(:issue) + + expect(hidden_issue).to receive(:publicly_visible?).and_return(false) + expect(visible_issue).to receive(:publicly_visible?).and_return(true) + + issues = described_class. + issues_readable_by_user([hidden_issue, visible_issue]) + + expect(issues).to eq([visible_issue]) + end + end + end end diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb index 5e652660e2c..549b0042038 100644 --- a/spec/models/concerns/mentionable_spec.rb +++ b/spec/models/concerns/mentionable_spec.rb @@ -68,7 +68,7 @@ describe Issue, "Mentionable" do describe '#create_cross_references!' do let(:project) { create(:project) } - let(:author) { double('author') } + let(:author) { build(:user) } let(:commit) { project.commit } let(:commit2) { project.commit } @@ -88,6 +88,10 @@ describe Issue, "Mentionable" do let(:author) { create(:author) } let(:issues) { create_list(:issue, 2, project: project, author: author) } + before do + project.team << [author, Gitlab::Access::DEVELOPER] + end + context 'before changes are persisted' do it 'ignores pre-existing references' do issue = create_issue(description: issues[0].to_reference) diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 6a897c96690..3259f795296 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -306,4 +306,257 @@ describe Issue, models: true do expect(user2.assigned_open_issues_count).to eq(1) end end + + describe '#visible_to_user?' do + context 'with a user' do + let(:user) { build(:user) } + let(:issue) { build(:issue) } + + it 'returns true when the issue is readable' do + expect(issue).to receive(:readable_by?).with(user).and_return(true) + + expect(issue.visible_to_user?(user)).to eq(true) + end + + it 'returns false when the issue is not readable' do + expect(issue).to receive(:readable_by?).with(user).and_return(false) + + expect(issue.visible_to_user?(user)).to eq(false) + end + end + + context 'without a user' do + let(:issue) { build(:issue) } + + it 'returns true when the issue is publicly visible' do + expect(issue).to receive(:publicly_visible?).and_return(true) + + expect(issue.visible_to_user?).to eq(true) + end + + it 'returns false when the issue is not publicly visible' do + expect(issue).to receive(:publicly_visible?).and_return(false) + + expect(issue.visible_to_user?).to eq(false) + end + end + end + + describe '#readable_by?' do + describe 'with a regular user that is not a team member' do + let(:user) { create(:user) } + + context 'using a public project' do + let(:project) { create(:empty_project, :public) } + + it 'returns true for a regular issue' do + issue = build(:issue, project: project) + + expect(issue).to be_readable_by(user) + end + + it 'returns false for a confidential issue' do + issue = build(:issue, project: project, confidential: true) + + expect(issue).not_to be_readable_by(user) + end + end + + context 'using an internal project' do + let(:project) { create(:empty_project, :internal) } + + context 'using an internal user' do + it 'returns true for a regular issue' do + issue = build(:issue, project: project) + + expect(issue).to be_readable_by(user) + end + + it 'returns false for a confidential issue' do + issue = build(:issue, :confidential, project: project) + + expect(issue).not_to be_readable_by(user) + end + end + + context 'using an external user' do + before do + allow(user).to receive(:external?).and_return(true) + end + + it 'returns false for a regular issue' do + issue = build(:issue, project: project) + + expect(issue).not_to be_readable_by(user) + end + + it 'returns false for a confidential issue' do + issue = build(:issue, :confidential, project: project) + + expect(issue).not_to be_readable_by(user) + end + end + end + + context 'using a private project' do + let(:project) { create(:empty_project, :private) } + + it 'returns false for a regular issue' do + issue = build(:issue, project: project) + + expect(issue).not_to be_readable_by(user) + end + + it 'returns false for a confidential issue' do + issue = build(:issue, :confidential, project: project) + + expect(issue).not_to be_readable_by(user) + end + + context 'when the user is the project owner' do + it 'returns true for a regular issue' do + issue = build(:issue, project: project) + + expect(issue).not_to be_readable_by(user) + end + + it 'returns true for a confidential issue' do + issue = build(:issue, :confidential, project: project) + + expect(issue).not_to be_readable_by(user) + end + end + end + end + + context 'with a regular user that is a team member' do + let(:user) { create(:user) } + let(:project) { create(:empty_project, :public) } + + context 'using a public project' do + before do + project.team << [user, Gitlab::Access::DEVELOPER] + end + + it 'returns true for a regular issue' do + issue = build(:issue, project: project) + + expect(issue).to be_readable_by(user) + end + + it 'returns true for a confidential issue' do + issue = build(:issue, :confidential, project: project) + + expect(issue).to be_readable_by(user) + end + end + + context 'using an internal project' do + let(:project) { create(:empty_project, :internal) } + + before do + project.team << [user, Gitlab::Access::DEVELOPER] + end + + it 'returns true for a regular issue' do + issue = build(:issue, project: project) + + expect(issue).to be_readable_by(user) + end + + it 'returns true for a confidential issue' do + issue = build(:issue, :confidential, project: project) + + expect(issue).to be_readable_by(user) + end + end + + context 'using a private project' do + let(:project) { create(:empty_project, :private) } + + before do + project.team << [user, Gitlab::Access::DEVELOPER] + end + + it 'returns true for a regular issue' do + issue = build(:issue, project: project) + + expect(issue).to be_readable_by(user) + end + + it 'returns true for a confidential issue' do + issue = build(:issue, :confidential, project: project) + + expect(issue).to be_readable_by(user) + end + end + end + + context 'with an admin user' do + let(:project) { create(:empty_project) } + let(:user) { create(:user, admin: true) } + + it 'returns true for a regular issue' do + issue = build(:issue, project: project) + + expect(issue).to be_readable_by(user) + end + + it 'returns true for a confidential issue' do + issue = build(:issue, :confidential, project: project) + + expect(issue).to be_readable_by(user) + end + end + end + + describe '#publicly_visible?' do + context 'using a public project' do + let(:project) { create(:empty_project, :public) } + + it 'returns true for a regular issue' do + issue = build(:issue, project: project) + + expect(issue).to be_publicly_visible + end + + it 'returns false for a confidential issue' do + issue = build(:issue, :confidential, project: project) + + expect(issue).not_to be_publicly_visible + end + end + + context 'using an internal project' do + let(:project) { create(:empty_project, :internal) } + + it 'returns false for a regular issue' do + issue = build(:issue, project: project) + + expect(issue).not_to be_publicly_visible + end + + it 'returns false for a confidential issue' do + issue = build(:issue, :confidential, project: project) + + expect(issue).not_to be_publicly_visible + end + end + + context 'using a private project' do + let(:project) { create(:empty_project, :private) } + + it 'returns false for a regular issue' do + issue = build(:issue, project: project) + + expect(issue).not_to be_publicly_visible + end + + it 'returns false for a confidential issue' do + issue = build(:issue, :confidential, project: project) + + expect(issue).not_to be_publicly_visible + end + end + end end -- cgit v1.2.1 From 6595d6aab078497293d657cd05d0af61c6cbda0b Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Thu, 28 Jul 2016 14:44:00 +0200 Subject: Bump gitlab_git to speedup DiffCollection iterations --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 4 ++-- app/views/projects/diffs/_stats.html.haml | 2 +- app/views/projects/diffs/_warning.html.haml | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2b04c15b827..5e181e865a6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -23,6 +23,7 @@ v 8.11.0 (unreleased) - The overhead of instrumented method calls has been reduced - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le) - Load project invited groups and members eagerly in `ProjectTeam#fetch_members` + - Bump gitlab_git to speedup DiffCollection iterations - Make branches sortable without push permission !5462 (winniehell) - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) - Add the `sprockets-es6` gem diff --git a/Gemfile b/Gemfile index a7d5e0e3e89..5f247abd2fc 100644 --- a/Gemfile +++ b/Gemfile @@ -53,7 +53,7 @@ gem 'browser', '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem 'gitlab_git', '~> 10.4.1' +gem 'gitlab_git', '~> 10.4.2' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes diff --git a/Gemfile.lock b/Gemfile.lock index 150a98bb7d0..7b4175ea824 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -278,7 +278,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) - gitlab_git (10.4.1) + gitlab_git (10.4.2) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -870,7 +870,7 @@ DEPENDENCIES github-linguist (~> 4.7.0) github-markup (~> 1.4) gitlab-flowdock-git-hook (~> 1.0.1) - gitlab_git (~> 10.4.1) + gitlab_git (~> 10.4.2) gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) diff --git a/app/views/projects/diffs/_stats.html.haml b/app/views/projects/diffs/_stats.html.haml index ea2a3e01277..e751dabdf99 100644 --- a/app/views/projects/diffs/_stats.html.haml +++ b/app/views/projects/diffs/_stats.html.haml @@ -2,7 +2,7 @@ .commit-stat-summary Showing = link_to '#', class: 'js-toggle-button' do - %strong #{pluralize(diff_files.count, "changed file")} + %strong #{pluralize(diff_files.size, "changed file")} with %strong.cgreen #{diff_files.sum(&:added_lines)} additions and diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml index 10fa1ddf2e5..295a1b62535 100644 --- a/app/views/projects/diffs/_warning.html.haml +++ b/app/views/projects/diffs/_warning.html.haml @@ -11,5 +11,5 @@ = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-sm" %p To preserve performance only - %strong #{diff_files.count} of #{diff_files.real_size} + %strong #{diff_files.size} of #{diff_files.real_size} files are displayed. -- cgit v1.2.1 From be9aa7f19474424991923f128053e2523fa166d8 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 26 Jul 2016 09:35:47 +0200 Subject: Add an URL field to Environments This MR adds a string (thus max 255 chars) field to the enviroments table to expose it later in other features. --- CHANGELOG | 1 + .../projects/environments_controller.rb | 23 +++++++--- app/models/environment.rb | 4 ++ app/views/projects/environments/_form.html.haml | 29 ++++++++++--- app/views/projects/environments/edit.html.haml | 6 +++ app/views/projects/environments/new.html.haml | 14 ++---- app/views/projects/environments/show.html.haml | 2 +- config/routes.rb | 2 +- ...160725083350_add_external_url_to_enviroments.rb | 12 ++++++ db/schema.rb | 3 +- .../projects/environments_controller_spec.rb | 50 ++++++++++++++++++++++ spec/factories/environments.rb | 1 + spec/features/environments_spec.rb | 4 +- spec/models/environment_spec.rb | 10 +++++ 14 files changed, 133 insertions(+), 28 deletions(-) create mode 100644 app/views/projects/environments/edit.html.haml create mode 100644 db/migrate/20160725083350_add_external_url_to_enviroments.rb create mode 100644 spec/controllers/projects/environments_controller_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 2b04c15b827..0292df068fa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 8.11.0 (unreleased) - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable - Optimize maximum user access level lookup in loading of notes - Add "No one can push" as an option for protected branches. !5081 + - Environments have an url to link to - Limit git rev-list output count to one in forced push check - Clean up unused routes (Josef Strzibny) - Add green outline to New Branch button. !5447 (winniehell) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 4b433796161..1f5c7506212 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -2,8 +2,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController layout 'project' before_action :authorize_read_environment! before_action :authorize_create_environment!, only: [:new, :create] - before_action :authorize_update_environment!, only: [:destroy] - before_action :environment, only: [:show, :destroy] + before_action :authorize_update_environment!, only: [:edit, :destroy] + before_action :environment, only: [:show, :edit, :update, :destroy] def index @environments = project.environments @@ -17,13 +17,24 @@ class Projects::EnvironmentsController < Projects::ApplicationController @environment = project.environments.new end + def edit + end + def create - @environment = project.environments.create(create_params) + @environment = project.environments.create(environment_params) if @environment.persisted? redirect_to namespace_project_environment_path(project.namespace, project, @environment) else - render 'new' + render :new + end + end + + def update + if @environment.update(environment_params) + redirect_to namespace_project_environment_path(project.namespace, project, @environment) + else + render :edit end end @@ -39,8 +50,8 @@ class Projects::EnvironmentsController < Projects::ApplicationController private - def create_params - params.require(:environment).permit(:name) + def environment_params + params.require(:environment).permit(:name, :external_url) end def environment diff --git a/app/models/environment.rb b/app/models/environment.rb index ac3a571a1f3..9eff0fdab03 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -10,6 +10,10 @@ class Environment < ActiveRecord::Base format: { with: Gitlab::Regex.environment_name_regex, message: Gitlab::Regex.environment_name_regex_message } + validates :external_url, + uniqueness: { scope: :project_id }, + length: { maximum: 255 } + def last_deployment deployments.last end diff --git a/app/views/projects/environments/_form.html.haml b/app/views/projects/environments/_form.html.haml index c07f4bd510c..6d040f5cfe6 100644 --- a/app/views/projects/environments/_form.html.haml +++ b/app/views/projects/environments/_form.html.haml @@ -1,7 +1,22 @@ -= form_for @environment, url: namespace_project_environments_path(@project.namespace, @project), html: { class: 'col-lg-9' } do |f| - = form_errors(@environment) - .form-group - = f.label :name, 'Name', class: 'label-light' - = f.text_field :name, required: true, class: 'form-control' - = f.submit 'Create environment', class: 'btn btn-create' - = link_to 'Cancel', namespace_project_environments_path(@project.namespace, @project), class: 'btn btn-cancel' +.row.prepend-top-default.append-bottom-default + .col-lg-3 + %h4.prepend-top-0 + Environments + %p + Environments allow you to track deployments of your application + = succeed "." do + = link_to "Read more about environments", help_page_path("ci/environments") + + = form_for [@project.namespace.becomes(Namespace), @project, @environment], html: { class: 'col-lg-9' } do |f| + = form_errors(@environment) + + .form-group + = f.label :name, 'Name', class: 'label-light' + = f.text_field :name, required: true, class: 'form-control' + .form-group + = f.label :external_url, 'External URL', class: 'label-light' + = f.url_field :external_url, class: 'form-control' + + .form-actions + = f.submit 'Save', class: 'btn btn-save' + = link_to 'Cancel', namespace_project_environments_path(@project.namespace, @project), class: 'btn btn-cancel' diff --git a/app/views/projects/environments/edit.html.haml b/app/views/projects/environments/edit.html.haml new file mode 100644 index 00000000000..6d1bdb9320f --- /dev/null +++ b/app/views/projects/environments/edit.html.haml @@ -0,0 +1,6 @@ +- page_title "Edit", @environment.name, "Environments" + +%h3.page-title + Edit environment +%hr += render 'form' diff --git a/app/views/projects/environments/new.html.haml b/app/views/projects/environments/new.html.haml index 89e06567196..e51667ade2d 100644 --- a/app/views/projects/environments/new.html.haml +++ b/app/views/projects/environments/new.html.haml @@ -1,12 +1,6 @@ - page_title 'New Environment' -.row.prepend-top-default.append-bottom-default - .col-lg-3 - %h4.prepend-top-0 - New Environment - %p - Environments allow you to track deployments of your application - = succeed "." do - = link_to "Read more about environments", help_page_path("ci/environments") - - = render 'form' +%h3.page-title + New environment +%hr += render 'form' diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index b8b1ce52a91..a07436ad7c9 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -6,10 +6,10 @@ .top-area .col-md-9 %h3.page-title= @environment.name.capitalize - .col-md-3 .nav-controls - if can?(current_user, :update_environment, @environment) + = link_to 'Edit', edit_namespace_project_environment_path(@project.namespace, @project, @environment), class: 'btn' = link_to 'Destroy', namespace_project_environment_path(@project.namespace, @project, @environment), data: { confirm: 'Are you sure you want to delete this environment?' }, class: 'btn btn-danger', method: :delete - if @deployments.blank? diff --git a/config/routes.rb b/config/routes.rb index 308d83af57e..ced204be7f7 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -741,7 +741,7 @@ Rails.application.routes.draw do end end - resources :environments, only: [:index, :show, :new, :create, :destroy] + resources :environments, constraints: { id: /\d+/ } resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do collection do diff --git a/db/migrate/20160725083350_add_external_url_to_enviroments.rb b/db/migrate/20160725083350_add_external_url_to_enviroments.rb new file mode 100644 index 00000000000..e887341159b --- /dev/null +++ b/db/migrate/20160725083350_add_external_url_to_enviroments.rb @@ -0,0 +1,12 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddExternalUrlToEnviroments < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column(:environments, :external_url, :string) + end +end diff --git a/db/schema.rb b/db/schema.rb index 2d2ae5fd840..4365af98962 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -427,9 +427,10 @@ ActiveRecord::Schema.define(version: 20160722221922) do create_table "environments", force: :cascade do |t| t.integer "project_id" - t.string "name", null: false + t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" + t.string "external_url" end add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb new file mode 100644 index 00000000000..b91a99d6b2e --- /dev/null +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -0,0 +1,50 @@ +require 'spec_helper' + +describe Projects::EnvironmentsController do + let(:environment) { create(:environment) } + let(:project) { environment.project } + let(:user) { create(:user) } + + before do + project.team << [user, :master] + + sign_in(user) + end + + render_views + + describe 'GET show' do + context 'with valid id' do + it 'responds with a status code 200' do + get :show, namespace_id: project.namespace, project_id: project, id: environment.id + + expect(response).to be_ok + end + end + + context 'with invalid id' do + it 'responds with a status code 404' do + get :show, namespace_id: project.namespace, project_id: project, id: 12345 + + expect(response).to be_not_found + end + end + end + + describe 'GET edit' do + it 'responds with a status code 200' do + get :edit, namespace_id: project.namespace, project_id: project, id: environment.id + + expect(response).to be_ok + end + end + + describe 'PATCH #update' do + it 'responds with a 302' do + patch :update, namespace_id: project.namespace, project_id: + project, id: environment.id, environment: { external_url: 'https://git.gitlab.com' } + + expect(response).to have_http_status(302) + end + end +end diff --git a/spec/factories/environments.rb b/spec/factories/environments.rb index 07265c26ca3..846cccfc7fa 100644 --- a/spec/factories/environments.rb +++ b/spec/factories/environments.rb @@ -3,5 +3,6 @@ FactoryGirl.define do sequence(:name) { |n| "environment#{n}" } project factory: :empty_project + sequence(:external_url) { |n| "https://env#{n}.example.gitlab.com" } end end diff --git a/spec/features/environments_spec.rb b/spec/features/environments_spec.rb index a7d9f2a0c72..fcd41b38413 100644 --- a/spec/features/environments_spec.rb +++ b/spec/features/environments_spec.rb @@ -140,7 +140,7 @@ feature 'Environments', feature: true do context 'for valid name' do before do fill_in('Name', with: 'production') - click_on 'Create environment' + click_on 'Save' end scenario 'does create a new pipeline' do @@ -151,7 +151,7 @@ feature 'Environments', feature: true do context 'for invalid name' do before do fill_in('Name', with: 'name with spaces') - click_on 'Create environment' + click_on 'Save' end scenario 'does show errors' do diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 7629af6a570..6c11cfc4c9b 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -11,4 +11,14 @@ describe Environment, models: true do it { is_expected.to validate_presence_of(:name) } it { is_expected.to validate_uniqueness_of(:name).scoped_to(:project_id) } it { is_expected.to validate_length_of(:name).is_within(0..255) } + + it { is_expected.to validate_length_of(:external_url).is_within(0..255) } + + # To circumvent a not null violation of the name column: + # https://github.com/thoughtbot/shoulda-matchers/issues/336 + it 'validates uniqueness of :external_url' do + create(:environment) + + is_expected.to validate_uniqueness_of(:external_url).scoped_to(:project_id) + end end -- cgit v1.2.1 From 84cd2120952e7ee4095cb4b5d7c959f2c11610c5 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 26 Jul 2016 09:37:02 +0200 Subject: Add API support for environments --- doc/api/enviroments.md | 118 +++++++++++++++++++++++++++++++++ lib/api/api.rb | 1 + lib/api/entities.rb | 4 ++ lib/api/environments.rb | 87 ++++++++++++++++++++++++ spec/requests/api/environments_spec.rb | 103 ++++++++++++++++++++++++++++ 5 files changed, 313 insertions(+) create mode 100644 doc/api/enviroments.md create mode 100644 lib/api/environments.rb create mode 100644 spec/requests/api/environments_spec.rb diff --git a/doc/api/enviroments.md b/doc/api/enviroments.md new file mode 100644 index 00000000000..c4b844fe77e --- /dev/null +++ b/doc/api/enviroments.md @@ -0,0 +1,118 @@ +# Environments + +## List environments + +Get all environments for a given project. + +``` +GET /projects/:id/environments +``` + +| Attribute | Type | Required | Description | +| --------- | ------- | -------- | --------------------- | +| `id` | integer | yes | The ID of the project | + +```bash +curl -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/projects/1/environments +``` + +Example response: + +```json +[ + { + "id": 1, + "name": "Env1", + "external_url": "https://env1.example.gitlab.com" + } +] +``` + +## Create a new environment + +Creates a new environment with the given name and external_url. + +It returns 200 if the environment was successfully created, 400 for wrong parameters +and 409 if the environment already exists. + +``` +POST /projects/:id/environment +``` + +| Attribute | Type | Required | Description | +| ------------- | ------- | -------- | ---------------------------- | +| `id` | integer | yes | The ID of the project | +| `name` | string | yes | The name of the environment | +| `external_url` | string | yes | Place to link to for this environment | + +```bash +curl --data "name=deploy&external_url=https://deploy.example.gitlab.com" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments" +``` + +Example response: + +```json +{ + "id": 1, + "name": "deploy", + "external_url": "https://deploy.example.gitlab.com" +} +``` + +## Delete an environment + +It returns 200 if the environment was successfully deleted, and 404 if the environment does not exist. + +``` +DELETE /projects/:id/environments/:environment_id +``` + +| Attribute | Type | Required | Description | +| --------- | ------- | -------- | --------------------- | +| `id` | integer | yes | The ID of the project | +| `environment_id` | integer | yes | The ID of the environment | + +```bash +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1" +``` + +Example response: + +```json +{ + "id": 1, + "name": "deploy", + "external_url": "https://deploy.example.gitlab.com" +} +``` + +## Edit an existing environment + +Updates an existing environments name and/or external_url. + +It returns 200 if the label was successfully updated, In case of an error, an additional error message is returned. + +``` +PUT /projects/:id/environments/:environments_id +``` + +| Attribute | Type | Required | Description | +| --------------- | ------- | --------------------------------- | ------------------------------- | +| `id` | integer | yes | The ID of the project | +| `environment_id` | integer | yes | The ID of the environment | The ID of the environment | +| `name` | string | no | The new name of the environment | +| `external_url` | string | no | The new external_url | + +```bash +curl -X PUT --data "name=staging&external_url=https://staging.example.gitlab.com" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1" +``` + +Example response: + +```json +{ + "id": 1, + "name": "staging", + "external_url": "https://staging.example.gitlab.com" +} +``` diff --git a/lib/api/api.rb b/lib/api/api.rb index 3d7d67510a8..9c960d74495 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -32,6 +32,7 @@ module API mount ::API::CommitStatuses mount ::API::Commits mount ::API::DeployKeys + mount ::API::Environments mount ::API::Files mount ::API::GroupMembers mount ::API::Groups diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 4eb95d8a215..3e21b7a0b8a 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -496,6 +496,10 @@ module API expose :key, :value end + class Environment < Grape::Entity + expose :id, :name, :external_url + end + class RepoLicense < Grape::Entity expose :key, :name, :nickname expose :featured, as: :popular diff --git a/lib/api/environments.rb b/lib/api/environments.rb new file mode 100644 index 00000000000..66a047f72fc --- /dev/null +++ b/lib/api/environments.rb @@ -0,0 +1,87 @@ +module API + # Environments RESTfull API endpoints + class Environments < Grape::API + before { authenticate! } + + resource :projects do + # Get all labels of the project + # + # Parameters: + # id (required) - The ID of a project + # Example Request: + # GET /projects/:id/environments + get ':id/environments' do + authorize! :read_environment, user_project + + present paginate(user_project.environments), with: Entities::Environment + end + + # Creates a new environment + # + # Parameters: + # id (required) - The ID of a project + # name (required) - The name of the environment to be created + # external_url (optional) - URL on which this deployment is viewable + # + # Example Request: + # POST /projects/:id/labels + post ':id/environments' do + authorize! :create_environment, user_project + required_attributes! [:name] + + attrs = attributes_for_keys [:name, :external_url] + environment = user_project.environments.find_by(name: attrs[:name]) + + conflict!('Environment already exists') if environment + + environment = user_project.environments.create(attrs) + + if environment.valid? + present environment, with: Entities::Environment + else + render_validation_error!(environment) + end + end + + # Deletes an existing environment + # + # Parameters: + # id (required) - The ID of a project + # environment_id (required) - The name of the environment to be deleted + # + # Example Request: + # DELETE /projects/:id/environments/:environment_id + delete ':id/environments/:environment_id' do + authorize! :admin_environment, user_project + + environment = user_project.environments.find(params[:environment_id]) + + present environment.destroy, with: Entities::Environment + end + + # Updates an existing environment + # + # Parameters: + # id (required) - The ID of a project + # environment_id (required) - The ID of the environment + # name (optional) - The name of the label to be deleted + # external_url (optional) - The new name of the label + # + # Example Request: + # PUT /projects/:id/environments/:environment_id + put ':id/environments/:environment_id' do + authorize! :update_environment, user_project + + environment = user_project.environments.find(params[:environment_id]) + + attrs = attributes_for_keys [:name, :external_url] + + if environment.update(attrs) + present environment, with: Entities::Environment + else + render_validation_error!(environment) + end + end + end + end +end diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb new file mode 100644 index 00000000000..822139dbf3b --- /dev/null +++ b/spec/requests/api/environments_spec.rb @@ -0,0 +1,103 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + let(:user) { create(:user) } + let(:non_member) { create(:user) } + let(:project) { create(:project, :private, creator_id: user.id, namespace: user.namespace) } + let!(:environment) { create(:environment, project: project) } + + before do + project.team << [user, :master] + end + + describe 'GET /projects/:id/environments' do + context 'as member of the project' do + it 'should return project labels' do + get api("/projects/#{project.id}/environments", user) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(1) + expect(json_response.first['name']).to eq(environment.name) + expect(json_response.first['external_url']).to eq(environment.external_url) + end + end + + context 'as non member' do + it 'should return a 404 status code' do + get api("/projects/#{project.id}/environments", non_member) + + expect(response).to have_http_status(404) + end + end + end + + describe 'POST /projects/:id/labels' do + context 'as a member' do + it 'creates a environment with valid params' do + post api("/projects/#{project.id}/environments", user), name: "mepmep" + + expect(response).to have_http_status(201) + expect(json_response['name']).to eq('mepmep') + expect(json_response['external']).to be nil + end + + it 'requires name to be passed' do + post api("/projects/#{project.id}/environments", user), external_url: 'test.gitlab.com' + + expect(response).to have_http_status(400) + end + + it 'should return 409 if environment already exists' do + post api("/projects/#{project.id}/environments", user), name: environment.name + + expect(response).to have_http_status(409) + expect(json_response['message']).to eq('Environment already exists') + end + end + + context 'a non member' do + it 'rejects the request' do + post api("/projects/#{project.id}/environments", non_member) + + expect(response).to have_http_status(404) + end + end + end + + describe 'DELETE /projects/:id/environments/:environment_id' do + context 'as a master' do + it 'should return 200 for an existing environment' do + delete api("/projects/#{project.id}/environments/#{environment.id}", user) + + expect(response).to have_http_status(200) + end + + it 'should return 404 for non existing id' do + delete api("/projects/#{project.id}/environments/12345", user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Not found') + end + end + end + + describe 'PUT /projects/:id/environments/:environment_id' do + it 'should return 200 if name and external_url are changed' do + put api("/projects/#{project.id}/environments/#{environment.id}", user), + name: 'Mepmep', external_url: 'mepmep.whatever.ninja' + + expect(response).to have_http_status(200) + expect(json_response['name']).to eq('Mepmep') + expect(json_response['external_url']).to eq('mepmep.whatever.ninja') + end + + it 'should return 404 if the environment does not exist' do + put api("/projects/#{project.id}/environments/12345", user) + + expect(response).to have_http_status(404) + end + end +end -- cgit v1.2.1 From 76e9b68439510af5c783a81b93944f1c8d96d150 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 26 Jul 2016 14:19:37 +0200 Subject: Incorporate feedback --- app/models/environment.rb | 10 ++++++++- ...160725083350_add_external_url_to_enviroments.rb | 3 --- db/schema.rb | 2 +- doc/api/enviroments.md | 7 +++---- lib/api/environments.rb | 5 +---- .../projects/environments_controller_spec.rb | 24 ++++++++++++++-------- spec/models/environment_spec.rb | 8 ++++++++ spec/requests/api/environments_spec.rb | 15 +++++++------- 8 files changed, 45 insertions(+), 29 deletions(-) diff --git a/app/models/environment.rb b/app/models/environment.rb index 9eff0fdab03..baed106e8c8 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -3,6 +3,8 @@ class Environment < ActiveRecord::Base has_many :deployments + before_validation :nullify_external_url + validates :name, presence: true, uniqueness: { scope: :project_id }, @@ -12,9 +14,15 @@ class Environment < ActiveRecord::Base validates :external_url, uniqueness: { scope: :project_id }, - length: { maximum: 255 } + length: { maximum: 255 }, + allow_nil: true, + addressable_url: true def last_deployment deployments.last end + + def nullify_external_url + self.external_url = nil if self.external_url.blank? + end end diff --git a/db/migrate/20160725083350_add_external_url_to_enviroments.rb b/db/migrate/20160725083350_add_external_url_to_enviroments.rb index e887341159b..21a8abd310b 100644 --- a/db/migrate/20160725083350_add_external_url_to_enviroments.rb +++ b/db/migrate/20160725083350_add_external_url_to_enviroments.rb @@ -1,6 +1,3 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - class AddExternalUrlToEnviroments < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers diff --git a/db/schema.rb b/db/schema.rb index 4365af98962..5b35a528e71 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160722221922) do +ActiveRecord::Schema.define(version: 20160726093600) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/doc/api/enviroments.md b/doc/api/enviroments.md index c4b844fe77e..16bf2627fef 100644 --- a/doc/api/enviroments.md +++ b/doc/api/enviroments.md @@ -32,8 +32,7 @@ Example response: Creates a new environment with the given name and external_url. -It returns 200 if the environment was successfully created, 400 for wrong parameters -and 409 if the environment already exists. +It returns 200 if the environment was successfully created, 400 for wrong parameters. ``` POST /projects/:id/environment @@ -43,7 +42,7 @@ POST /projects/:id/environment | ------------- | ------- | -------- | ---------------------------- | | `id` | integer | yes | The ID of the project | | `name` | string | yes | The name of the environment | -| `external_url` | string | yes | Place to link to for this environment | +| `external_url` | string | no | Place to link to for this environment | ```bash curl --data "name=deploy&external_url=https://deploy.example.gitlab.com" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environments" @@ -88,7 +87,7 @@ Example response: ## Edit an existing environment -Updates an existing environments name and/or external_url. +Updates an existing environment's name and/or external_url. It returns 200 if the label was successfully updated, In case of an error, an additional error message is returned. diff --git a/lib/api/environments.rb b/lib/api/environments.rb index 66a047f72fc..532baec42c7 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -30,9 +30,6 @@ module API required_attributes! [:name] attrs = attributes_for_keys [:name, :external_url] - environment = user_project.environments.find_by(name: attrs[:name]) - - conflict!('Environment already exists') if environment environment = user_project.environments.create(attrs) @@ -52,7 +49,7 @@ module API # Example Request: # DELETE /projects/:id/environments/:environment_id delete ':id/environments/:environment_id' do - authorize! :admin_environment, user_project + authorize! :update_environment, user_project environment = user_project.environments.find(params[:environment_id]) diff --git a/spec/controllers/projects/environments_controller_spec.rb b/spec/controllers/projects/environments_controller_spec.rb index b91a99d6b2e..768105cae95 100644 --- a/spec/controllers/projects/environments_controller_spec.rb +++ b/spec/controllers/projects/environments_controller_spec.rb @@ -11,12 +11,10 @@ describe Projects::EnvironmentsController do sign_in(user) end - render_views - describe 'GET show' do context 'with valid id' do it 'responds with a status code 200' do - get :show, namespace_id: project.namespace, project_id: project, id: environment.id + get :show, environment_params expect(response).to be_ok end @@ -24,16 +22,18 @@ describe Projects::EnvironmentsController do context 'with invalid id' do it 'responds with a status code 404' do - get :show, namespace_id: project.namespace, project_id: project, id: 12345 + params = environment_params + params[:id] = 12345 + get :show, params - expect(response).to be_not_found + expect(response).to have_http_status(404) end end end describe 'GET edit' do it 'responds with a status code 200' do - get :edit, namespace_id: project.namespace, project_id: project, id: environment.id + get :edit, environment_params expect(response).to be_ok end @@ -41,10 +41,18 @@ describe Projects::EnvironmentsController do describe 'PATCH #update' do it 'responds with a 302' do - patch :update, namespace_id: project.namespace, project_id: - project, id: environment.id, environment: { external_url: 'https://git.gitlab.com' } + patch_params = environment_params.merge(environment: { external_url: 'https://git.gitlab.com' }) + patch :update, patch_params expect(response).to have_http_status(302) end end + + def environment_params + { + namespace_id: project.namespace, + project_id: project, + id: environment.id + } + end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 6c11cfc4c9b..ef2148be1bd 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -21,4 +21,12 @@ describe Environment, models: true do is_expected.to validate_uniqueness_of(:external_url).scoped_to(:project_id) end + + describe '#nullify_external_url' do + it 'replaces a blank url with nil' do + env = build(:environment, external_url: "") + + expect(env.save).to be true + end + end end diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index 822139dbf3b..b731c58a206 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -5,7 +5,7 @@ describe API::API, api: true do let(:user) { create(:user) } let(:non_member) { create(:user) } - let(:project) { create(:project, :private, creator_id: user.id, namespace: user.namespace) } + let(:project) { create(:project, :private, namespace: user.namespace) } let!(:environment) { create(:environment, project: project) } before do @@ -14,7 +14,7 @@ describe API::API, api: true do describe 'GET /projects/:id/environments' do context 'as member of the project' do - it 'should return project labels' do + it 'should return project environments' do get api("/projects/#{project.id}/environments", user) expect(response).to have_http_status(200) @@ -34,7 +34,7 @@ describe API::API, api: true do end end - describe 'POST /projects/:id/labels' do + describe 'POST /projects/:id/environments' do context 'as a member' do it 'creates a environment with valid params' do post api("/projects/#{project.id}/environments", user), name: "mepmep" @@ -50,11 +50,10 @@ describe API::API, api: true do expect(response).to have_http_status(400) end - it 'should return 409 if environment already exists' do + it 'should return 400 if environment already exists' do post api("/projects/#{project.id}/environments", user), name: environment.name - expect(response).to have_http_status(409) - expect(json_response['message']).to eq('Environment already exists') + expect(response).to have_http_status(400) end end @@ -87,11 +86,11 @@ describe API::API, api: true do describe 'PUT /projects/:id/environments/:environment_id' do it 'should return 200 if name and external_url are changed' do put api("/projects/#{project.id}/environments/#{environment.id}", user), - name: 'Mepmep', external_url: 'mepmep.whatever.ninja' + name: 'Mepmep', external_url: 'https://mepmep.whatever.ninja' expect(response).to have_http_status(200) expect(json_response['name']).to eq('Mepmep') - expect(json_response['external_url']).to eq('mepmep.whatever.ninja') + expect(json_response['external_url']).to eq('https://mepmep.whatever.ninja') end it 'should return 404 if the environment does not exist' do -- cgit v1.2.1 From d05af7b7c6975ae66808ed6676a1b947c7abe244 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Thu, 28 Jul 2016 07:09:40 +0200 Subject: Check for Ci::Build artifacts at database level --- CHANGELOG | 1 + app/models/ci/build.rb | 1 + app/views/projects/ci/pipelines/_pipeline.html.haml | 2 +- spec/factories/ci/builds.rb | 16 ++++++++++++++++ spec/features/pipelines_spec.rb | 10 ++++++++++ 5 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5e181e865a6..88f37735c69 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -25,6 +25,7 @@ v 8.11.0 (unreleased) - Load project invited groups and members eagerly in `ProjectTeam#fetch_members` - Bump gitlab_git to speedup DiffCollection iterations - Make branches sortable without push permission !5462 (winniehell) + - Check for Ci::Build artifacts at database level on pipeline partial - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) - Add the `sprockets-es6` gem - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index aac78d75f57..08f396210c9 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -13,6 +13,7 @@ module Ci scope :unstarted, ->() { where(runner_id: nil) } scope :ignore_failures, ->() { where(allow_failure: false) } scope :with_artifacts, ->() { where.not(artifacts_file: [nil, '']) } + scope :with_artifacts_not_expired, ->() { with_artifacts.where('artifacts_expire_at IS NULL OR artifacts_expire_at > ?', Time.now) } scope :with_expired_artifacts, ->() { with_artifacts.where('artifacts_expire_at < ?', Time.now) } scope :last_month, ->() { where('created_at > ?', Date.today - 1.month) } scope :manual_actions, ->() { where(when: :manual) } diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 2f7d54f0bdd..558c35553da 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -57,7 +57,7 @@ %td.pipeline-actions .controls.hidden-xs.pull-right - - artifacts = pipeline.builds.latest.select { |b| b.artifacts? } + - artifacts = pipeline.builds.latest.with_artifacts_not_expired - actions = pipeline.manual_actions - if artifacts.present? || actions.any? .btn-group.inline diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 5e19e403c6b..1b32d560b16 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -90,5 +90,21 @@ FactoryGirl.define do build.save! end end + + trait :artifacts_expired do + after(:create) do |build, _| + build.artifacts_file = + fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts.zip'), + 'application/zip') + + build.artifacts_metadata = + fixture_file_upload(Rails.root.join('spec/fixtures/ci_build_artifacts_metadata.gz'), + 'application/x-gzip') + + build.artifacts_expire_at = 1.minute.ago + + build.save! + end + end end end diff --git a/spec/features/pipelines_spec.rb b/spec/features/pipelines_spec.rb index 7f861db1969..377a9aba60d 100644 --- a/spec/features/pipelines_spec.rb +++ b/spec/features/pipelines_spec.rb @@ -116,9 +116,19 @@ describe "Pipelines" do it { expect(page).to have_link(with_artifacts.name) } end + context 'with artifacts expired' do + let!(:with_artifacts_expired) { create(:ci_build, :artifacts_expired, :success, pipeline: pipeline, name: 'rspec', stage: 'test') } + + before { visit namespace_project_pipelines_path(project.namespace, project) } + + it { expect(page).not_to have_selector('.build-artifacts') } + end + context 'without artifacts' do let!(:without_artifacts) { create(:ci_build, :success, pipeline: pipeline, name: 'rspec', stage: 'test') } + before { visit namespace_project_pipelines_path(project.namespace, project) } + it { expect(page).not_to have_selector('.build-artifacts') } end end -- cgit v1.2.1 From a42cce1b966046c21ec48b18435d38e68a20f7fa Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 29 Jul 2016 12:30:38 +0200 Subject: Improve code, remove unused validator, improve names --- lib/gitlab/ci/config/node/cache.rb | 4 +- lib/gitlab/ci/config/node/commands.rb | 12 +++-- lib/gitlab/ci/config/node/configurable.rb | 7 ++- lib/gitlab/ci/config/node/global.rb | 4 +- lib/gitlab/ci/config/node/job.rb | 4 +- lib/gitlab/ci/config/node/null.rb | 2 +- lib/gitlab/ci/config/node/trigger.rb | 26 +++++++++++ lib/gitlab/ci/config/node/undefined.rb | 6 +-- lib/gitlab/ci/config/node/validators.rb | 9 ---- lib/gitlab/ci/config/node/while.rb | 26 ----------- spec/lib/gitlab/ci/config/node/artifacts_spec.rb | 2 +- spec/lib/gitlab/ci/config/node/trigger_spec.rb | 56 ++++++++++++++++++++++++ spec/lib/gitlab/ci/config/node/while_spec.rb | 56 ------------------------ 13 files changed, 100 insertions(+), 114 deletions(-) create mode 100644 lib/gitlab/ci/config/node/trigger.rb delete mode 100644 lib/gitlab/ci/config/node/while.rb create mode 100644 spec/lib/gitlab/ci/config/node/trigger_spec.rb delete mode 100644 spec/lib/gitlab/ci/config/node/while_spec.rb diff --git a/lib/gitlab/ci/config/node/cache.rb b/lib/gitlab/ci/config/node/cache.rb index 21d96b220b8..b4bda2841ac 100644 --- a/lib/gitlab/ci/config/node/cache.rb +++ b/lib/gitlab/ci/config/node/cache.rb @@ -8,8 +8,10 @@ module Gitlab class Cache < Entry include Configurable + ALLOWED_KEYS = %i[key untracked paths] + validations do - validates :config, allowed_keys: %i[key untracked paths] + validates :config, allowed_keys: ALLOWED_KEYS end node :key, Node::Key, diff --git a/lib/gitlab/ci/config/node/commands.rb b/lib/gitlab/ci/config/node/commands.rb index f7e6950001e..d7657ae314b 100644 --- a/lib/gitlab/ci/config/node/commands.rb +++ b/lib/gitlab/ci/config/node/commands.rb @@ -11,22 +11,20 @@ module Gitlab validations do include LegacyValidationHelpers - validate :string_or_array_of_strings - - def string_or_array_of_strings - unless config_valid? + validate do + unless string_or_array_of_strings?(config) errors.add(:config, 'should be a string or an array of strings') end end - def config_valid? - validate_string(config) || validate_array_of_strings(config) + def string_or_array_of_strings?(field) + validate_string(field) || validate_array_of_strings(field) end end def value - [@config].flatten + Array(@config) end end end diff --git a/lib/gitlab/ci/config/node/configurable.rb b/lib/gitlab/ci/config/node/configurable.rb index 93a9a253322..aedc28fe1d0 100644 --- a/lib/gitlab/ci/config/node/configurable.rb +++ b/lib/gitlab/ci/config/node/configurable.rb @@ -56,10 +56,9 @@ module Gitlab end define_method("#{symbol}_value") do - if @entries[symbol] - return unless @entries[symbol].valid? - @entries[symbol].value - end + return unless @entries[symbol] && @entries[symbol].valid? + + @entries[symbol].value end alias_method symbol.to_sym, "#{symbol}_value".to_sym diff --git a/lib/gitlab/ci/config/node/global.rb b/lib/gitlab/ci/config/node/global.rb index b545b78a940..ccd539fb003 100644 --- a/lib/gitlab/ci/config/node/global.rb +++ b/lib/gitlab/ci/config/node/global.rb @@ -42,7 +42,7 @@ module Gitlab super compose_jobs! - compose_stages! + compose_deprecated_entries! end def compose_jobs! @@ -54,7 +54,7 @@ module Gitlab @entries[:jobs] = factory.create! end - def compose_stages! + def compose_deprecated_entries! ## # Deprecated `:types` key workaround - if types are defined and # stages are not defined we use types definition as stages. diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index ace79d829f2..e84737acbb9 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -66,10 +66,10 @@ module Gitlab node :services, Services, description: 'Services that will be used to execute this job.' - node :only, While, + node :only, Trigger, description: 'Refs policy this job will be executed for.' - node :except, While, + node :except, Trigger, description: 'Refs policy this job will be executed for.' node :variables, Variables, diff --git a/lib/gitlab/ci/config/node/null.rb b/lib/gitlab/ci/config/node/null.rb index 880d29f663d..88a5f53f13c 100644 --- a/lib/gitlab/ci/config/node/null.rb +++ b/lib/gitlab/ci/config/node/null.rb @@ -3,7 +3,7 @@ module Gitlab class Config module Node ## - # This class represents an undefined and unspecified node. + # This class represents an undefined node. # # Implements the Null Object pattern. # diff --git a/lib/gitlab/ci/config/node/trigger.rb b/lib/gitlab/ci/config/node/trigger.rb new file mode 100644 index 00000000000..d8b31975088 --- /dev/null +++ b/lib/gitlab/ci/config/node/trigger.rb @@ -0,0 +1,26 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents a trigger policy for the job. + # + class Trigger < Entry + include Validatable + + validations do + include LegacyValidationHelpers + + validate :array_of_strings_or_regexps + + def array_of_strings_or_regexps + unless validate_array_of_strings_or_regexps(config) + errors.add(:config, 'should be an array of strings or regexps') + end + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/undefined.rb b/lib/gitlab/ci/config/node/undefined.rb index 84dab61e7e9..45fef8c3ae5 100644 --- a/lib/gitlab/ci/config/node/undefined.rb +++ b/lib/gitlab/ci/config/node/undefined.rb @@ -3,16 +3,12 @@ module Gitlab class Config module Node ## - # This class represents an undefined and unspecified entry node. + # This class represents an unspecified entry node. # # It decorates original entry adding method that indicates it is # unspecified. # class Undefined < SimpleDelegator - def initialize(entry) - super - end - def specified? false end diff --git a/lib/gitlab/ci/config/node/validators.rb b/lib/gitlab/ci/config/node/validators.rb index 23d5faf6f07..e20908ad3cb 100644 --- a/lib/gitlab/ci/config/node/validators.rb +++ b/lib/gitlab/ci/config/node/validators.rb @@ -44,15 +44,6 @@ module Gitlab end end - class RequiredValidator < ActiveModel::EachValidator - def validate_each(record, attribute, value) - if value.nil? - raise Entry::InvalidError, - "Entry needs #{attribute} attribute set internally." - end - end - end - class KeyValidator < ActiveModel::EachValidator include LegacyValidationHelpers diff --git a/lib/gitlab/ci/config/node/while.rb b/lib/gitlab/ci/config/node/while.rb deleted file mode 100644 index 84d4352624d..00000000000 --- a/lib/gitlab/ci/config/node/while.rb +++ /dev/null @@ -1,26 +0,0 @@ -module Gitlab - module Ci - class Config - module Node - ## - # Entry that represents a ref and trigger policy for the job. - # - class While < Entry - include Validatable - - validations do - include LegacyValidationHelpers - - validate :array_of_strings_or_regexps - - def array_of_strings_or_regexps - unless validate_array_of_strings_or_regexps(config) - errors.add(:config, 'should be an array of strings or regexps') - end - end - end - end - end - end - end -end diff --git a/spec/lib/gitlab/ci/config/node/artifacts_spec.rb b/spec/lib/gitlab/ci/config/node/artifacts_spec.rb index beed29b18ae..c09a0a9c793 100644 --- a/spec/lib/gitlab/ci/config/node/artifacts_spec.rb +++ b/spec/lib/gitlab/ci/config/node/artifacts_spec.rb @@ -8,7 +8,7 @@ describe Gitlab::Ci::Config::Node::Artifacts do let(:config) { { paths: %w[public/] } } describe '#value' do - it 'returns image string' do + it 'returns artifacs configuration' do expect(entry.value).to eq config end end diff --git a/spec/lib/gitlab/ci/config/node/trigger_spec.rb b/spec/lib/gitlab/ci/config/node/trigger_spec.rb new file mode 100644 index 00000000000..a4a3e36754e --- /dev/null +++ b/spec/lib/gitlab/ci/config/node/trigger_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe Gitlab::Ci::Config::Node::Trigger do + let(:entry) { described_class.new(config) } + + describe 'validations' do + context 'when entry config value is valid' do + context 'when config is a branch or tag name' do + let(:config) { %w[master feature/branch] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + + describe '#value' do + it 'returns key value' do + expect(entry.value).to eq config + end + end + end + + context 'when config is a regexp' do + let(:config) { ['/^issue-.*$/'] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + + context 'when config is a special keyword' do + let(:config) { %w[tags triggers branches] } + + describe '#valid?' do + it 'is valid' do + expect(entry).to be_valid + end + end + end + end + + context 'when entry value is not valid' do + let(:config) { [1] } + + describe '#errors' do + it 'saves errors' do + expect(entry.errors) + .to include 'trigger config should be an array of strings or regexps' + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/config/node/while_spec.rb b/spec/lib/gitlab/ci/config/node/while_spec.rb deleted file mode 100644 index aac2ed7b3db..00000000000 --- a/spec/lib/gitlab/ci/config/node/while_spec.rb +++ /dev/null @@ -1,56 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Ci::Config::Node::While do - let(:entry) { described_class.new(config) } - - describe 'validations' do - context 'when entry config value is valid' do - context 'when config is a branch or tag name' do - let(:config) { %w[master feature/branch] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - - describe '#value' do - it 'returns key value' do - expect(entry.value).to eq config - end - end - end - - context 'when config is a regexp' do - let(:config) { ['/^issue-.*$/'] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - - context 'when config is a special keyword' do - let(:config) { %w[tags triggers branches] } - - describe '#valid?' do - it 'is valid' do - expect(entry).to be_valid - end - end - end - end - - context 'when entry value is not valid' do - let(:config) { [1] } - - describe '#errors' do - it 'saves errors' do - expect(entry.errors) - .to include 'while config should be an array of strings or regexps' - end - end - end - end -end -- cgit v1.2.1 From 7658f31a621f6ebc97af5c21802854ed56b0bd0f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 29 Jul 2016 07:28:34 -0700 Subject: Clarify backup_keep_time config parameter with S3 Discussed in gitlab-org/omnibus-gitlab#1453 [ci skip] --- doc/raketasks/backup_restore.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index fa976134341..5fa96736d59 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -382,6 +382,13 @@ backups using all your disk space. To do this add the following lines to gitlab_rails['backup_keep_time'] = 604800 ``` +Note that the `backup_keep_time` configuration option only manages local +files. GitLab does not automatically prune old files stored in a third-party +object storage (e.g. AWS S3) because the user may not have permission to list +and delete files. We recommend that you configure the appropriate retention +policy for your object storage. For example, you can configure [the S3 backup +policy here as described here](http://stackoverflow.com/questions/37553070/gitlab-omnibus-delete-backup-from-amazon-s3). + NOTE: This cron job does not [backup your omnibus-gitlab configuration](#backup-and-restore-omnibus-gitlab-configuration) or [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079). ## Alternative backup strategies -- cgit v1.2.1 From 60529e021667194c402d4f6e85a83e02a8bb9f75 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 29 Jul 2016 11:14:53 -0700 Subject: Properly abort a merge when merge conflicts occur If somehow a user attempted to accept a merge request that had conflicts (e.g. the "Accept Merge Request" button or the MR itself was not updated), `MergeService` did not properly detect that a conflict occurred. It would assume that the MR went through without any issues and close the MR as though everything was fine. This could cause data loss if the source branch were removed. Closes #20425 --- CHANGELOG | 1 + app/services/merge_requests/merge_service.rb | 8 +++++++- spec/services/merge_requests/merge_service_spec.rb | 11 +++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 31a7eae90b9..6f7cf029440 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -38,6 +38,7 @@ v 8.11.0 (unreleased) v 8.10.3 (unreleased) - Fix hooks missing on imported GitLab projects + - Properly abort a merge when merge conflicts occur v 8.10.2 - User can now search branches by name. !5144 diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 0dac0614141..b037780c431 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -35,7 +35,13 @@ module MergeRequests } commit_id = repository.merge(current_user, merge_request, options) - merge_request.update(merge_commit_sha: commit_id) + + if commit_id + merge_request.update(merge_commit_sha: commit_id) + else + merge_request.update(merge_error: 'Conflicts detected during merge') + false + end rescue GitHooksService::PreReceiveError => e merge_request.update(merge_error: e.message) false diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index f5bf3c1e367..8ffebcac698 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -75,6 +75,17 @@ describe MergeRequests::MergeService, services: true do expect(merge_request.merge_error).to eq("error") end + + it 'aborts if there is a merge conflict' do + allow_any_instance_of(Repository).to receive(:merge).and_return(false) + allow(service).to receive(:execute_hooks) + + service.execute(merge_request) + + expect(merge_request.open?).to be_truthy + expect(merge_request.merge_commit_sha).to be_nil + expect(merge_request.merge_error).to eq("Conflicts detected during merge") + end end end end -- cgit v1.2.1 From 1b72256fa14e65256d78347f81b289d43c44e991 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Fri, 29 Jul 2016 12:14:36 +0200 Subject: Use Grape DSL for environment endpoints Also a couple of minor edits for this branch are included --- config/routes.rb | 2 +- doc/api/enviroments.md | 50 +++++++++---------- lib/api/api.rb | 4 ++ lib/api/environments.rb | 91 ++++++++++++++++------------------ spec/models/environment_spec.rb | 1 + spec/requests/api/environments_spec.rb | 57 ++++++++++++--------- 6 files changed, 108 insertions(+), 97 deletions(-) diff --git a/config/routes.rb b/config/routes.rb index ced204be7f7..371eb4bee7f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -741,7 +741,7 @@ Rails.application.routes.draw do end end - resources :environments, constraints: { id: /\d+/ } + resources :environments resources :builds, only: [:index, :show], constraints: { id: /\d+/ } do collection do diff --git a/doc/api/enviroments.md b/doc/api/enviroments.md index 16bf2627fef..1e12ded448c 100644 --- a/doc/api/enviroments.md +++ b/doc/api/enviroments.md @@ -32,7 +32,7 @@ Example response: Creates a new environment with the given name and external_url. -It returns 200 if the environment was successfully created, 400 for wrong parameters. +It returns 201 if the environment was successfully created, 400 for wrong parameters. ``` POST /projects/:id/environment @@ -58,21 +58,25 @@ Example response: } ``` -## Delete an environment +## Edit an existing environment -It returns 200 if the environment was successfully deleted, and 404 if the environment does not exist. +Updates an existing environment's name and/or external_url. + +It returns 200 if the environment was successfully updated. In case of an error, a status code 400 is returned. ``` -DELETE /projects/:id/environments/:environment_id +PUT /projects/:id/environments/:environments_id ``` -| Attribute | Type | Required | Description | -| --------- | ------- | -------- | --------------------- | -| `id` | integer | yes | The ID of the project | -| `environment_id` | integer | yes | The ID of the environment | +| Attribute | Type | Required | Description | +| --------------- | ------- | --------------------------------- | ------------------------------- | +| `id` | integer | yes | The ID of the project | +| `environment_id` | integer | yes | The ID of the environment | The ID of the environment | +| `name` | string | no | The new name of the environment | +| `external_url` | string | no | The new external_url | ```bash -curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1" +curl -X PUT --data "name=staging&external_url=https://staging.example.gitlab.com" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1" ``` Example response: @@ -80,30 +84,26 @@ Example response: ```json { "id": 1, - "name": "deploy", - "external_url": "https://deploy.example.gitlab.com" + "name": "staging", + "external_url": "https://staging.example.gitlab.com" } ``` -## Edit an existing environment - -Updates an existing environment's name and/or external_url. +## Delete an environment -It returns 200 if the label was successfully updated, In case of an error, an additional error message is returned. +It returns 200 if the environment was successfully deleted, and 404 if the environment does not exist. ``` -PUT /projects/:id/environments/:environments_id +DELETE /projects/:id/environments/:environment_id ``` -| Attribute | Type | Required | Description | -| --------------- | ------- | --------------------------------- | ------------------------------- | -| `id` | integer | yes | The ID of the project | -| `environment_id` | integer | yes | The ID of the environment | The ID of the environment | -| `name` | string | no | The new name of the environment | -| `external_url` | string | no | The new external_url | +| Attribute | Type | Required | Description | +| --------- | ------- | -------- | --------------------- | +| `id` | integer | yes | The ID of the project | +| `environment_id` | integer | yes | The ID of the environment | ```bash -curl -X PUT --data "name=staging&external_url=https://staging.example.gitlab.com" -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1" +curl -X DELETE -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v3/projects/1/environment/1" ``` Example response: @@ -111,7 +111,7 @@ Example response: ```json { "id": 1, - "name": "staging", - "external_url": "https://staging.example.gitlab.com" + "name": "deploy", + "external_url": "https://deploy.example.gitlab.com" } ``` diff --git a/lib/api/api.rb b/lib/api/api.rb index 9c960d74495..bd16806892b 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -7,6 +7,10 @@ module API rack_response({ 'message' => '404 Not found' }.to_json, 404) end + rescue_from Grape::Exceptions::ValidationErrors do |e| + error!({ messages: e.full_messages }, 400) + end + rescue_from :all do |exception| # lifted from https://github.com/rails/rails/blob/master/actionpack/lib/action_dispatch/middleware/debug_exceptions.rb#L60 # why is this not wrapped in something reusable? diff --git a/lib/api/environments.rb b/lib/api/environments.rb index 532baec42c7..a50f007d697 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -3,82 +3,77 @@ module API class Environments < Grape::API before { authenticate! } + params do + requires :id, type: String, desc: 'The project ID' + end resource :projects do - # Get all labels of the project - # - # Parameters: - # id (required) - The ID of a project - # Example Request: - # GET /projects/:id/environments + desc 'Get all environments of the project' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Environment + end get ':id/environments' do authorize! :read_environment, user_project present paginate(user_project.environments), with: Entities::Environment end - # Creates a new environment - # - # Parameters: - # id (required) - The ID of a project - # name (required) - The name of the environment to be created - # external_url (optional) - URL on which this deployment is viewable - # - # Example Request: - # POST /projects/:id/labels + desc 'Creates a new environment' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Environment + end + params do + requires :name, type: String, desc: 'The name of the environment to be created' + optional :external_url, type: String, desc: 'URL on which this deployment is viewable' + end post ':id/environments' do authorize! :create_environment, user_project - required_attributes! [:name] - - attrs = attributes_for_keys [:name, :external_url] - environment = user_project.environments.create(attrs) + create_params = declared(params, include_parent_namespaces: false).to_h + environment = user_project.environments.create(create_params) - if environment.valid? + if environment.persisted? present environment, with: Entities::Environment else render_validation_error!(environment) end end - # Deletes an existing environment - # - # Parameters: - # id (required) - The ID of a project - # environment_id (required) - The name of the environment to be deleted - # - # Example Request: - # DELETE /projects/:id/environments/:environment_id - delete ':id/environments/:environment_id' do - authorize! :update_environment, user_project - - environment = user_project.environments.find(params[:environment_id]) - - present environment.destroy, with: Entities::Environment + desc 'Updates an existing environment' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Environment + end + params do + requires :environment_id, type: Integer, desc: 'The environment ID' + optional :name, type: String, desc: 'The new environment name' + optional :external_url, type: String, desc: 'The new URL on which this deployment is viewable' end - - # Updates an existing environment - # - # Parameters: - # id (required) - The ID of a project - # environment_id (required) - The ID of the environment - # name (optional) - The name of the label to be deleted - # external_url (optional) - The new name of the label - # - # Example Request: - # PUT /projects/:id/environments/:environment_id put ':id/environments/:environment_id' do authorize! :update_environment, user_project environment = user_project.environments.find(params[:environment_id]) - attrs = attributes_for_keys [:name, :external_url] - - if environment.update(attrs) + update_params = declared(params, include_missing: false).extract!(:name, :external_url).to_h + if environment.update(update_params) present environment, with: Entities::Environment else render_validation_error!(environment) end end + + desc 'Deletes an existing environment' do + detail 'This feature was introduced in GitLab 8.11.' + success Entities::Environment + end + params do + requires :environment_id, type: Integer, desc: 'The environment ID' + end + delete ':id/environments/:environment_id' do + authorize! :update_environment, user_project + + environment = user_project.environments.find(params[:environment_id]) + + present environment.destroy, with: Entities::Environment + end end end end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index ef2148be1bd..8a84ac0a7c7 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -27,6 +27,7 @@ describe Environment, models: true do env = build(:environment, external_url: "") expect(env.save).to be true + expect(env.external_url).to be_nil end end end diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index b731c58a206..d315e456dda 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -14,7 +14,7 @@ describe API::API, api: true do describe 'GET /projects/:id/environments' do context 'as member of the project' do - it 'should return project environments' do + it 'returns project environments' do get api("/projects/#{project.id}/environments", user) expect(response).to have_http_status(200) @@ -26,7 +26,7 @@ describe API::API, api: true do end context 'as non member' do - it 'should return a 404 status code' do + it 'returns a 404 status code' do get api("/projects/#{project.id}/environments", non_member) expect(response).to have_http_status(404) @@ -50,7 +50,7 @@ describe API::API, api: true do expect(response).to have_http_status(400) end - it 'should return 400 if environment already exists' do + it 'returns a 400 if environment already exists' do post api("/projects/#{project.id}/environments", user), name: environment.name expect(response).to have_http_status(400) @@ -61,42 +61,53 @@ describe API::API, api: true do it 'rejects the request' do post api("/projects/#{project.id}/environments", non_member) - expect(response).to have_http_status(404) + expect(response).to have_http_status(400) end end end - describe 'DELETE /projects/:id/environments/:environment_id' do - context 'as a master' do - it 'should return 200 for an existing environment' do - delete api("/projects/#{project.id}/environments/#{environment.id}", user) - - expect(response).to have_http_status(200) - end - - it 'should return 404 for non existing id' do - delete api("/projects/#{project.id}/environments/12345", user) + describe 'PUT /projects/:id/environments/:environment_id' do + it 'returns a 200 if name and external_url are changed' do + url = 'https://mepmep.whatever.ninja' + put api("/projects/#{project.id}/environments/#{environment.id}", user), + name: 'Mepmep', external_url: url - expect(response).to have_http_status(404) - expect(json_response['message']).to eq('404 Not found') - end + expect(response).to have_http_status(200) + expect(json_response['name']).to eq('Mepmep') + expect(json_response['external_url']).to eq(url) end - end - describe 'PUT /projects/:id/environments/:environment_id' do - it 'should return 200 if name and external_url are changed' do + it "won't update the external_url if only the name is passed" do + url = environment.external_url put api("/projects/#{project.id}/environments/#{environment.id}", user), - name: 'Mepmep', external_url: 'https://mepmep.whatever.ninja' + name: 'Mepmep' expect(response).to have_http_status(200) expect(json_response['name']).to eq('Mepmep') - expect(json_response['external_url']).to eq('https://mepmep.whatever.ninja') + expect(json_response['external_url']).to eq(url) end - it 'should return 404 if the environment does not exist' do + it 'returns a 404 if the environment does not exist' do put api("/projects/#{project.id}/environments/12345", user) expect(response).to have_http_status(404) end end + + describe 'DELETE /projects/:id/environments/:environment_id' do + context 'as a master' do + it 'returns a 200 for an existing environment' do + delete api("/projects/#{project.id}/environments/#{environment.id}", user) + + expect(response).to have_http_status(200) + end + + it 'returns a 404 for non existing id' do + delete api("/projects/#{project.id}/environments/12345", user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 Not found') + end + end + end end -- cgit v1.2.1 From a54419f01f6cad33138e3d4ed049741699251f85 Mon Sep 17 00:00:00 2001 From: winniehell Date: Sun, 24 Jul 2016 07:32:47 +0200 Subject: Make "New issue" button in Issue page less obtrusive (!5457) --- CHANGELOG | 1 + app/views/help/ui.html.haml | 2 +- app/views/projects/issues/show.html.haml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 31a7eae90b9..641d6aa0d9f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,6 +27,7 @@ v 8.11.0 (unreleased) - Bump gitlab_git to speedup DiffCollection iterations - Make branches sortable without push permission !5462 (winniehell) - Check for Ci::Build artifacts at database level on pipeline partial + - Make "New issue" button in Issue page less obtrusive !5457 (winniehell) - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) - Add the `sprockets-es6` gem - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml index 431d312b4ca..85e188d6f8b 100644 --- a/app/views/help/ui.html.haml +++ b/app/views/help/ui.html.haml @@ -189,7 +189,7 @@ %li %a Sort by date - = link_to 'New issue', '#', class: 'btn btn-new' + = link_to 'New issue', '#', class: 'btn btn-new btn-inverted' .lead Only nav links without button and search diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index 9b6a97c0959..e5cce16a171 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -38,7 +38,7 @@ %li = link_to 'Edit', edit_namespace_project_issue_path(@project.namespace, @project, @issue) - if can?(current_user, :create_issue, @project) - = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-success', title: 'New issue', id: 'new_issue_link' do + = link_to new_namespace_project_issue_path(@project.namespace, @project), class: 'hidden-xs hidden-sm btn btn-grouped new-issue-link btn-new btn-inverted', title: 'New issue', id: 'new_issue_link' do New issue - if can?(current_user, :update_issue, @issue) = link_to 'Reopen issue', issue_path(@issue, issue: { state_event: :reopen }, status_only: true, format: 'json'), data: {no_turbolink: true}, class: "hidden-xs hidden-sm btn btn-grouped btn-reopen #{issue_button_visibility(@issue, false)}", title: 'Reopen issue' -- cgit v1.2.1 From 9eb100241e2d18788523a24a3e9a6371f9bfb2a6 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 29 Jul 2016 16:23:13 -0700 Subject: Clarify which project is deleted to avoid alarm Closes #13654 --- app/controllers/projects_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index ec7a2e63b9a..a6e1aa5ccc1 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -97,7 +97,7 @@ class ProjectsController < Projects::ApplicationController end if @project.pending_delete? - flash[:alert] = "Project queued for delete." + flash[:alert] = "Project #{@project.name} queued for deletion." end respond_to do |format| -- cgit v1.2.1 From 48ff40a047103bf09d4ac53fdbc984d73bc464cb Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 29 Jul 2016 21:04:04 -0700 Subject: Improve diff performance by eliminating redundant checks for text blobs On a merge request with over 1000 changed files, there were redundant calls to blob_text_viewable?, which incurred about 7% of the time. Improves #14775 --- CHANGELOG | 1 + app/helpers/blob_helper.rb | 2 +- app/views/projects/blob/_actions.html.haml | 3 ++- app/views/projects/diffs/_file.html.haml | 9 +++++---- spec/helpers/blob_helper_spec.rb | 18 ++++++++++++++++++ 5 files changed, 27 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 31a7eae90b9..a4bb72a9221 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.11.0 (unreleased) - Fix the title of the toggle dropdown button. !5515 (herminiotorres) + - Improve diff performance by eliminating redundant checks for text blobs - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) - Fix CI status icon link underline (ClemMakesApps) - Cache the commit author in RequestStore to avoid extra lookups in PostReceive diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index abe115d8c68..48c27828219 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -13,7 +13,7 @@ module BlobHelper blob = project.repository.blob_at(ref, path) rescue nil - return unless blob && blob_text_viewable?(blob) + return unless blob from_mr = options[:from_merge_request_id] link_opts = {} diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml index cdac50f7a8d..ff893ea74e1 100644 --- a/app/views/projects/blob/_actions.html.haml +++ b/app/views/projects/blob/_actions.html.haml @@ -16,6 +16,7 @@ - if current_user .btn-group{ role: "group" } - = edit_blob_link + - if blob_text_viewable?(@blob) + = edit_blob_link = replace_blob_link = delete_blob_link diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index c306909fb1a..1854c64cbd7 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -9,10 +9,11 @@ = icon('comment') \ - - if editable_diff?(diff_file) - = edit_blob_link(@merge_request.source_project, - @merge_request.source_branch, diff_file.new_path, - from_merge_request_id: @merge_request.id) + - if editable_diff?(diff_file) + = edit_blob_link(@merge_request.source_project, + @merge_request.source_branch, diff_file.new_path, + from_merge_request_id: @merge_request.id, + skip_visible_check: true) = view_file_btn(diff_commit.id, diff_file, project) diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb index bd0108f9938..b2d6d59b1ee 100644 --- a/spec/helpers/blob_helper_spec.rb +++ b/spec/helpers/blob_helper_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe BlobHelper do + include TreeHelper + let(:blob_name) { 'test.lisp' } let(:no_context_content) { ":type \"assem\"))" } let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" } @@ -65,4 +67,20 @@ describe BlobHelper do expect(sanitize_svg(blob).data).to eq(expected) end end + + describe "#edit_blob_link" do + let(:project) { create(:project) } + + before do + allow(self).to receive(:current_user).and_return(double) + end + + it 'verifies blob is text' do + expect(self).not_to receive(:blob_text_viewable?) + + button = edit_blob_link(project, 'refs/heads/master', 'README.md') + + expect(button).to start_with(' Date: Sun, 31 Jul 2016 15:36:11 -0400 Subject: Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. --- CHANGELOG | 1 + config/initializers/trusted_proxies.rb | 2 ++ spec/initializers/trusted_proxies_spec.rb | 6 ++++++ 3 files changed, 9 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 9b66108c160..a0a3484d9a2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,7 @@ v 8.11.0 (unreleased) - Make error pages responsive (Takuya Noguchi) - Change requests_profiles resource constraint to catch virtually any file - Reduce number of queries made for merge_requests/:id/diffs + - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. v 8.10.3 (unreleased) - Fix hooks missing on imported GitLab projects diff --git a/config/initializers/trusted_proxies.rb b/config/initializers/trusted_proxies.rb index 30770b71e24..cd869657c53 100644 --- a/config/initializers/trusted_proxies.rb +++ b/config/initializers/trusted_proxies.rb @@ -7,6 +7,8 @@ module Rack class Request def trusted_proxy?(ip) Rails.application.config.action_dispatch.trusted_proxies.any? { |proxy| proxy === ip } + rescue IPAddr::InvalidAddressError + false end end end diff --git a/spec/initializers/trusted_proxies_spec.rb b/spec/initializers/trusted_proxies_spec.rb index 52d5a7dffc9..290e47763eb 100644 --- a/spec/initializers/trusted_proxies_spec.rb +++ b/spec/initializers/trusted_proxies_spec.rb @@ -47,6 +47,12 @@ describe 'trusted_proxies', lib: true do expect(request.remote_ip).to eq('1.1.1.1') expect(request.ip).to eq('1.1.1.1') end + + it 'handles invalid ip addresses' do + request = stub_request('HTTP_X_FORWARDED_FOR' => '(null), 1.1.1.1:12345, 1.1.1.1') + expect(request.remote_ip).to eq('1.1.1.1') + expect(request.ip).to eq('1.1.1.1') + end end def stub_request(headers = {}) -- cgit v1.2.1 From c9ce36e829be6a5991996a495946fe9416747c6e Mon Sep 17 00:00:00 2001 From: lookatmike Date: Sun, 31 Jul 2016 16:18:06 -0400 Subject: Moved to 8.10.3 release. --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index a0a3484d9a2..9075972e6d0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,11 +36,11 @@ v 8.11.0 (unreleased) - Make error pages responsive (Takuya Noguchi) - Change requests_profiles resource constraint to catch virtually any file - Reduce number of queries made for merge_requests/:id/diffs - - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. v 8.10.3 (unreleased) - Fix hooks missing on imported GitLab projects - Properly abort a merge when merge conflicts occur + - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. v 8.10.2 - User can now search branches by name. !5144 -- cgit v1.2.1 From 5b4ceeed6317cc8039642981ba356565e11d991e Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 29 Jul 2016 18:24:11 -0300 Subject: Fix attr reader to force the intended values for source and target shas When importing a pull request from GitHub, the old and new branches may no longer actually exist by those names, but we need to recreate the merge request diff with the right source and target shas. We use these `target_branch_sha` and `source_branch_sha` attributes to force these to the intended values. But the reader methods were always looking up to the target/source branch head instead of check if these values was previously set. --- app/models/merge_request.rb | 4 ++++ spec/models/merge_request_spec.rb | 18 +++++++++++++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 471e32f3b60..fdcbbdc1d08 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -238,10 +238,14 @@ class MergeRequest < ActiveRecord::Base end def target_branch_sha + return @target_branch_sha if defined?(@target_branch_sha) + target_branch_head.try(:sha) end def source_branch_sha + return @source_branch_sha if defined?(@source_branch_sha) + source_branch_head.try(:sha) end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index c8ad7ab3e7f..a0e3c26e542 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -65,11 +65,11 @@ describe MergeRequest, models: true do end describe '#target_branch_sha' do - context 'when the target branch does not exist anymore' do - let(:project) { create(:project) } + let(:project) { create(:project) } - subject { create(:merge_request, source_project: project, target_project: project) } + subject { create(:merge_request, source_project: project, target_project: project) } + context 'when the target branch does not exist' do before do project.repository.raw_repository.delete_branch(subject.target_branch) end @@ -78,6 +78,12 @@ describe MergeRequest, models: true do expect(subject.target_branch_sha).to be_nil end end + + it 'returns memoized value' do + subject.target_branch_sha = '8ffb3c15a5475e59ae909384297fede4badcb4c7' + + expect(subject.target_branch_sha).to eq '8ffb3c15a5475e59ae909384297fede4badcb4c7' + end end describe '#source_branch_sha' do @@ -103,6 +109,12 @@ describe MergeRequest, models: true do expect(subject.source_branch_sha).to be_nil end end + + it 'returns memoized value' do + subject.source_branch_sha = '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b' + + expect(subject.source_branch_sha).to eq '2e5d3239642f9161dcbbc4b70a211a68e5e45e2b' + end end describe '#to_reference' do -- cgit v1.2.1 From 849e8e0c371e4994ae6a47e8e91470e7bb1eaf18 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 29 Jul 2016 18:35:46 -0300 Subject: Update CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 9b66108c160..716665157bf 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -38,6 +38,7 @@ v 8.11.0 (unreleased) - Reduce number of queries made for merge_requests/:id/diffs v 8.10.3 (unreleased) + - Fix importer for GitHub Pull Requests when a branch was removed - Fix hooks missing on imported GitLab projects - Properly abort a merge when merge conflicts occur -- cgit v1.2.1 From 285ba1b20f226f0bf7ab01010b64cabdccecf096 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Sun, 31 Jul 2016 19:44:02 -0300 Subject: fixup! Fix attr reader to force the intended values for source and target shas --- app/models/merge_request.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index fdcbbdc1d08..f1b9c1d6feb 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -238,15 +238,11 @@ class MergeRequest < ActiveRecord::Base end def target_branch_sha - return @target_branch_sha if defined?(@target_branch_sha) - - target_branch_head.try(:sha) + @target_branch_sha || target_branch_head.try(:sha) end def source_branch_sha - return @source_branch_sha if defined?(@source_branch_sha) - - source_branch_head.try(:sha) + @source_branch_sha || source_branch_head.try(:sha) end def diff_refs -- cgit v1.2.1 From 0fa50494b8f1c765f7c046e0648afec81c27dcd7 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 31 Jul 2016 22:14:48 -0700 Subject: Improve spinach test to be more specific about link to click If you add another branch to gitlab-test that includes the word 'test', browse_files.feature will fail with an ambiguous match. --- features/steps/project/source/browse_files.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb index 0fe046dcbf6..9a8896acb15 100644 --- a/features/steps/project/source/browse_files.rb +++ b/features/steps/project/source/browse_files.rb @@ -293,7 +293,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps first('.js-project-refs-dropdown').click page.within '.project-refs-form' do - click_link 'test' + click_link "'test'" end end -- cgit v1.2.1 From 34c1c8a3b14ab3b29fbde97532c89404d9573a1d Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 1 Aug 2016 08:42:09 +0200 Subject: Minor fixes in the Env API endpoints --- app/controllers/projects/environments_controller.rb | 2 +- lib/api/environments.rb | 6 +++++- spec/requests/api/environments_spec.rb | 20 ++++++++++++++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 1f5c7506212..58678f96879 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -2,7 +2,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController layout 'project' before_action :authorize_read_environment! before_action :authorize_create_environment!, only: [:new, :create] - before_action :authorize_update_environment!, only: [:edit, :destroy] + before_action :authorize_update_environment!, only: [:edit, :update, :destroy] before_action :environment, only: [:show, :edit, :update, :destroy] def index diff --git a/lib/api/environments.rb b/lib/api/environments.rb index a50f007d697..819f80d8365 100644 --- a/lib/api/environments.rb +++ b/lib/api/environments.rb @@ -11,6 +11,10 @@ module API detail 'This feature was introduced in GitLab 8.11.' success Entities::Environment 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/environments' do authorize! :read_environment, user_project @@ -51,7 +55,7 @@ module API authorize! :update_environment, user_project environment = user_project.environments.find(params[:environment_id]) - + update_params = declared(params, include_missing: false).extract!(:name, :external_url).to_h if environment.update(update_params) present environment, with: Entities::Environment diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb index d315e456dda..05e57905343 100644 --- a/spec/requests/api/environments_spec.rb +++ b/spec/requests/api/environments_spec.rb @@ -14,6 +14,10 @@ describe API::API, api: true do describe 'GET /projects/:id/environments' do context 'as member of the project' do + it_behaves_like 'a paginated resources' do + let(:request) { get api("/projects/#{project.id}/environments", user) } + end + it 'returns project environments' do get api("/projects/#{project.id}/environments", user) @@ -59,9 +63,13 @@ describe API::API, api: true do context 'a non member' do it 'rejects the request' do - post api("/projects/#{project.id}/environments", non_member) + post api("/projects/#{project.id}/environments", non_member), name: 'gitlab.com' - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) + end + + it 'returns a 400 when the required params are missing' do + post api("/projects/12345/environments", non_member), external_url: 'http://env.git.com' end end end @@ -109,5 +117,13 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end end + + context 'a non member' do + it 'rejects the request' do + delete api("/projects/#{project.id}/environments/#{environment.id}", non_member) + + expect(response).to have_http_status(404) + end + end end end -- cgit v1.2.1 From 52bb564812d106124b95c93f5a502f3ced9c280b Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 27 Jul 2016 17:56:25 +0200 Subject: squashed - fix timing issues in prod importing projects added changelog fix specs refactored code based on feedback fix rubocop warning --- CHANGELOG | 1 + lib/gitlab/import_export/file_importer.rb | 20 +++++++++++++++++++- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 9075972e6d0..4095eaa6e63 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,6 +41,7 @@ v 8.10.3 (unreleased) - Fix hooks missing on imported GitLab projects - Properly abort a merge when merge conflicts occur - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. + - Fix timing problems running imports on production v 8.10.2 - User can now search branches by name. !5144 diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb index 82d1e1805c5..ff7174c995f 100644 --- a/lib/gitlab/import_export/file_importer.rb +++ b/lib/gitlab/import_export/file_importer.rb @@ -3,6 +3,8 @@ module Gitlab class FileImporter include Gitlab::ImportExport::CommandLineUtil + MAX_RETRIES = 8 + def self.import(*args) new(*args).import end @@ -14,7 +16,10 @@ module Gitlab def import FileUtils.mkdir_p(@shared.export_path) - decompress_archive + + wait_for_archived_file do + decompress_archive + end rescue => e @shared.error(e) false @@ -22,6 +27,19 @@ module Gitlab private + # Exponentially sleep until I/O finishes copying the file + def wait_for_archived_file + MAX_RETRIES.times do |retry_number| + if File.exist?(@archive_file) + yield + + break + else + sleep(2**retry_number) + end + end + end + def decompress_archive result = untar_zxf(archive: @archive_file, dir: @shared.export_path) -- cgit v1.2.1 From dad1d0b8646b75ffcb3b1f6758848c53482f6bd2 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 29 Jul 2016 15:27:21 +0200 Subject: fix return value and spec --- lib/gitlab/import_export/file_importer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb index ff7174c995f..4b5f1f26286 100644 --- a/lib/gitlab/import_export/file_importer.rb +++ b/lib/gitlab/import_export/file_importer.rb @@ -33,7 +33,7 @@ module Gitlab if File.exist?(@archive_file) yield - break + return true else sleep(2**retry_number) end -- cgit v1.2.1 From aad0ae71620d8e988faf75587a612b933df00366 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 22 Jul 2016 12:10:45 +0200 Subject: squashed - fixed label and milestone association problems, updated specs and refactored reader class a bit --- CHANGELOG | 1 + app/models/label_link.rb | 6 +- app/models/project.rb | 10 ++ lib/gitlab/import_export.rb | 2 +- lib/gitlab/import_export/import_export.yml | 22 ++- lib/gitlab/import_export/json_hash_builder.rb | 110 +++++++++++++ lib/gitlab/import_export/project_tree_restorer.rb | 4 +- lib/gitlab/import_export/reader.rb | 77 +-------- lib/gitlab/import_export/relation_factory.rb | 51 ++++-- spec/lib/gitlab/import_export/project.json | 176 ++++++--------------- .../import_export/project_tree_restorer_spec.rb | 12 ++ .../import_export/project_tree_saver_spec.rb | 30 ++-- 12 files changed, 265 insertions(+), 236 deletions(-) create mode 100644 lib/gitlab/import_export/json_hash_builder.rb diff --git a/CHANGELOG b/CHANGELOG index 9075972e6d0..3537b147b2a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,6 +41,7 @@ v 8.10.3 (unreleased) - Fix hooks missing on imported GitLab projects - Properly abort a merge when merge conflicts occur - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. + - Fix Import/Export issue importing milestones and labels not associated properly v 8.10.2 - User can now search branches by name. !5144 diff --git a/app/models/label_link.rb b/app/models/label_link.rb index 47bd6eaf35f..51b5c2b1f4c 100644 --- a/app/models/label_link.rb +++ b/app/models/label_link.rb @@ -1,7 +1,9 @@ class LabelLink < ActiveRecord::Base + include Importable + belongs_to :target, polymorphic: true belongs_to :label - validates :target, presence: true - validates :label, presence: true + validates :target, presence: true, unless: :importing? + validates :label, presence: true, unless: :importing? end diff --git a/app/models/project.rb b/app/models/project.rb index 7aecd7860c5..83b848ded8b 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1253,6 +1253,16 @@ class Project < ActiveRecord::Base authorized_for_user_by_shared_projects?(user, min_access_level) end + def append_or_update_attribute(name, value) + old_values = public_send(name.to_s) + + if Project.reflect_on_association(name).try(:macro) == :has_many && old_values.any? + update_attribute(name, old_values + value) + else + update_attribute(name, value) + end + end + private def authorized_for_user_by_group?(user, min_access_level) diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index d6d14bd98a0..48b2c43ac21 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -2,7 +2,7 @@ module Gitlab module ImportExport extend self - VERSION = '0.1.2' + VERSION = '0.1.3' FILENAME_LIMIT = 50 def export_path(relative_path:) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 15afe8174a4..1da51043611 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -3,11 +3,12 @@ project_tree: - issues: - :events - notes: - - :author - - :events - - :labels - - milestones: - - :events + - :author + - :events + - label_links: + - :label + - milestone: + - :events - snippets: - notes: :author @@ -20,6 +21,10 @@ project_tree: - :events - :merge_request_diff - :events + - label_links: + - :label + - milestone: + - :events - pipelines: - notes: - :author @@ -31,6 +36,9 @@ project_tree: - :services - :hooks - :protected_branches + - :labels + - milestones: + - :events # Only include the following attributes for the models specified. included_attributes: @@ -55,6 +63,10 @@ excluded_attributes: - :expired_at merge_request_diff: - :st_diffs + issues: + - :milestone_id + merge_requests: + - :milestone_id methods: statuses: diff --git a/lib/gitlab/import_export/json_hash_builder.rb b/lib/gitlab/import_export/json_hash_builder.rb new file mode 100644 index 00000000000..008300bde45 --- /dev/null +++ b/lib/gitlab/import_export/json_hash_builder.rb @@ -0,0 +1,110 @@ +module Gitlab + module ImportExport + # Generates a hash that conforms with http://apidock.com/rails/Hash/to_json + # and its peculiar options. + class JsonHashBuilder + def self.build(model_objects, attributes_finder) + new(model_objects, attributes_finder).build + end + + def initialize(model_objects, attributes_finder) + @model_objects = model_objects + @attributes_finder = attributes_finder + end + + def build + process_model_objects(@model_objects) + end + + private + + # Called when the model is actually a hash containing other relations (more models) + # Returns the config in the right format for calling +to_json+ + # + # +model_object_hash+ - A model relationship such as: + # {:merge_requests=>[:merge_request_diff, :notes]} + def process_model_objects(model_object_hash) + json_config_hash = {} + current_key = model_object_hash.keys.first + + model_object_hash.values.flatten.each do |model_object| + @attributes_finder.parse(current_key) { |hash| json_config_hash[current_key] ||= hash } + handle_model_object(current_key, model_object, json_config_hash) + end + + json_config_hash + end + + # Creates or adds to an existing hash an individual model or list + # + # +current_key+ main model that will be a key in the hash + # +model_object+ model or list of models to include in the hash + # +json_config_hash+ the original hash containing the root model + def handle_model_object(current_key, model_object, json_config_hash) + model_or_sub_model = model_object.is_a?(Hash) ? process_model_objects(model_object) : model_object + + if json_config_hash[current_key] + add_model_value(current_key, model_or_sub_model, json_config_hash) + else + create_model_value(current_key, model_or_sub_model, json_config_hash) + end + end + + # Constructs a new hash that will hold the configuration for that particular object + # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+ + # + # +current_key+ main model that will be a key in the hash + # +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 + 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) + @attributes_finder.parse(value) do |hash| + parsed_hash = { include: hash_or_merge(value, hash) } + end + end + + # Adds new model configuration to an existing hash with key +current_key+ + # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+ + # + # +current_key+ main model that will be a key in the hash + # +value+ existing model to be included in the hash + # +json_config_hash+ the original hash containing the root model + def add_model_value(current_key, value, json_config_hash) + @attributes_finder.parse(value) { |hash| value = { value => hash } } + + add_to_array(current_key, json_config_hash, value) + end + + # Adds new model configuration to an existing hash with key +current_key+ + # it creates a new array if it was previously a single value + # + # +current_key+ main model that will be a key in the hash + # +value+ existing model to be included in the hash + # +json_config_hash+ the original hash containing the root model + def add_to_array(current_key, json_config_hash, value) + old_values = json_config_hash[current_key][:include] + + json_config_hash[current_key][:include] = ([old_values] + [value]).compact.flatten + end + + # Construct a new hash or merge with an existing one a model configuration + # This is to fulfil +to_json+ requirements. + # + # +hash+ hash containing configuration generated mainly from +@attributes_finder+ + # +value+ existing model to be included in the hash + def hash_or_merge(value, hash) + value.is_a?(Hash) ? value.merge(hash) : { value => hash } + end + end + end +end diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index 051110c23cf..c7b3551b84c 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -47,7 +47,7 @@ module Gitlab relation_key = relation.is_a?(Hash) ? relation.keys.first : relation relation_hash = create_relation(relation_key, @tree_hash[relation_key.to_s]) - saved << restored_project.update_attribute(relation_key, relation_hash) + saved << restored_project.append_or_update_attribute(relation_key, relation_hash) end saved.all? end @@ -78,7 +78,7 @@ module Gitlab relation_key = relation.keys.first.to_s return if tree_hash[relation_key].blank? - tree_hash[relation_key].each do |relation_item| + [tree_hash[relation_key]].flatten.each do |relation_item| relation.values.flatten.each do |sub_relation| # We just use author to get the user ID, do not attempt to create an instance. next if sub_relation == :author diff --git a/lib/gitlab/import_export/reader.rb b/lib/gitlab/import_export/reader.rb index 15f5dd31035..5021a1a14ce 100644 --- a/lib/gitlab/import_export/reader.rb +++ b/lib/gitlab/import_export/reader.rb @@ -29,87 +29,12 @@ module Gitlab def build_hash(model_list) model_list.map do |model_objects| if model_objects.is_a?(Hash) - build_json_config_hash(model_objects) + Gitlab::ImportExport::JsonHashBuilder.build(model_objects, @attributes_finder) else @attributes_finder.find(model_objects) end end end - - # Called when the model is actually a hash containing other relations (more models) - # Returns the config in the right format for calling +to_json+ - # +model_object_hash+ - A model relationship such as: - # {:merge_requests=>[:merge_request_diff, :notes]} - def build_json_config_hash(model_object_hash) - @json_config_hash = {} - - model_object_hash.values.flatten.each do |model_object| - current_key = model_object_hash.keys.first - - @attributes_finder.parse(current_key) { |hash| @json_config_hash[current_key] ||= hash } - - handle_model_object(current_key, model_object) - process_sub_model(current_key, model_object) if model_object.is_a?(Hash) - end - @json_config_hash - end - - # If the model is a hash, process the sub_models, which could also be hashes - # If there is a list, add to an existing array, otherwise use hash syntax - # +current_key+ main model that will be a key in the hash - # +model_object+ model or list of models to include in the hash - def process_sub_model(current_key, model_object) - sub_model_json = build_json_config_hash(model_object).dup - @json_config_hash.slice!(current_key) - - if @json_config_hash[current_key] && @json_config_hash[current_key][:include] - @json_config_hash[current_key][:include] << sub_model_json - else - @json_config_hash[current_key] = { include: sub_model_json } - end - end - - # Creates or adds to an existing hash an individual model or list - # +current_key+ main model that will be a key in the hash - # +model_object+ model or list of models to include in the hash - def handle_model_object(current_key, model_object) - if @json_config_hash[current_key] - add_model_value(current_key, model_object) - else - create_model_value(current_key, model_object) - end - end - - # Constructs a new hash that will hold the configuration for that particular object - # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+ - # +current_key+ main model that will be a key in the hash - # +value+ existing model to be included in the hash - def create_model_value(current_key, value) - parsed_hash = { include: value } - - @attributes_finder.parse(value) do |hash| - parsed_hash = { include: hash_or_merge(value, hash) } - end - @json_config_hash[current_key] = parsed_hash - end - - # Adds new model configuration to an existing hash with key +current_key+ - # It may include exceptions or other attribute detail configuration, parsed by +@attributes_finder+ - # +current_key+ main model that will be a key in the hash - # +value+ existing model to be included in the hash - def add_model_value(current_key, value) - @attributes_finder.parse(value) { |hash| value = { value => hash } } - old_values = @json_config_hash[current_key][:include] - @json_config_hash[current_key][:include] = ([old_values] + [value]).compact.flatten - end - - # Construct a new hash or merge with an existing one a model configuration - # This is to fulfil +to_json+ requirements. - # +value+ existing model to be included in the hash - # +hash+ hash containing configuration generated mainly from +@attributes_finder+ - def hash_or_merge(value, hash) - value.is_a?(Hash) ? value.merge(hash) : { value => hash } - end end end end diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index e41c7e6bf4f..e9c1b79fa45 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -13,6 +13,10 @@ module Gitlab BUILD_MODELS = %w[Ci::Build commit_status].freeze + IMPORTED_OBJECT_MAX_RETRIES = 5.freeze + + EXISTING_OBJECT_CHECK = %i[milestone milestones label labels].freeze + def self.create(*args) new(*args).create end @@ -22,24 +26,35 @@ module Gitlab @relation_hash = relation_hash.except('id', 'noteable_id') @members_mapper = members_mapper @user = user + @imported_object_retries = 0 end # Creates an object from an actual model with name "relation_sym" with params from # the relation_hash, updating references with new object IDs, mapping users using # the "members_mapper" object, also updating notes if required. def create - set_note_author if @relation_name == :notes + setup_models + + generate_imported_object + end + + private + + def setup_models + if @relation_name == :notes + set_note_author + + # TODO: note attatchments not supported yet + @relation_hash['attachment'] = nil + end + update_user_references update_project_references reset_ci_tokens if @relation_name == 'Ci::Trigger' @relation_hash['data'].deep_symbolize_keys! if @relation_name == :events && @relation_hash['data'] set_st_diffs if @relation_name == :merge_request_diff - - generate_imported_object end - private - def update_user_references USER_REFERENCES.each do |reference| if @relation_hash[reference] @@ -112,10 +127,14 @@ module Gitlab end def imported_object - imported_object = relation_class.new(parsed_relation_hash) - yield(imported_object) if block_given? - imported_object.importing = true if imported_object.respond_to?(:importing) - imported_object + yield(existing_or_new_object) if block_given? + existing_or_new_object.importing = true if existing_or_new_object.respond_to?(:importing) + existing_or_new_object + rescue ActiveRecord::RecordNotUnique + # as the operation is not atomic, retry in the unlikely scenario an INSERT is + # performed on the same object between the SELECT and the INSERT + @imported_object_retries += 1 + retry if @imported_object_retries < IMPORTED_OBJECT_MAX_RETRIES end def update_note_for_missing_author(author_name) @@ -134,6 +153,20 @@ module Gitlab def set_st_diffs @relation_hash['st_diffs'] = @relation_hash.delete('utf8_st_diffs') end + + def existing_or_new_object + # Only find existing records to avoid mapping tables such as milestones + # Otherwise always create the record, skipping the extra SELECT clause. + @existing_or_new_object ||= begin + if EXISTING_OBJECT_CHECK.include?(@relation_name) + existing_object = relation_class.find_or_initialize_by(parsed_relation_hash.slice('title', 'project_id')) + existing_object.assign_attributes(parsed_relation_hash) + existing_object + else + relation_class.new(parsed_relation_hash) + end + end + end end end end diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index b1a5d72c624..b5550ca1963 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -18,7 +18,6 @@ "position": 0, "branch_name": null, "description": "Aliquam enim illo et possimus.", - "milestone_id": 18, "state": "opened", "iid": 10, "updated_by_id": null, @@ -27,6 +26,52 @@ "due_date": null, "moved_to_id": null, "test_ee_field": "test", + "milestone": { + "id": 1, + "title": "v0.0", + "project_id": 8, + "description": "test milestone", + "due_date": null, + "created_at": "2016-06-14T15:02:04.415Z", + "updated_at": "2016-06-14T15:02:04.415Z", + "state": "active", + "iid": 1, + "events": [ + { + "id": 487, + "target_type": "Milestone", + "target_id": 1, + "title": null, + "data": null, + "project_id": 46, + "created_at": "2016-06-14T15:02:04.418Z", + "updated_at": "2016-06-14T15:02:04.418Z", + "action": 1, + "author_id": 18 + } + ] + }, + "label_links": [ + { + "id": 2, + "label_id": 2, + "target_id": 3, + "target_type": "Issue", + "created_at": "2016-07-22T08:57:02.840Z", + "updated_at": "2016-07-22T08:57:02.840Z", + "label": { + "id": 2, + "title": "test2", + "color": "#428bca", + "project_id": 8, + "created_at": "2016-07-22T08:55:44.161Z", + "updated_at": "2016-07-22T08:55:44.161Z", + "template": false, + "description": "", + "priority": null + } + } + ], "notes": [ { "id": 351, @@ -233,7 +278,6 @@ "position": 0, "branch_name": null, "description": "Voluptate vel reprehenderit facilis omnis voluptas magnam tenetur.", - "milestone_id": 16, "state": "opened", "iid": 9, "updated_by_id": null, @@ -447,7 +491,6 @@ "position": 0, "branch_name": null, "description": "Ea recusandae neque autem tempora.", - "milestone_id": 16, "state": "closed", "iid": 8, "updated_by_id": null, @@ -661,7 +704,6 @@ "position": 0, "branch_name": null, "description": "Maiores architecto quos in dolorem.", - "milestone_id": 17, "state": "opened", "iid": 7, "updated_by_id": null, @@ -875,7 +917,6 @@ "position": 0, "branch_name": null, "description": "Ut aut ut et tenetur velit aut id modi.", - "milestone_id": 16, "state": "opened", "iid": 6, "updated_by_id": null, @@ -1089,7 +1130,6 @@ "position": 0, "branch_name": null, "description": "Dicta nisi nihil non ipsa velit.", - "milestone_id": 20, "state": "closed", "iid": 5, "updated_by_id": null, @@ -1303,7 +1343,6 @@ "position": 0, "branch_name": null, "description": "Ut et explicabo vel voluptatem consequuntur ut sed.", - "milestone_id": 19, "state": "closed", "iid": 4, "updated_by_id": null, @@ -1517,7 +1556,6 @@ "position": 0, "branch_name": null, "description": "Non asperiores velit accusantium voluptate.", - "milestone_id": 18, "state": "closed", "iid": 3, "updated_by_id": null, @@ -1731,7 +1769,6 @@ "position": 0, "branch_name": null, "description": "Molestiae corporis magnam et fugit aliquid nulla quia.", - "milestone_id": 17, "state": "closed", "iid": 2, "updated_by_id": null, @@ -1945,7 +1982,6 @@ "position": 0, "branch_name": null, "description": "Quod ad architecto qui est sed quia.", - "milestone_id": 20, "state": "closed", "iid": 1, "updated_by_id": null, @@ -2259,117 +2295,6 @@ "author_id": 25 } ] - }, - { - "id": 18, - "title": "v2.0", - "project_id": 5, - "description": "Error dolorem rerum aut nulla.", - "due_date": null, - "created_at": "2016-06-14T15:02:04.576Z", - "updated_at": "2016-06-14T15:02:04.576Z", - "state": "active", - "iid": 3, - "events": [ - { - "id": 242, - "target_type": "Milestone", - "target_id": 18, - "title": null, - "data": null, - "project_id": 36, - "created_at": "2016-06-14T15:02:04.579Z", - "updated_at": "2016-06-14T15:02:04.579Z", - "action": 1, - "author_id": 1 - }, - { - "id": 58, - "target_type": "Milestone", - "target_id": 18, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-06-14T15:02:04.579Z", - "updated_at": "2016-06-14T15:02:04.579Z", - "action": 1, - "author_id": 22 - } - ] - }, - { - "id": 17, - "title": "v1.0", - "project_id": 5, - "description": "Molestiae perspiciatis voluptates doloremque commodi veniam consequatur.", - "due_date": null, - "created_at": "2016-06-14T15:02:04.569Z", - "updated_at": "2016-06-14T15:02:04.569Z", - "state": "active", - "iid": 2, - "events": [ - { - "id": 243, - "target_type": "Milestone", - "target_id": 17, - "title": null, - "data": null, - "project_id": 36, - "created_at": "2016-06-14T15:02:04.570Z", - "updated_at": "2016-06-14T15:02:04.570Z", - "action": 1, - "author_id": 1 - }, - { - "id": 57, - "target_type": "Milestone", - "target_id": 17, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-06-14T15:02:04.570Z", - "updated_at": "2016-06-14T15:02:04.570Z", - "action": 1, - "author_id": 20 - } - ] - }, - { - "id": 16, - "title": "v0.0", - "project_id": 5, - "description": "Velit numquam et sed sit.", - "due_date": null, - "created_at": "2016-06-14T15:02:04.561Z", - "updated_at": "2016-06-14T15:02:04.561Z", - "state": "closed", - "iid": 1, - "events": [ - { - "id": 244, - "target_type": "Milestone", - "target_id": 16, - "title": null, - "data": null, - "project_id": 36, - "created_at": "2016-06-14T15:02:04.563Z", - "updated_at": "2016-06-14T15:02:04.563Z", - "action": 1, - "author_id": 26 - }, - { - "id": 56, - "target_type": "Milestone", - "target_id": 16, - "title": null, - "data": null, - "project_id": 5, - "created_at": "2016-06-14T15:02:04.563Z", - "updated_at": "2016-06-14T15:02:04.563Z", - "action": 1, - "author_id": 26 - } - ] } ], "snippets": [ @@ -2471,7 +2396,6 @@ "title": "Cannot be automatically merged", "created_at": "2016-06-14T15:02:36.568Z", "updated_at": "2016-06-14T15:02:56.815Z", - "milestone_id": null, "state": "opened", "merge_status": "unchecked", "target_project_id": 5, @@ -2909,7 +2833,6 @@ "title": "Can be automatically merged", "created_at": "2016-06-14T15:02:36.418Z", "updated_at": "2016-06-14T15:02:57.013Z", - "milestone_id": null, "state": "opened", "merge_status": "unchecked", "target_project_id": 5, @@ -3194,7 +3117,6 @@ "title": "Qui accusantium et inventore facilis doloribus occaecati officiis.", "created_at": "2016-06-14T15:02:25.168Z", "updated_at": "2016-06-14T15:02:59.521Z", - "milestone_id": 17, "state": "opened", "merge_status": "unchecked", "target_project_id": 5, @@ -3479,7 +3401,6 @@ "title": "In voluptas aut sequi voluptatem ullam vel corporis illum consequatur.", "created_at": "2016-06-14T15:02:24.760Z", "updated_at": "2016-06-14T15:02:59.749Z", - "milestone_id": 20, "state": "opened", "merge_status": "unchecked", "target_project_id": 5, @@ -4170,7 +4091,6 @@ "title": "Voluptates consequatur eius nemo amet libero animi illum delectus tempore.", "created_at": "2016-06-14T15:02:24.415Z", "updated_at": "2016-06-14T15:02:59.958Z", - "milestone_id": 17, "state": "opened", "merge_status": "unchecked", "target_project_id": 5, @@ -4719,7 +4639,6 @@ "title": "In a rerum harum nihil accusamus aut quia nobis non.", "created_at": "2016-06-14T15:02:24.000Z", "updated_at": "2016-06-14T15:03:00.225Z", - "milestone_id": 19, "state": "opened", "merge_status": "unchecked", "target_project_id": 5, @@ -5219,7 +5138,6 @@ "title": "Corporis provident similique perspiciatis dolores eos animi.", "created_at": "2016-06-14T15:02:23.767Z", "updated_at": "2016-06-14T15:03:00.475Z", - "milestone_id": 18, "state": "opened", "merge_status": "unchecked", "target_project_id": 5, @@ -5480,7 +5398,6 @@ "title": "Eligendi reprehenderit doloribus quia et sit id.", "created_at": "2016-06-14T15:02:23.014Z", "updated_at": "2016-06-14T15:03:00.685Z", - "milestone_id": 20, "state": "opened", "merge_status": "unchecked", "target_project_id": 5, @@ -6171,7 +6088,6 @@ "title": "Et ipsam voluptas velit sequi illum ut.", "created_at": "2016-06-14T15:02:22.825Z", "updated_at": "2016-06-14T15:03:00.904Z", - "milestone_id": 16, "state": "opened", "merge_status": "unchecked", "target_project_id": 5, diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 6ae20c943b1..32c0d6462f1 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -60,6 +60,18 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do expect { restored_project_json }.to change(MergeRequestDiff.where.not(st_diffs: nil), :count).by(9) end + + it 'has labels associated to label links, associated to issues' do + restored_project_json + + expect(Label.first.label_links.first.target).not_to be_nil + end + + it 'has milestones associated to issues' do + restored_project_json + + expect(Milestone.find_by_description('test milestone').issues).not_to be_empty + end end end end diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index 057ef6e76a0..3a86a4ce07c 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -31,10 +31,6 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do expect(saved_project_json).to include({ "visibility_level" => 20 }) end - it 'has events' do - expect(saved_project_json['milestones'].first['events']).not_to be_empty - end - it 'has milestones' do expect(saved_project_json['milestones']).not_to be_empty end @@ -43,8 +39,12 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do expect(saved_project_json['merge_requests']).not_to be_empty end - it 'has labels' do - expect(saved_project_json['labels']).not_to be_empty + it 'has merge request\'s milestones' do + expect(saved_project_json['merge_requests'].first['milestone']).not_to be_empty + end + + it 'has events' do + expect(saved_project_json['merge_requests'].first['milestone']['events']).not_to be_empty end it 'has snippets' do @@ -103,6 +103,14 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do expect(saved_project_json['pipelines'].first['notes']).not_to be_empty end + it 'has labels with no associations' do + expect(saved_project_json['labels']).not_to be_empty + end + + it 'has labels associated to records' do + expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty + end + it 'does not complain about non UTF-8 characters in MR diffs' do ActiveRecord::Base.connection.execute("UPDATE merge_request_diffs SET st_diffs = '---\n- :diff: !binary |-\n LS0tIC9kZXYvbnVsbAorKysgYi9pbWFnZXMvbnVjb3IucGRmCkBAIC0wLDAg\n KzEsMTY3OSBAQAorJVBERi0xLjUNJeLjz9MNCisxIDAgb2JqDTw8L01ldGFk\n YXR'") @@ -113,19 +121,19 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do def setup_project issue = create(:issue, assignee: user) - label = create(:label) snippet = create(:project_snippet) release = create(:release) project = create(:project, :public, issues: [issue], - labels: [label], snippets: [snippet], releases: [release] ) - - merge_request = create(:merge_request, source_project: project) + label = create(:label, project: project) + create(:label_link, label: label, target: issue) + milestone = create(:milestone, project: project) + merge_request = create(:merge_request, source_project: project, milestone: milestone) commit_status = create(:commit_status, project: project) ci_pipeline = create(:ci_pipeline, @@ -135,7 +143,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do statuses: [commit_status]) create(:ci_build, pipeline: ci_pipeline, project: project) - milestone = create(:milestone, project: project) + create(:milestone, project: project) create(:note, noteable: issue, project: project) create(:note, noteable: merge_request, project: project) create(:note, noteable: snippet, project: project) -- cgit v1.2.1 From 81495528f90b009bd4e649fbfd2b0ff951032fc4 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 1 Aug 2016 11:07:06 +0200 Subject: refactored wait_for_archived_file method --- lib/gitlab/import_export/file_importer.rb | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/lib/gitlab/import_export/file_importer.rb b/lib/gitlab/import_export/file_importer.rb index 4b5f1f26286..eca6e5b6d51 100644 --- a/lib/gitlab/import_export/file_importer.rb +++ b/lib/gitlab/import_export/file_importer.rb @@ -30,14 +30,12 @@ module Gitlab # Exponentially sleep until I/O finishes copying the file def wait_for_archived_file MAX_RETRIES.times do |retry_number| - if File.exist?(@archive_file) - yield + break if File.exist?(@archive_file) - return true - else - sleep(2**retry_number) - end + sleep(2**retry_number) end + + yield end def decompress_archive -- cgit v1.2.1 From 84a3225b0cde0ed2e343864583e7b79d7118e05c Mon Sep 17 00:00:00 2001 From: zs Date: Sun, 24 Jul 2016 01:28:12 +0200 Subject: State specific default sort order for issuables Provide more sensible default sort order for issues and merge requests based on the following table: | type | state | default sort order | |----------------|--------|--------------------| | issues | open | last created | | issues | closed | last updated | | issues | all | last created | | merge requests | open | last created | | merge requests | merged | last updated | | merge requests | closed | last updated | | merge requests | all | last created | --- CHANGELOG | 1 + app/controllers/application_controller.rb | 56 ------- app/controllers/concerns/issuable_collections.rb | 79 ++++++++++ app/controllers/concerns/issues_action.rb | 10 +- app/controllers/concerns/merge_requests_action.rb | 10 +- app/controllers/projects/issues_controller.rb | 3 +- .../projects/merge_requests_controller.rb | 3 +- app/finders/issuable_finder.rb | 2 +- app/helpers/application_helper.rb | 1 - app/helpers/sorting_helper.rb | 4 +- app/views/projects/issues/index.html.haml | 8 +- spec/features/issuables/default_sort_order_spec.rb | 171 +++++++++++++++++++++ spec/features/issues_spec.rb | 21 +-- .../user_lists_merge_requests_spec.rb | 33 ++-- spec/support/issue_helpers.rb | 13 ++ spec/support/merge_request_helpers.rb | 13 ++ 16 files changed, 322 insertions(+), 106 deletions(-) create mode 100644 app/controllers/concerns/issuable_collections.rb create mode 100644 spec/features/issuables/default_sort_order_spec.rb create mode 100644 spec/support/issue_helpers.rb create mode 100644 spec/support/merge_request_helpers.rb diff --git a/CHANGELOG b/CHANGELOG index 2b04c15b827..dc6bf3c0cc2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,6 +32,7 @@ v 8.11.0 (unreleased) - Make error pages responsive (Takuya Noguchi) - Change requests_profiles resource constraint to catch virtually any file - Reduce number of queries made for merge_requests/:id/diffs + - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) v 8.10.3 (unreleased) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index a1004d9bcea..634d36a4467 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -243,42 +243,6 @@ class ApplicationController < ActionController::Base end end - def set_filters_params - set_default_sort - - params[:scope] = 'all' if params[:scope].blank? - params[:state] = 'opened' if params[:state].blank? - - @sort = params[:sort] - @filter_params = params.dup - - if @project - @filter_params[:project_id] = @project.id - elsif @group - @filter_params[:group_id] = @group.id - else - # TODO: this filter ignore issues/mr created in public or - # internal repos where you are not a member. Enable this filter - # or improve current implementation to filter only issues you - # created or assigned or mentioned - # @filter_params[:authorized_only] = true - end - - @filter_params - end - - def get_issues_collection - set_filters_params - @issuable_finder = IssuesFinder.new(current_user, @filter_params) - @issuable_finder.execute - end - - def get_merge_requests_collection - set_filters_params - @issuable_finder = MergeRequestsFinder.new(current_user, @filter_params) - @issuable_finder.execute - end - def import_sources_enabled? !current_application_settings.import_sources.empty? end @@ -363,24 +327,4 @@ class ApplicationController < ActionController::Base def u2f_app_id request.base_url end - - private - - def set_default_sort - key = if is_a_listing_page_for?('issues') || is_a_listing_page_for?('merge_requests') - 'issuable_sort' - end - - cookies[key] = params[:sort] if key && params[:sort].present? - params[:sort] = cookies[key] if key - params[:sort] ||= 'id_desc' - end - - def is_a_listing_page_for?(page_type) - controller_name, action_name = params.values_at(:controller, :action) - - (controller_name == "projects/#{page_type}" && action_name == 'index') || - (controller_name == 'groups' && action_name == page_type) || - (controller_name == 'dashboard' && action_name == page_type) - end end diff --git a/app/controllers/concerns/issuable_collections.rb b/app/controllers/concerns/issuable_collections.rb new file mode 100644 index 00000000000..c802922e0af --- /dev/null +++ b/app/controllers/concerns/issuable_collections.rb @@ -0,0 +1,79 @@ +module IssuableCollections + extend ActiveSupport::Concern + include SortingHelper + + included do + helper_method :issues_finder + helper_method :merge_requests_finder + end + + private + + def issues_collection + issues_finder.execute + end + + def merge_requests_collection + merge_requests_finder.execute + end + + def issues_finder + @issues_finder ||= issuable_finder_for(IssuesFinder) + end + + def merge_requests_finder + @merge_requests_finder ||= issuable_finder_for(MergeRequestsFinder) + end + + def issuable_finder_for(finder_class) + finder_class.new(current_user, filter_params) + end + + def filter_params + set_sort_order_from_cookie + set_default_scope + set_default_state + + @filter_params = params.dup + @filter_params[:sort] ||= default_sort_order + + @sort = @filter_params[:sort] + + if @project + @filter_params[:project_id] = @project.id + elsif @group + @filter_params[:group_id] = @group.id + else + # TODO: this filter ignore issues/mr created in public or + # internal repos where you are not a member. Enable this filter + # or improve current implementation to filter only issues you + # created or assigned or mentioned + # @filter_params[:authorized_only] = true + end + + @filter_params + end + + def set_default_scope + params[:scope] = 'all' if params[:scope].blank? + end + + def set_default_state + params[:state] = 'opened' if params[:state].blank? + end + + def set_sort_order_from_cookie + key = 'issuable_sort' + + cookies[key] = params[:sort] if params[:sort].present? + params[:sort] = cookies[key] + end + + def default_sort_order + case params[:state] + when 'opened', 'all' then sort_value_recently_created + when 'merged', 'closed' then sort_value_recently_updated + else sort_value_recently_created + end + end +end diff --git a/app/controllers/concerns/issues_action.rb b/app/controllers/concerns/issues_action.rb index 4feabc32b1c..b89fb94be6e 100644 --- a/app/controllers/concerns/issues_action.rb +++ b/app/controllers/concerns/issues_action.rb @@ -1,12 +1,14 @@ module IssuesAction extend ActiveSupport::Concern + include IssuableCollections def issues - @issues = get_issues_collection.non_archived - @issues = @issues.page(params[:page]) - @issues = @issues.preload(:author, :project) + @label = issues_finder.labels.first - @label = @issuable_finder.labels.first + @issues = issues_collection + .non_archived + .preload(:author, :project) + .page(params[:page]) respond_to do |format| format.html diff --git a/app/controllers/concerns/merge_requests_action.rb b/app/controllers/concerns/merge_requests_action.rb index 06a6b065e7e..a1b0eee37f9 100644 --- a/app/controllers/concerns/merge_requests_action.rb +++ b/app/controllers/concerns/merge_requests_action.rb @@ -1,11 +1,13 @@ module MergeRequestsAction extend ActiveSupport::Concern + include IssuableCollections def merge_requests - @merge_requests = get_merge_requests_collection.non_archived - @merge_requests = @merge_requests.page(params[:page]) - @merge_requests = @merge_requests.preload(:author, :target_project) + @label = merge_requests_finder.labels.first - @label = @issuable_finder.labels.first + @merge_requests = merge_requests_collection + .non_archived + .preload(:author, :target_project) + .page(params[:page]) end end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 3c6f29ac0ba..7f5c3ff3d6a 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -3,6 +3,7 @@ class Projects::IssuesController < Projects::ApplicationController include ToggleSubscriptionAction include IssuableActions include ToggleAwardEmoji + include IssuableCollections before_action :module_enabled before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests, @@ -24,7 +25,7 @@ class Projects::IssuesController < Projects::ApplicationController def index terms = params['issue_search'] - @issues = get_issues_collection + @issues = issues_collection if terms.present? if terms =~ /\A#(\d+)\z/ diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 47c21a18b33..03166294ddd 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -5,6 +5,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController include IssuableActions include NotesHelper include ToggleAwardEmoji + include IssuableCollections before_action :module_enabled before_action :merge_request, only: [ @@ -29,7 +30,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController def index terms = params['issue_search'] - @merge_requests = get_merge_requests_collection + @merge_requests = merge_requests_collection if terms.present? if terms =~ /\A[#!](\d+)\z/ diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index a0932712bd0..33daac0399e 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -109,7 +109,7 @@ class IssuableFinder scope.where(title: params[:milestone_title]) else - nil + Milestone.none end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 03495cf5ec4..50de93d4bdf 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -245,7 +245,6 @@ module ApplicationHelper milestone_title: params[:milestone_title], assignee_id: params[:assignee_id], author_id: params[:author_id], - sort: params[:sort], issue_search: params[:issue_search], label_name: params[:label_name] } diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb index d86f1999f5c..e1c0b497550 100644 --- a/app/helpers/sorting_helper.rb +++ b/app/helpers/sorting_helper.rb @@ -102,11 +102,11 @@ module SortingHelper end def sort_value_oldest_created - 'id_asc' + 'created_asc' end def sort_value_recently_created - 'id_desc' + 'created_desc' end def sort_value_milestone_soon diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index d0edd2f22ec..1a87045aa60 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -19,7 +19,13 @@ Subscribe = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project) - if can? current_user, :create_issue, @project - = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do + = link_to new_namespace_project_issue_path(@project.namespace, + @project, + issue: { assignee_id: issues_finder.assignee.try(:id), + milestone_id: issues_finder.milestones.first.try(:id) }), + class: "btn btn-new", + title: "New Issue", + id: "new_issue_link" do New Issue = render 'shared/issuable/filter', type: :issues diff --git a/spec/features/issuables/default_sort_order_spec.rb b/spec/features/issuables/default_sort_order_spec.rb new file mode 100644 index 00000000000..0d495cd04aa --- /dev/null +++ b/spec/features/issuables/default_sort_order_spec.rb @@ -0,0 +1,171 @@ +require 'spec_helper' + +describe 'Projects > Issuables > Default sort order', feature: true do + let(:project) { create(:empty_project, :public) } + + let(:first_created_issuable) { issuables.order_created_asc.first } + let(:last_created_issuable) { issuables.order_created_desc.first } + + let(:first_updated_issuable) { issuables.order_updated_asc.first } + let(:last_updated_issuable) { issuables.order_updated_desc.first } + + context 'for merge requests' do + include MergeRequestHelpers + + let!(:issuables) do + timestamps = [{ created_at: 3.minutes.ago, updated_at: 20.seconds.ago }, + { created_at: 2.minutes.ago, updated_at: 30.seconds.ago }, + { created_at: 4.minutes.ago, updated_at: 10.seconds.ago }] + + timestamps.each_with_index do |ts, i| + create issuable_type, { title: "#{issuable_type}_#{i}", + source_branch: "#{issuable_type}_#{i}", + source_project: project }.merge(ts) + end + + MergeRequest.all + end + + context 'in the "merge requests" tab', js: true do + let(:issuable_type) { :merge_request } + + it 'is "last created"' do + visit_merge_requests project + + expect(first_merge_request).to include(last_created_issuable.title) + expect(last_merge_request).to include(first_created_issuable.title) + end + end + + context 'in the "merge requests / open" tab', js: true do + let(:issuable_type) { :merge_request } + + it 'is "last created"' do + visit_merge_requests_with_state(project, 'open') + + expect(selected_sort_order).to eq('last created') + expect(first_merge_request).to include(last_created_issuable.title) + expect(last_merge_request).to include(first_created_issuable.title) + end + end + + context 'in the "merge requests / merged" tab', js: true do + let(:issuable_type) { :merged_merge_request } + + it 'is "last updated"' do + visit_merge_requests_with_state(project, 'merged') + + expect(selected_sort_order).to eq('last updated') + expect(first_merge_request).to include(last_updated_issuable.title) + expect(last_merge_request).to include(first_updated_issuable.title) + end + end + + context 'in the "merge requests / closed" tab', js: true do + let(:issuable_type) { :closed_merge_request } + + it 'is "last updated"' do + visit_merge_requests_with_state(project, 'closed') + + expect(selected_sort_order).to eq('last updated') + expect(first_merge_request).to include(last_updated_issuable.title) + expect(last_merge_request).to include(first_updated_issuable.title) + end + end + + context 'in the "merge requests / all" tab', js: true do + let(:issuable_type) { :merge_request } + + it 'is "last created"' do + visit_merge_requests_with_state(project, 'all') + + expect(selected_sort_order).to eq('last created') + expect(first_merge_request).to include(last_created_issuable.title) + expect(last_merge_request).to include(first_created_issuable.title) + end + end + end + + context 'for issues' do + include IssueHelpers + + let!(:issuables) do + timestamps = [{ created_at: 3.minutes.ago, updated_at: 20.seconds.ago }, + { created_at: 2.minutes.ago, updated_at: 30.seconds.ago }, + { created_at: 4.minutes.ago, updated_at: 10.seconds.ago }] + + timestamps.each_with_index do |ts, i| + create issuable_type, { title: "#{issuable_type}_#{i}", + project: project }.merge(ts) + end + + Issue.all + end + + context 'in the "issues" tab', js: true do + let(:issuable_type) { :issue } + + it 'is "last created"' do + visit_issues project + + expect(selected_sort_order).to eq('last created') + expect(first_issue).to include(last_created_issuable.title) + expect(last_issue).to include(first_created_issuable.title) + end + end + + context 'in the "issues / open" tab', js: true do + let(:issuable_type) { :issue } + + it 'is "last created"' do + visit_issues_with_state(project, 'open') + + expect(selected_sort_order).to eq('last created') + expect(first_issue).to include(last_created_issuable.title) + expect(last_issue).to include(first_created_issuable.title) + end + end + + context 'in the "issues / closed" tab', js: true do + let(:issuable_type) { :closed_issue } + + it 'is "last updated"' do + visit_issues_with_state(project, 'closed') + + expect(selected_sort_order).to eq('last updated') + expect(first_issue).to include(last_updated_issuable.title) + expect(last_issue).to include(first_updated_issuable.title) + end + end + + context 'in the "issues / all" tab', js: true do + let(:issuable_type) { :issue } + + it 'is "last created"' do + visit_issues_with_state(project, 'all') + + expect(selected_sort_order).to eq('last created') + expect(first_issue).to include(last_created_issuable.title) + expect(last_issue).to include(first_created_issuable.title) + end + end + end + + def selected_sort_order + find('.pull-right .dropdown button').text.downcase + end + + def visit_merge_requests_with_state(project, state) + visit_merge_requests project + visit_issuables_with_state state + end + + def visit_issues_with_state(project, state) + visit_issues project + visit_issuables_with_state state + end + + def visit_issuables_with_state(state) + within('.issues-state-filters') { find("span", text: state.titleize).click } + end +end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index 93dcb2ec3fc..9c92b52898c 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe 'Issues', feature: true do + include IssueHelpers include SortingHelper let(:project) { create(:project) } @@ -186,15 +187,15 @@ describe 'Issues', feature: true do it 'sorts by newest' do visit namespace_project_issues_path(project.namespace, project, sort: sort_value_recently_created) - expect(first_issue).to include('baz') - expect(last_issue).to include('foo') + expect(first_issue).to include('foo') + expect(last_issue).to include('baz') end it 'sorts by oldest' do visit namespace_project_issues_path(project.namespace, project, sort: sort_value_oldest_created) - expect(first_issue).to include('foo') - expect(last_issue).to include('baz') + expect(first_issue).to include('baz') + expect(last_issue).to include('foo') end it 'sorts by most recently updated' do @@ -350,8 +351,8 @@ describe 'Issues', feature: true do sort: sort_value_oldest_created, assignee_id: user2.id) - expect(first_issue).to include('foo') - expect(last_issue).to include('bar') + expect(first_issue).to include('bar') + expect(last_issue).to include('foo') expect(page).not_to have_content 'baz' end end @@ -590,14 +591,6 @@ describe 'Issues', feature: true do end end - def first_issue - page.all('ul.issues-list > li').first.text - end - - def last_issue - page.all('ul.issues-list > li').last.text - end - def drop_in_dropzone(file_path) # Generate a fake input selector page.execute_script <<-JS diff --git a/spec/features/merge_requests/user_lists_merge_requests_spec.rb b/spec/features/merge_requests/user_lists_merge_requests_spec.rb index 1c130057c56..cabb8e455f9 100644 --- a/spec/features/merge_requests/user_lists_merge_requests_spec.rb +++ b/spec/features/merge_requests/user_lists_merge_requests_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe 'Projects > Merge requests > User lists merge requests', feature: true do + include MergeRequestHelpers include SortingHelper let(:project) { create(:project, :public) } @@ -23,10 +24,12 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true milestone: create(:milestone, due_date: '2013-12-12'), created_at: 2.minutes.ago, updated_at: 2.minutes.ago) + # lfs in itself is not a great choice for the title if one wants to match the whole body content later on + # just think about the scenario when faker generates 'Chester Runolfsson' as the user's name create(:merge_request, - title: 'lfs', + title: 'merge_lfs', source_project: project, - source_branch: 'lfs', + source_branch: 'merge_lfs', created_at: 3.minutes.ago, updated_at: 10.seconds.ago) end @@ -35,7 +38,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true visit_merge_requests(project, assignee_id: IssuableFinder::NONE) expect(current_path).to eq(namespace_project_merge_requests_path(project.namespace, project)) - expect(page).to have_content 'lfs' + expect(page).to have_content 'merge_lfs' expect(page).not_to have_content 'fix' expect(page).not_to have_content 'markdown' expect(count_merge_requests).to eq(1) @@ -44,7 +47,7 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true it 'filters on a specific assignee' do visit_merge_requests(project, assignee_id: user.id) - expect(page).not_to have_content 'lfs' + expect(page).not_to have_content 'merge_lfs' expect(page).to have_content 'fix' expect(page).to have_content 'markdown' expect(count_merge_requests).to eq(2) @@ -53,23 +56,23 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true it 'sorts by newest' do visit_merge_requests(project, sort: sort_value_recently_created) - expect(first_merge_request).to include('lfs') - expect(last_merge_request).to include('fix') + expect(first_merge_request).to include('fix') + expect(last_merge_request).to include('merge_lfs') expect(count_merge_requests).to eq(3) end it 'sorts by oldest' do visit_merge_requests(project, sort: sort_value_oldest_created) - expect(first_merge_request).to include('fix') - expect(last_merge_request).to include('lfs') + expect(first_merge_request).to include('merge_lfs') + expect(last_merge_request).to include('fix') expect(count_merge_requests).to eq(3) end it 'sorts by last updated' do visit_merge_requests(project, sort: sort_value_recently_updated) - expect(first_merge_request).to include('lfs') + expect(first_merge_request).to include('merge_lfs') expect(count_merge_requests).to eq(3) end @@ -143,18 +146,6 @@ describe 'Projects > Merge requests > User lists merge requests', feature: true end end - def visit_merge_requests(project, opts = {}) - visit namespace_project_merge_requests_path(project.namespace, project, opts) - end - - def first_merge_request - page.all('ul.mr-list > li').first.text - end - - def last_merge_request - page.all('ul.mr-list > li').last.text - end - def count_merge_requests page.all('ul.mr-list > li').count end diff --git a/spec/support/issue_helpers.rb b/spec/support/issue_helpers.rb new file mode 100644 index 00000000000..85241793743 --- /dev/null +++ b/spec/support/issue_helpers.rb @@ -0,0 +1,13 @@ +module IssueHelpers + def visit_issues(project, opts = {}) + visit namespace_project_issues_path project.namespace, project, opts + end + + def first_issue + page.all('ul.issues-list > li').first.text + end + + def last_issue + page.all('ul.issues-list > li').last.text + end +end diff --git a/spec/support/merge_request_helpers.rb b/spec/support/merge_request_helpers.rb new file mode 100644 index 00000000000..d5801c8272f --- /dev/null +++ b/spec/support/merge_request_helpers.rb @@ -0,0 +1,13 @@ +module MergeRequestHelpers + def visit_merge_requests(project, opts = {}) + visit namespace_project_merge_requests_path project.namespace, project, opts + end + + def first_merge_request + page.all('ul.mr-list > li').first.text + end + + def last_merge_request + page.all('ul.mr-list > li').last.text + end +end -- cgit v1.2.1 From 44eec823fb68238705eb932dd14aa211b730a316 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Mon, 1 Aug 2016 12:29:40 +0200 Subject: Avoid line_code and position calculation on line partial for plain view --- CHANGELOG | 1 + app/views/projects/diffs/_line.html.haml | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9075972e6d0..7eef5996031 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,6 +32,7 @@ v 8.11.0 (unreleased) - Add the `sprockets-es6` gem - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) - Profile requests when a header is passed + - Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab. - Add commit stats in commit api. !5517 (dixpac) - Make error pages responsive (Takuya Noguchi) - Change requests_profiles resource constraint to catch virtually any file diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index 5a8a131d10c..4d3af905b58 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -1,8 +1,7 @@ - plain = local_assigns.fetch(:plain, false) -- line_code = diff_file.line_code(line) -- position = diff_file.position(line) - type = line.type -%tr.line_holder{ id: line_code, class: type } +- line_code = diff_file.line_code(line) unless plain +%tr.line_holder{ plain ? { class: type} : { class: type, id: line_code } } - case type - when 'match' = render "projects/diffs/match_line", { line: line.text, @@ -24,4 +23,4 @@ = link_text - else %a{href: "##{line_code}", data: { linenumber: link_text }} - %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, position, type) unless plain) }= diff_line_content(line.text, type) + %td.line_content.noteable_line{ class: type, data: (diff_view_line_data(line_code, diff_file.position(line), type) unless plain) }= diff_line_content(line.text, type) -- cgit v1.2.1 From 3fe18525ddca414017d330e992999bad05002fa8 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 31 Jul 2016 20:51:12 -0700 Subject: Trim extra displayed carriage returns in diffs and files with CRLFs Closes #20440 --- CHANGELOG | 1 + lib/rouge/formatters/html_gitlab.rb | 2 +- spec/lib/gitlab/highlight_spec.rb | 12 ++++++++++++ spec/support/test_env.rb | 3 ++- 4 files changed, 16 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 95aeda301f7..0061bba03b9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -44,6 +44,7 @@ v 8.10.3 (unreleased) - Properly abort a merge when merge conflicts occur - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. - Fix Import/Export issue importing milestones and labels not associated properly + - Trim extra displayed carriage returns in diffs and files with CRLFs v 8.10.2 - User can now search branches by name. !5144 diff --git a/lib/rouge/formatters/html_gitlab.rb b/lib/rouge/formatters/html_gitlab.rb index f818dc78d34..4edfd015074 100644 --- a/lib/rouge/formatters/html_gitlab.rb +++ b/lib/rouge/formatters/html_gitlab.rb @@ -18,7 +18,7 @@ module Rouge is_first = false yield %() - line.each { |token, value| yield span(token, value) } + line.each { |token, value| yield span(token, value.chomp) } yield %() @line_number += 1 diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index 364532e94e3..80a9473d6aa 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -17,6 +17,18 @@ describe Gitlab::Highlight, lib: true do expect(lines[21]).to eq(%Q{ unless File.directory?(path)\n}) expect(lines[26]).to eq(%Q{ @cmd_status = 0\n}) end + + describe 'with CRLF' do + let(:branch) { 'crlf-diff' } + let(:blob) { repository.blob_at_branch(branch, path) } + let(:lines) do + Gitlab::Highlight.highlight_lines(project.repository, 'crlf-diff-test', 'files/whitespace') + end + + it 'strips extra LFs' do + expect(lines[0]).to eq("test ") + end + end end describe 'custom highlighting from .gitattributes' do diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 3735abe2302..4561aa9644d 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -21,7 +21,8 @@ module TestEnv 'expand-collapse-diffs' => '4842455', 'expand-collapse-files' => '025db92', 'expand-collapse-lines' => '238e82d', - 'video' => '8879059' + 'video' => '8879059', + 'crlf-diff' => '5938907' } # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily -- cgit v1.2.1 From fe25d1d5cfdd8c52854b459b49bbead1a608822c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 1 Aug 2016 13:16:04 +0200 Subject: Fix specs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/finders/branches_finder_spec.rb | 2 +- spec/lib/gitlab/highlight_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb index 6ea9a3a3ec5..482caeee64a 100644 --- a/spec/finders/branches_finder_spec.rb +++ b/spec/finders/branches_finder_spec.rb @@ -20,7 +20,7 @@ describe BranchesFinder do result = branches_finder.execute - expect(result.first.name).to eq('video') + expect(result.first.name).to eq('crlf-diff') end it 'sorts by last_updated' do diff --git a/spec/lib/gitlab/highlight_spec.rb b/spec/lib/gitlab/highlight_spec.rb index 80a9473d6aa..fc021416d92 100644 --- a/spec/lib/gitlab/highlight_spec.rb +++ b/spec/lib/gitlab/highlight_spec.rb @@ -22,7 +22,7 @@ describe Gitlab::Highlight, lib: true do let(:branch) { 'crlf-diff' } let(:blob) { repository.blob_at_branch(branch, path) } let(:lines) do - Gitlab::Highlight.highlight_lines(project.repository, 'crlf-diff-test', 'files/whitespace') + Gitlab::Highlight.highlight_lines(project.repository, 'crlf-diff', 'files/whitespace') end it 'strips extra LFs' do -- cgit v1.2.1 From 2dcfaa19833256f7638153a499ed907f86949dd6 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Mon, 1 Aug 2016 14:08:43 +0200 Subject: Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration --- CHANGELOG | 1 + lib/gitlab/metrics.rb | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c9658e4ba5c..f3245f8799f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.11.0 (unreleased) - Make branches sortable without push permission !5462 (winniehell) - Check for Ci::Build artifacts at database level on pipeline partial - Make "New issue" button in Issue page less obtrusive !5457 (winniehell) + - Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) - Add the `sprockets-es6` gem - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb index 081576a440c..41fcd971c22 100644 --- a/lib/gitlab/metrics.rb +++ b/lib/gitlab/metrics.rb @@ -141,10 +141,9 @@ module Gitlab end end + # Allow access from other metrics related middlewares def self.current_transaction Transaction.current end - - private_class_method :current_transaction end end -- cgit v1.2.1 From 2e06800bfdb1fa46602beffce6ea8fb282e8a29a Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Mon, 1 Aug 2016 14:23:41 +0200 Subject: Fix RequestProfiler::Middleware error when code is reloaded in development Closes #20452 --- CHANGELOG | 1 + config/initializers/request_profiler.rb | 2 ++ lib/gitlab/request_profiler/middleware.rb | 1 + 3 files changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 387006eec4e..d25ff7da27f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -40,6 +40,7 @@ v 8.11.0 (unreleased) - Change requests_profiles resource constraint to catch virtually any file - Reduce number of queries made for merge_requests/:id/diffs - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) + - Fix RequestProfiler::Middleware error when code is reloaded in development v 8.10.3 (unreleased) - Fix hooks missing on imported GitLab projects diff --git a/config/initializers/request_profiler.rb b/config/initializers/request_profiler.rb index fb5a7b8372e..a9aa802681a 100644 --- a/config/initializers/request_profiler.rb +++ b/config/initializers/request_profiler.rb @@ -1,3 +1,5 @@ +require 'gitlab/request_profiler/middleware' + Rails.application.configure do |config| config.middleware.use(Gitlab::RequestProfiler::Middleware) end diff --git a/lib/gitlab/request_profiler/middleware.rb b/lib/gitlab/request_profiler/middleware.rb index 8da8b754975..0c54f2dd71f 100644 --- a/lib/gitlab/request_profiler/middleware.rb +++ b/lib/gitlab/request_profiler/middleware.rb @@ -1,4 +1,5 @@ require 'ruby-prof' +require 'gitlab/request_profiler' module Gitlab module RequestProfiler -- cgit v1.2.1 From 7e77b1fd39f60d5b311bdaa350acbf005a4b398e Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 1 Aug 2016 12:45:27 +0000 Subject: Update CHANGELOG --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 4095eaa6e63..9075972e6d0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -41,7 +41,6 @@ v 8.10.3 (unreleased) - Fix hooks missing on imported GitLab projects - Properly abort a merge when merge conflicts occur - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. - - Fix timing problems running imports on production v 8.10.2 - User can now search branches by name. !5144 -- cgit v1.2.1 From 8bcdc4b185d70bfb2a50d22ec99a26c2c463a2f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 1 Aug 2016 13:52:51 +0000 Subject: API methods should be documented using Grape's DSL See https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2397#note_13491048 --- doc/development/doc_styleguide.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 6ee7b3cfeeb..3a3597bccaa 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -244,6 +244,12 @@ In this case: Here is a list of must-have items. Use them in the exact order that appears on this document. Further explanation is given below. +- Every method must be described using [Grape's DSL](https://github.com/ruby-grape/grape/tree/v0.13.0#describing-methods) + (see https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/api/environments.rb + for a good example): + - `desc` for the method summary (you can pass it a block for additional details) + - `params` for the method params (this acts as description **and** validation + of the params) - Every method must have the REST API request. For example: ``` -- cgit v1.2.1 From 9845079950da010b8a4c07777f984aaf02642ad0 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Fri, 29 Jul 2016 16:20:00 -0300 Subject: Fix search results for notes without commits --- CHANGELOG | 1 + app/views/search/results/_note.html.haml | 8 ++++++-- spec/features/search_spec.rb | 20 ++++++++++++++++++++ 3 files changed, 27 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 39b77460deb..e46befcec2a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,7 @@ v 8.11.0 (unreleased) - Check for Ci::Build artifacts at database level on pipeline partial - Make "New issue" button in Issue page less obtrusive !5457 (winniehell) - Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration + - Fix search for notes which belongs to deleted objects - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) - Add the `sprockets-es6` gem - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml index 8163aff43b6..e0400083870 100644 --- a/app/views/search/results/_note.html.haml +++ b/app/views/search/results/_note.html.haml @@ -1,6 +1,7 @@ - project = note.project - note_url = Gitlab::UrlBuilder.build(note) -- noteable_identifier = note.noteable.try(:iid) || note.noteable.id +- noteable_identifier = note.noteable.try(:iid) || note.noteable.try(:id) + .search-result-row %h5.note-search-caption.str-truncated %i.fa.fa-comment @@ -10,7 +11,10 @@ · - if note.for_commit? - = link_to "Commit #{truncate_sha(note.commit_id)}", note_url + = link_to_if(noteable_identifier, "Commit #{truncate_sha(note.commit_id)}", note_url) do + = truncate_sha(note.commit_id) + %span.light Commit deleted + - else %span #{note.noteable_type.titleize} ##{noteable_identifier} · diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index d0a301038c4..09f70cd3b00 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -28,6 +28,26 @@ describe "Search", feature: true do end context 'search for comments' do + context 'when comment belongs to a invalid commit' do + let(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'Bug here') } + + before { note.update_attributes(commit_id: 12345678) } + + it 'finds comment' do + visit namespace_project_path(project.namespace, project) + + page.within '.search' do + fill_in 'search', with: note.note + click_button 'Go' + end + + click_link 'Comments' + + expect(page).to have_text("Commit deleted") + expect(page).to have_text("12345678") + end + end + it 'finds a snippet' do snippet = create(:project_snippet, :private, project: project, author: user, title: 'Some title') note = create(:note, -- cgit v1.2.1 From af7ce322bdbaf74eaf54eac92c2ed5183e0d8e9c Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Wed, 27 Jul 2016 15:39:45 -0400 Subject: webhooks: include old revision in MR update events --- CHANGELOG | 1 + app/services/merge_requests/base_service.rb | 9 ++++++--- app/services/merge_requests/refresh_service.rb | 2 +- spec/services/merge_requests/refresh_service_spec.rb | 6 +++--- 4 files changed, 11 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 39b77460deb..4285a548e64 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ v 8.11.0 (unreleased) - Optimize checking if a user has read access to a list of issues !5370 - Nokogiri's various parsing methods are now instrumented - Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363 + - Include old revision in merge request update hooks (Ben Boeckel) - Add build event color in HipChat messages (David Eisner) - Make fork counter always clickable. !5463 (winniehell) - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333 diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index bc3606a14c2..ba424b09463 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -17,16 +17,19 @@ module MergeRequests end end - def hook_data(merge_request, action) + def hook_data(merge_request, action, oldrev = nil) hook_data = merge_request.to_hook_data(current_user) hook_data[:object_attributes][:url] = Gitlab::UrlBuilder.build(merge_request) hook_data[:object_attributes][:action] = action + if oldrev && !Gitlab::Git.blank_ref?(oldrev) + hook_data[:object_attributes][:oldrev] = oldrev + end hook_data end - def execute_hooks(merge_request, action = 'open') + def execute_hooks(merge_request, action = 'open', oldrev = nil) if merge_request.project - merge_data = hook_data(merge_request, action) + merge_data = hook_data(merge_request, action, oldrev) merge_request.project.execute_hooks(merge_data, :merge_request_hooks) merge_request.project.execute_services(merge_data, :merge_request_hooks) end diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index 1daf6bbf553..5cedd6f11d9 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -137,7 +137,7 @@ module MergeRequests # Call merge request webhook with update branches def execute_mr_web_hooks merge_requests_for_source_branch.each do |merge_request| - execute_hooks(merge_request, 'update') + execute_hooks(merge_request, 'update', @oldrev) end end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index ce643b3f860..781ee7ffed3 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -57,7 +57,7 @@ describe MergeRequests::RefreshService, services: true do it 'should execute hooks with update action' do expect(refresh_service).to have_received(:execute_hooks). - with(@merge_request, 'update') + with(@merge_request, 'update', @oldrev) end it { expect(@merge_request.notes).not_to be_empty } @@ -113,7 +113,7 @@ describe MergeRequests::RefreshService, services: true do it 'should execute hooks with update action' do expect(refresh_service).to have_received(:execute_hooks). - with(@fork_merge_request, 'update') + with(@fork_merge_request, 'update', @oldrev) end it { expect(@merge_request.notes).to be_empty } @@ -158,7 +158,7 @@ describe MergeRequests::RefreshService, services: true do it 'refreshes the merge request' do expect(refresh_service).to receive(:execute_hooks). - with(@fork_merge_request, 'update') + with(@fork_merge_request, 'update', Gitlab::Git::BLANK_SHA) allow_any_instance_of(Repository).to receive(:merge_base).and_return(@oldrev) refresh_service.execute(Gitlab::Git::BLANK_SHA, @newrev, 'refs/heads/master') -- cgit v1.2.1 From d61b92a350f50fa1a443f0a180da401684f5cdca Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 1 Aug 2016 12:20:38 -0500 Subject: Convert image diff background image to CSS --- CHANGELOG | 1 + app/assets/images/trans_bg.gif | Bin 49 -> 0 bytes app/assets/stylesheets/pages/diff.scss | 5 ++++- 3 files changed, 5 insertions(+), 1 deletion(-) delete mode 100644 app/assets/images/trans_bg.gif diff --git a/CHANGELOG b/CHANGELOG index 39b77460deb..71e837ad291 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,6 +29,7 @@ v 8.11.0 (unreleased) - Bump gitlab_git to speedup DiffCollection iterations - Make branches sortable without push permission !5462 (winniehell) - Check for Ci::Build artifacts at database level on pipeline partial + - Convert image diff background image to CSS (ClemMakesApps) - Make "New issue" button in Issue page less obtrusive !5457 (winniehell) - Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) diff --git a/app/assets/images/trans_bg.gif b/app/assets/images/trans_bg.gif deleted file mode 100644 index 1a1c9c15ec7..00000000000 Binary files a/app/assets/images/trans_bg.gif and /dev/null differ diff --git a/app/assets/stylesheets/pages/diff.scss b/app/assets/stylesheets/pages/diff.scss index 21b1c223c88..21cee2e3a70 100644 --- a/app/assets/stylesheets/pages/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -164,7 +164,10 @@ line-height: 0; img { border: 1px solid #fff; - background: image-url('trans_bg.gif'); + background-image: linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%), + linear-gradient(45deg, #e5e5e5 25%, transparent 25%, transparent 75%, #e5e5e5 75%, #e5e5e5 100%); + background-size: 10px 10px; + background-position: 0 0, 5px 5px; max-width: 100%; } &.deleted { -- cgit v1.2.1 From fe23512c9d0cc8348cb4221fc110b53748a6996b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 1 Aug 2016 15:09:03 -0500 Subject: Convert switch icon into icon font --- CHANGELOG | 1 + app/assets/images/switch_icon.png | Bin 231 -> 0 bytes app/assets/stylesheets/pages/commits.scss | 2 -- app/views/projects/compare/_form.html.haml | 2 +- 4 files changed, 2 insertions(+), 3 deletions(-) delete mode 100644 app/assets/images/switch_icon.png diff --git a/CHANGELOG b/CHANGELOG index 39b77460deb..e7eec5f3948 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -3,6 +3,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.11.0 (unreleased) - Fix the title of the toggle dropdown button. !5515 (herminiotorres) - Improve diff performance by eliminating redundant checks for text blobs + - Convert switch icon into icon font (ClemMakesApps) - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) - Fix CI status icon link underline (ClemMakesApps) - Cache the commit author in RequestStore to avoid extra lookups in PostReceive diff --git a/app/assets/images/switch_icon.png b/app/assets/images/switch_icon.png deleted file mode 100644 index c6b6c8d9521..00000000000 Binary files a/app/assets/images/switch_icon.png and /dev/null differ diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index 0298577c494..2beef15bbf9 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -1,8 +1,6 @@ .commits-compare-switch { @include btn-default; @include btn-white; - background: image-url("switch_icon.png") no-repeat center center; - text-indent: -9999px; float: left; margin-right: 9px; } diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index af09b3418ea..d79336f5a60 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -1,7 +1,7 @@ = form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' do .clearfix - if params[:to] && params[:from] - = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'} + = link_to icon('exchange'), {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has-tooltip', title: 'Switch base of comparison'} .form-group.dropdown.compare-form-group.js-compare-from-dropdown .input-group.inline-input-group %span.input-group-addon from -- cgit v1.2.1 From 0720b9ce0059feca284404e6fc1ede0cba542fe3 Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Mon, 1 Aug 2016 16:55:50 +0200 Subject: Catch what warden might throw when profiling requests to re-throw it Closes #20488 --- CHANGELOG | 1 + lib/gitlab/request_profiler/middleware.rb | 10 ++++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 3b61e52b2fc..0cff6857c2c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -44,6 +44,7 @@ v 8.11.0 (unreleased) - Reduce number of queries made for merge_requests/:id/diffs - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) - Fix RequestProfiler::Middleware error when code is reloaded in development + - Catch what warden might throw when profiling requests to re-throw it v 8.10.3 (unreleased) - Fix importer for GitHub Pull Requests when a branch was removed diff --git a/lib/gitlab/request_profiler/middleware.rb b/lib/gitlab/request_profiler/middleware.rb index 0c54f2dd71f..4e787dc0656 100644 --- a/lib/gitlab/request_profiler/middleware.rb +++ b/lib/gitlab/request_profiler/middleware.rb @@ -29,7 +29,9 @@ module Gitlab def call_with_profiling(env) ret = nil result = RubyProf::Profile.profile do - ret = @app.call(env) + ret = catch(:warden) do + @app.call(env) + end end printer = RubyProf::CallStackPrinter.new(result) @@ -41,7 +43,11 @@ module Gitlab printer.print(file) end - ret + if ret.is_a?(Array) + ret + else + throw(:warden, ret) + end end end end -- cgit v1.2.1 From 99c02ed53c994fbd71442410c78daf220c6d1ced Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 1 Aug 2016 13:11:45 -0700 Subject: Only use RequestStore in ProjectTeam#max_member_access_for_user if it is active --- app/models/project_team.rb | 9 +++- spec/models/project_team_spec.rb | 101 +++++++++++++++++++++++---------------- 2 files changed, 68 insertions(+), 42 deletions(-) diff --git a/app/models/project_team.rb b/app/models/project_team.rb index fdfaf052730..19fd082534c 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -138,8 +138,13 @@ class ProjectTeam def max_member_access_for_user_ids(user_ids) user_ids = user_ids.uniq key = "max_member_access:#{project.id}" - RequestStore.store[key] ||= {} - access = RequestStore.store[key] + + access = {} + + if RequestStore.active? + RequestStore.store[key] ||= {} + access = RequestStore.store[key] + end # Lookup only the IDs we need user_ids = user_ids - access.keys diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 1f42fbd3385..5eaf0d3b7a6 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -199,48 +199,69 @@ describe ProjectTeam, models: true do end end - describe "#max_member_access_for_users" do - it 'returns correct roles for different users' do - master = create(:user) - reporter = create(:user) - promoted_guest = create(:user) - guest = create(:user) - project = create(:project) + shared_examples_for "#max_member_access_for_users" do |enable_request_store| + describe "#max_member_access_for_users" do + before do + RequestStore.begin! if enable_request_store + end - project.team << [master, :master] - project.team << [reporter, :reporter] - project.team << [promoted_guest, :guest] - project.team << [guest, :guest] + after do + if enable_request_store + RequestStore.end! + RequestStore.clear! + end + end - group = create(:group) - group_developer = create(:user) - second_developer = create(:user) - project.project_group_links.create( - group: group, - group_access: Gitlab::Access::DEVELOPER) - - group.add_master(promoted_guest) - group.add_developer(group_developer) - group.add_developer(second_developer) - - second_group = create(:group) - project.project_group_links.create( - group: second_group, - group_access: Gitlab::Access::MASTER) - second_group.add_master(second_developer) - - users = [master, reporter, promoted_guest, guest, group_developer, second_developer].map(&:id) - - expected = { - master.id => Gitlab::Access::MASTER, - reporter.id => Gitlab::Access::REPORTER, - promoted_guest.id => Gitlab::Access::DEVELOPER, - guest.id => Gitlab::Access::GUEST, - group_developer.id => Gitlab::Access::DEVELOPER, - second_developer.id => Gitlab::Access::MASTER - } - - expect(project.team.max_member_access_for_user_ids(users)).to eq(expected) + it 'returns correct roles for different users' do + master = create(:user) + reporter = create(:user) + promoted_guest = create(:user) + guest = create(:user) + project = create(:project) + + project.team << [master, :master] + project.team << [reporter, :reporter] + project.team << [promoted_guest, :guest] + project.team << [guest, :guest] + + group = create(:group) + group_developer = create(:user) + second_developer = create(:user) + project.project_group_links.create( + group: group, + group_access: Gitlab::Access::DEVELOPER) + + group.add_master(promoted_guest) + group.add_developer(group_developer) + group.add_developer(second_developer) + + second_group = create(:group) + project.project_group_links.create( + group: second_group, + group_access: Gitlab::Access::MASTER) + second_group.add_master(second_developer) + + users = [master, reporter, promoted_guest, guest, group_developer, second_developer].map(&:id) + + expected = { + master.id => Gitlab::Access::MASTER, + reporter.id => Gitlab::Access::REPORTER, + promoted_guest.id => Gitlab::Access::DEVELOPER, + guest.id => Gitlab::Access::GUEST, + group_developer.id => Gitlab::Access::DEVELOPER, + second_developer.id => Gitlab::Access::MASTER + } + + expect(project.team.max_member_access_for_user_ids(users)).to eq(expected) + end end end + + describe '#max_member_access_for_users with RequestStore' do + it_behaves_like "#max_member_access_for_users", true + end + + describe '#max_member_access_for_users without RequestStore' do + it_behaves_like "#max_member_access_for_users", false + end end -- cgit v1.2.1 From 957331bf45e33c5d1ca0331ca6acb56fc8ecdb92 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 1 Aug 2016 15:14:47 -0700 Subject: Update CHANGELOG for 8.10.3 [ci skip] --- CHANGELOG | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 0cff6857c2c..4daf9cd9092 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -46,13 +46,15 @@ v 8.11.0 (unreleased) - Fix RequestProfiler::Middleware error when code is reloaded in development - Catch what warden might throw when profiling requests to re-throw it -v 8.10.3 (unreleased) - - Fix importer for GitHub Pull Requests when a branch was removed - - Fix hooks missing on imported GitLab projects - - Properly abort a merge when merge conflicts occur - - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. - - Fix Import/Export issue importing milestones and labels not associated properly - - Trim extra displayed carriage returns in diffs and files with CRLFs +v 8.10.3 + - Fix Import/Export issue importing milestones and labels not associated properly. !5426 + - Fix timing problems running imports on production. !5523 + - Add a log message when a project is scheduled for destruction for debugging. !5540 + - Fix hooks missing on imported GitLab projects. !5549 + - Properly abort a merge when merge conflicts occur. !5569 + - Fix importer for GitHub Pull Requests when a branch was removed. !5573 + - Ignore invalid IPs in X-Forwarded-For when trusted proxies are configured. !5584 + - Trim extra displayed carriage returns in diffs and files with CRLFs. !5588 v 8.10.2 - User can now search branches by name. !5144 -- cgit v1.2.1 From 6cee51903dc39b8071594d83ac0703ccfe4388fd Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Mon, 1 Aug 2016 18:38:41 -0500 Subject: Update installation guide for 8.11 --- doc/install/installation.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 9bc0dbb5e2a..af8e31a705b 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -269,9 +269,9 @@ sudo usermod -aG redis git ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-10-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 8-11-stable gitlab -**Note:** You can change `8-10-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `8-11-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It -- cgit v1.2.1 From 3f2f4bda456a6e04aca6ab27983b337ddfdef3c5 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sun, 31 Jul 2016 18:37:15 -0700 Subject: Remove delay when hitting Reply... button on page with a lot of discussions --- app/assets/javascripts/gfm_auto_complete.js | 4 ++-- app/assets/javascripts/gl_form.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/gfm_auto_complete.js b/app/assets/javascripts/gfm_auto_complete.js index 41f4c1914f2..2e5b15f4b77 100644 --- a/app/assets/javascripts/gfm_auto_complete.js +++ b/app/assets/javascripts/gfm_auto_complete.js @@ -47,8 +47,8 @@ } } }, - setup: function(wrap) { - this.input = $('.js-gfm-input'); + setup: function(input) { + this.input = input || $('.js-gfm-input'); this.destroyAtWho(); this.setupAtWho(); if (this.dataSource) { diff --git a/app/assets/javascripts/gl_form.js b/app/assets/javascripts/gl_form.js index 6ac7564a848..528a673eb15 100644 --- a/app/assets/javascripts/gl_form.js +++ b/app/assets/javascripts/gl_form.js @@ -21,7 +21,7 @@ this.form.find('.div-dropzone').remove(); this.form.addClass('gfm-form'); disableButtonIfEmptyField(this.form.find('.js-note-text'), this.form.find('.js-comment-button')); - GitLab.GfmAutoComplete.setup(); + GitLab.GfmAutoComplete.setup(this.form.find('.js-gfm-input')); new DropzoneInput(this.form); autosize(this.textarea); this.addEventListeners(); -- cgit v1.2.1 From 6e87ce282db9f5264f77ae2238c1fff23beece1a Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 1 Aug 2016 16:21:37 -0700 Subject: Add changelog item --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 4daf9cd9092..d6f928fe225 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.11.0 (unreleased) - Clean up unused routes (Josef Strzibny) - Add green outline to New Branch button. !5447 (winniehell) - Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects + - Remove delay when hitting "Reply..." button on page with a lot of discussions - Retrieve rendered HTML from cache in one request - Fix renaming repository when name contains invalid chararacters under project settings - Optimize checking if a user has read access to a list of issues !5370 -- cgit v1.2.1 From a70431f874112212cb44b7a104b2e32f440af941 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 1 Aug 2016 16:59:44 -0700 Subject: Redirect to external issue tracker from `/issues` Prior, in order to display the correct link to "Issues" in the project navigation, we were performing a check against the project to see if it used an external issue tracker, and if so, we used that URL. This was inefficient. Now, we simply _always_ link to `namespace_project_issues_path`, and then in the controller we redirect to the external tracker if it's present. This also removes the need for the url_for_issue helper. Bonus! :tada: --- app/controllers/projects/issues_controller.rb | 7 +++ app/helpers/issues_helper.rb | 16 ------ app/views/layouts/nav/_project.html.haml | 2 +- app/views/projects/issues/_head.html.haml | 2 +- .../controllers/projects/issues_controller_spec.rb | 58 ++++++++++++++-------- spec/helpers/issues_helper_spec.rb | 46 ----------------- 6 files changed, 45 insertions(+), 86 deletions(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 7f5c3ff3d6a..cb1e514c60e 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -5,6 +5,7 @@ class Projects::IssuesController < Projects::ApplicationController include ToggleAwardEmoji include IssuableCollections + before_action :redirect_to_external_issue_tracker, only: [:index] before_action :module_enabled before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests, :related_branches, :can_create_branch] @@ -201,6 +202,12 @@ class Projects::IssuesController < Projects::ApplicationController return render_404 unless @project.issues_enabled && @project.default_issues_tracker? end + def redirect_to_external_issue_tracker + return unless @project.external_issue_tracker + + redirect_to @project.external_issue_tracker.issues_url + end + # Since iids are implemented only in 6.1 # user may navigate to issue page using old global ids. # diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 2b0defd1dda..5061ccb93a4 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -13,22 +13,6 @@ module IssuesHelper OpenStruct.new(id: 0, title: 'None (backlog)', name: 'Unassigned') end - def url_for_project_issues(project = @project, options = {}) - return '' if project.nil? - - url = - if options[:only_path] - project.issues_tracker.project_path - else - project.issues_tracker.project_url - end - - # Ensure we return a valid URL to prevent possible XSS. - URI.parse(url).to_s - rescue URI::InvalidURIError - '' - end - def url_for_new_issue(project = @project, options = {}) return '' if project.nil? diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 9e65d94186b..1d3b8fc3683 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -66,7 +66,7 @@ - if project_nav_tab? :issues = nav_link(controller: [:issues, :labels, :milestones]) do - = link_to url_for_project_issues(@project, only_path: true), title: 'Issues', class: 'shortcuts-issues' do + = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues', class: 'shortcuts-issues' do %span Issues - if @project.default_issues_tracker? diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml index 403adb7426b..60b45115b73 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/issues/_head.html.haml @@ -2,7 +2,7 @@ %ul{ class: (container_class) } - if project_nav_tab?(:issues) && !current_controller?(:merge_requests) = nav_link(controller: :issues) do - = link_to url_for_project_issues(@project, only_path: true), title: 'Issues' do + = link_to namespace_project_issues_path(@project.namespace, @project), title: 'Issues' do %span Issues diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 77f65057f71..ed31f689d3d 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -6,37 +6,51 @@ describe Projects::IssuesController do let(:issue) { create(:issue, project: project) } describe "GET #index" do - before do - sign_in(user) - project.team << [user, :developer] - end + context 'external issue tracker' do + it 'redirects to the external issue tracker' do + external = double(issues_url: 'https://example.com/issues') + allow(project).to receive(:external_issue_tracker).and_return(external) + controller.instance_variable_set(:@project, project) - it "returns index" do - get :index, namespace_id: project.namespace.path, project_id: project.path + get :index, namespace_id: project.namespace.path, project_id: project - expect(response).to have_http_status(200) + expect(response).to redirect_to('https://example.com/issues') + end end - it "return 301 if request path doesn't match project path" do - get :index, namespace_id: project.namespace.path, project_id: project.path.upcase + context 'internal issue tracker' do + before do + sign_in(user) + project.team << [user, :developer] + end - expect(response).to redirect_to(namespace_project_issues_path(project.namespace, project)) - end + it "returns index" do + get :index, namespace_id: project.namespace.path, project_id: project.path - it "returns 404 when issues are disabled" do - project.issues_enabled = false - project.save + expect(response).to have_http_status(200) + end - get :index, namespace_id: project.namespace.path, project_id: project.path - expect(response).to have_http_status(404) - end + it "return 301 if request path doesn't match project path" do + get :index, namespace_id: project.namespace.path, project_id: project.path.upcase + + expect(response).to redirect_to(namespace_project_issues_path(project.namespace, project)) + end + + it "returns 404 when issues are disabled" do + project.issues_enabled = false + project.save - it "returns 404 when external issue tracker is enabled" do - controller.instance_variable_set(:@project, project) - allow(project).to receive(:default_issues_tracker?).and_return(false) + get :index, namespace_id: project.namespace.path, project_id: project.path + expect(response).to have_http_status(404) + end + + it "returns 404 when external issue tracker is enabled" do + controller.instance_variable_set(:@project, project) + allow(project).to receive(:default_issues_tracker?).and_return(false) - get :index, namespace_id: project.namespace.path, project_id: project.path - expect(response).to have_http_status(404) + get :index, namespace_id: project.namespace.path, project_id: project.path + expect(response).to have_http_status(404) + end end end diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index 831ae7fb69c..ca4aea47413 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -5,52 +5,6 @@ describe IssuesHelper do let(:issue) { create :issue, project: project } let(:ext_project) { create :redmine_project } - describe "url_for_project_issues" do - let(:project_url) { ext_project.external_issue_tracker.project_url } - let(:ext_expected) { project_url.gsub(':project_id', ext_project.id.to_s) } - let(:int_expected) { polymorphic_path([@project.namespace, project]) } - - it "should return internal path if used internal tracker" do - @project = project - expect(url_for_project_issues).to match(int_expected) - end - - it "should return path to external tracker" do - @project = ext_project - - expect(url_for_project_issues).to match(ext_expected) - end - - it "should return empty string if project nil" do - @project = nil - - expect(url_for_project_issues).to eq "" - end - - it 'returns an empty string if project_url is invalid' do - expect(project).to receive_message_chain('issues_tracker.project_url') { 'javascript:alert("foo");' } - - expect(url_for_project_issues(project)).to eq '' - end - - it 'returns an empty string if project_path is invalid' do - expect(project).to receive_message_chain('issues_tracker.project_path') { 'javascript:alert("foo");' } - - expect(url_for_project_issues(project, only_path: true)).to eq '' - end - - describe "when external tracker was enabled and then config removed" do - before do - @project = ext_project - allow(Gitlab.config).to receive(:issues_tracker).and_return(nil) - end - - it "should return path to external tracker" do - expect(url_for_project_issues).to match(ext_expected) - end - end - end - describe "url_for_issue" do let(:issues_url) { ext_project.external_issue_tracker.issues_url} let(:ext_expected) { issues_url.gsub(':id', issue.iid.to_s).gsub(':project_id', ext_project.id.to_s) } -- cgit v1.2.1 From 901d4d2ca54d173f9c6b1f39c7548ef7fc9e8cd7 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 1 Aug 2016 18:23:12 -0700 Subject: Remove `url_for_new_issue` helper Now we link to the standard `IssuesController#new` action, and let it redirect if we're using an external tracker. --- app/controllers/projects/issues_controller.rb | 12 ++++-- app/helpers/issues_helper.rb | 16 -------- app/views/projects/buttons/_dropdown.html.haml | 2 +- .../controllers/projects/issues_controller_spec.rb | 14 +++++++ spec/helpers/issues_helper_spec.rb | 46 ---------------------- 5 files changed, 24 insertions(+), 66 deletions(-) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index cb1e514c60e..660e0eba06f 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -5,7 +5,7 @@ class Projects::IssuesController < Projects::ApplicationController include ToggleAwardEmoji include IssuableCollections - before_action :redirect_to_external_issue_tracker, only: [:index] + before_action :redirect_to_external_issue_tracker, only: [:index, :new] before_action :module_enabled before_action :issue, only: [:edit, :update, :show, :referenced_merge_requests, :related_branches, :can_create_branch] @@ -203,9 +203,15 @@ class Projects::IssuesController < Projects::ApplicationController end def redirect_to_external_issue_tracker - return unless @project.external_issue_tracker + external = @project.external_issue_tracker - redirect_to @project.external_issue_tracker.issues_url + return unless external + + if action_name == 'new' + redirect_to external.new_issue_path + else + redirect_to external.issues_url + end end # Since iids are implemented only in 6.1 diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 5061ccb93a4..2e82b44437b 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -13,22 +13,6 @@ module IssuesHelper OpenStruct.new(id: 0, title: 'None (backlog)', name: 'Unassigned') end - def url_for_new_issue(project = @project, options = {}) - return '' if project.nil? - - url = - if options[:only_path] - project.issues_tracker.new_issue_path - else - project.issues_tracker.new_issue_url - end - - # Ensure we return a valid URL to prevent possible XSS. - URI.parse(url).to_s - rescue URI::InvalidURIError - '' - end - def url_for_issue(issue_iid, project = @project, options = {}) return '' if project.nil? diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index 16b8e1cca91..ca907077c2b 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -9,7 +9,7 @@ - if can_create_issue %li - = link_to url_for_new_issue(@project, only_path: true) do + = link_to new_namespace_project_issue_path(@project.namespace, @project) do = icon('exclamation-circle fw') New issue diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index ed31f689d3d..ec820de3d09 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -54,6 +54,20 @@ describe Projects::IssuesController do end end + describe 'GET #new' do + context 'external issue tracker' do + it 'redirects to the external issue tracker' do + external = double(new_issue_path: 'https://example.com/issues/new') + allow(project).to receive(:external_issue_tracker).and_return(external) + controller.instance_variable_set(:@project, project) + + get :new, namespace_id: project.namespace.path, project_id: project + + expect(response).to redirect_to('https://example.com/issues/new') + end + end + end + describe 'PUT #update' do context 'when moving issue to another private project' do let(:another_project) { create(:project, :private) } diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb index ca4aea47413..9ee46dd2508 100644 --- a/spec/helpers/issues_helper_spec.rb +++ b/spec/helpers/issues_helper_spec.rb @@ -51,52 +51,6 @@ describe IssuesHelper do end end - describe 'url_for_new_issue' do - let(:issues_url) { ext_project.external_issue_tracker.new_issue_url } - let(:ext_expected) { issues_url.gsub(':project_id', ext_project.id.to_s) } - let(:int_expected) { new_namespace_project_issue_path(project.namespace, project) } - - it "should return internal path if used internal tracker" do - @project = project - expect(url_for_new_issue).to match(int_expected) - end - - it "should return path to external tracker" do - @project = ext_project - - expect(url_for_new_issue).to match(ext_expected) - end - - it "should return empty string if project nil" do - @project = nil - - expect(url_for_new_issue).to eq "" - end - - it 'returns an empty string if issue_url is invalid' do - expect(project).to receive_message_chain('issues_tracker.new_issue_url') { 'javascript:alert("foo");' } - - expect(url_for_new_issue(project)).to eq '' - end - - it 'returns an empty string if issue_path is invalid' do - expect(project).to receive_message_chain('issues_tracker.new_issue_path') { 'javascript:alert("foo");' } - - expect(url_for_new_issue(project, only_path: true)).to eq '' - end - - describe "when external tracker was enabled and then config removed" do - before do - @project = ext_project - allow(Gitlab.config).to receive(:issues_tracker).and_return(nil) - end - - it "should return internal path" do - expect(url_for_new_issue).to match(ext_expected) - end - end - end - describe "merge_requests_sentence" do subject { merge_requests_sentence(merge_requests)} let(:merge_requests) do -- cgit v1.2.1 From fa7217f3c59cfcc5815524b0415e881e1f9eda62 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Tue, 2 Aug 2016 01:30:35 +0000 Subject: fix runner install link --- doc/ci/quick_start/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/quick_start/README.md b/doc/ci/quick_start/README.md index 7fa1a478f34..6a3c416d995 100644 --- a/doc/ci/quick_start/README.md +++ b/doc/ci/quick_start/README.md @@ -233,7 +233,7 @@ Awesome! You started using CI in GitLab! Visit the [examples README][examples] to see a list of examples using GitLab CI with various languages. -[runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#installation +[runner-install]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/tree/master#install-gitlab-runner [blog-ci]: https://about.gitlab.com/2015/05/06/why-were-replacing-gitlab-ci-jobs-with-gitlab-ci-dot-yml/ [examples]: ../examples/README.md [ci]: https://about.gitlab.com/gitlab-ci/ -- cgit v1.2.1 From ae9f0ca818b203df3bf61e5598c4e7e63c4c2d70 Mon Sep 17 00:00:00 2001 From: winniehell Date: Sun, 31 Jul 2016 04:09:19 +0200 Subject: Add failing test for #20462 --- spec/finders/branches_finder_spec.rb | 6 +++- spec/routing/project_routing_spec.rb | 5 +++- spec/support/test_env.rb | 1 + spec/views/projects/tree/show.html.haml_spec.rb | 37 +++++++++++++++++++++++++ 4 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 spec/views/projects/tree/show.html.haml_spec.rb diff --git a/spec/finders/branches_finder_spec.rb b/spec/finders/branches_finder_spec.rb index 482caeee64a..6fce11de30f 100644 --- a/spec/finders/branches_finder_spec.rb +++ b/spec/finders/branches_finder_spec.rb @@ -20,7 +20,11 @@ describe BranchesFinder do result = branches_finder.execute - expect(result.first.name).to eq('crlf-diff') + recently_updated_branch = repository.branches.max do |a, b| + repository.commit(a.target).committed_date <=> repository.commit(b.target).committed_date + end + + expect(result.first.name).to eq(recently_updated_branch.name) end it 'sorts by last_updated' do diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 9151cd3aefe..b941e78f983 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -479,13 +479,16 @@ end describe Projects::NetworkController, 'routing' do it 'to #show' do expect(get('/gitlab/gitlabhq/network/master')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') - expect(get('/gitlab/gitlabhq/network/master.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') + expect(get('/gitlab/gitlabhq/network/ends-with.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') + expect(get('/gitlab/gitlabhq/network/master?format=json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') end end describe Projects::GraphsController, 'routing' do it 'to #show' do expect(get('/gitlab/gitlabhq/graphs/master')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') + expect(get('/gitlab/gitlabhq/graphs/ends-with.json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'ends-with.json') + expect(get('/gitlab/gitlabhq/graphs/master?format=json')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') end end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 4561aa9644d..1c0c66969e3 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -6,6 +6,7 @@ module TestEnv # When developing the seed repository, comment out the branch you will modify. BRANCH_SHA = { 'empty-branch' => '7efb185', + 'ends-with.json' => '98b0d8b3', 'flatten-dir' => 'e56497b', 'feature' => '0b4bc9a', 'feature_conflict' => 'bb5206f', diff --git a/spec/views/projects/tree/show.html.haml_spec.rb b/spec/views/projects/tree/show.html.haml_spec.rb new file mode 100644 index 00000000000..0f3fc1ee1ac --- /dev/null +++ b/spec/views/projects/tree/show.html.haml_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe 'projects/tree/show' do + include Devise::TestHelpers + + let(:project) { create(:project) } + let(:repository) { project.repository } + + before do + assign(:project, project) + assign(:repository, repository) + + allow(view).to receive(:can?).and_return(true) + allow(view).to receive(:can_collaborate_with_project?).and_return(true) + end + + context 'for branch names ending on .json' do + let(:ref) { 'ends-with.json' } + let(:commit) { repository.commit(ref) } + let(:path) { '' } + let(:tree) { repository.tree(commit.id, path) } + + before do + assign(:ref, ref) + assign(:commit, commit) + assign(:id, commit.id) + assign(:tree, tree) + assign(:path, path) + end + + it 'displays correctly' do + render + expect(rendered).to have_css('.js-project-refs-dropdown .dropdown-toggle-text', text: ref) + expect(rendered).to have_css('.readme-holder .file-content', text: ref) + end + end +end -- cgit v1.2.1 From 010477edc034330dfe4bce7b4dfac252e1fb0a25 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 7 Jul 2016 12:41:57 +0100 Subject: Append .json onto graph request URL (!5136) --- app/views/projects/graphs/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index a985b442b2d..8777e0d8fcd 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -32,7 +32,7 @@ :javascript $.ajax({ type: "GET", - url: location.href, + url: "#{namespace_project_graph_path(@project.namespace, @project, current_ref, :json)}", dataType: "json", success: function (data) { var graph = new ContributorsStatGraph(); -- cgit v1.2.1 From e1832914df2eccea1730586b26e759b562e8b7c1 Mon Sep 17 00:00:00 2001 From: winniehell Date: Sun, 31 Jul 2016 22:52:44 +0200 Subject: Allow branch names ending with .json for graph and network page (!5579) --- CHANGELOG | 1 + app/views/projects/graphs/show.html.haml | 2 +- config/routes.rb | 18 +++++++++++------- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 4daf9cd9092..6bd6f40975f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,6 +34,7 @@ v 8.11.0 (unreleased) - Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration - Fix search for notes which belongs to deleted objects - Add GitLab Workhorse version to admin dashboard (Katarzyna Kobierska Ula Budziszewska) + - Allow branch names ending with .json for graph and network page !5579 (winniehell) - Add the `sprockets-es6` gem - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) - Profile requests when a header is passed diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 8777e0d8fcd..ac5f792d140 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -32,7 +32,7 @@ :javascript $.ajax({ type: "GET", - url: "#{namespace_project_graph_path(@project.namespace, @project, current_ref, :json)}", + url: "#{namespace_project_graph_path(@project.namespace, @project, current_ref, format: :json)}", dataType: "json", success: function (data) { var graph = new ContributorsStatGraph(); diff --git a/config/routes.rb b/config/routes.rb index 371eb4bee7f..2f5f32d9e30 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -626,13 +626,17 @@ Rails.application.routes.draw do get '/compare/:from...:to', to: 'compare#show', as: 'compare', constraints: { from: /.+/, to: /.+/ } - resources :network, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } - - resources :graphs, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } do - member do - get :commits - get :ci - get :languages + # Don't use format parameter as file extension (old 3.0.x behavior) + # See http://guides.rubyonrails.org/routing.html#route-globbing-and-wildcard-segments + scope format: false do + resources :network, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } + + resources :graphs, only: [:show], constraints: { id: Gitlab::Regex.git_reference_regex } do + member do + get :commits + get :ci + get :languages + end end end -- cgit v1.2.1 From 701e5ccbe6018129130ee70a78f213c406c93fcf Mon Sep 17 00:00:00 2001 From: winniehell Date: Mon, 1 Aug 2016 03:58:31 +0200 Subject: Add failing tests for #19028 --- spec/lib/banzai/filter/relative_link_filter_spec.rb | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index 9921171f2aa..224baca8030 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -78,12 +78,24 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do end context 'with a valid repository' do + it 'rebuilds absolute URL for a file in the repo' do + doc = filter(link('/doc/api/README.md')) + expect(doc.at_css('a')['href']). + to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" + end + it 'rebuilds relative URL for a file in the repo' do doc = filter(link('doc/api/README.md')) expect(doc.at_css('a')['href']). to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" end + it 'rebuilds relative URL for a file in the repo with leading ./' do + doc = filter(link('./doc/api/README.md')) + expect(doc.at_css('a')['href']). + to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" + end + it 'rebuilds relative URL for a file in the repo up one directory' do relative_link = link('../api/README.md') doc = filter(relative_link, requested_path: 'doc/update/7.14-to-8.0.md') -- cgit v1.2.1 From 40da543f2fddefcdebf12e52425314355a16a57d Mon Sep 17 00:00:00 2001 From: winniehell Date: Mon, 1 Aug 2016 04:52:05 +0200 Subject: Add support for relative links starting with ./ or / to RelativeLinkFilter (!5586) --- CHANGELOG | 1 + lib/banzai/filter/relative_link_filter.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 4daf9cd9092..c099c63ce86 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 8.11.0 (unreleased) - Fix the title of the toggle dropdown button. !5515 (herminiotorres) - Improve diff performance by eliminating redundant checks for text blobs - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) + - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell) - Fix CI status icon link underline (ClemMakesApps) - Cache the commit author in RequestStore to avoid extra lookups in PostReceive - Fix of 'Commits being passed to custom hooks are already reachable when using the UI' diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb index 337fb50317d..5b73fc8fcee 100644 --- a/lib/banzai/filter/relative_link_filter.rb +++ b/lib/banzai/filter/relative_link_filter.rb @@ -87,10 +87,13 @@ module Banzai def build_relative_path(path, request_path) return request_path if path.empty? return path unless request_path + return path[1..-1] if path.start_with?('/') parts = request_path.split('/') parts.pop if uri_type(request_path) != :tree + path.sub!(%r{^\./}, '') + while path.start_with?('../') parts.pop path.sub!('../', '') -- cgit v1.2.1 From f15fb92209ccea3e13916d2f2d3899008f9df578 Mon Sep 17 00:00:00 2001 From: winniehell Date: Tue, 2 Aug 2016 04:50:47 +0200 Subject: Revert "md5 and utf_encode js libraries" This reverts commit 1bba46d66117b4a96d279fd964a45fe673db658c. --- app/assets/javascripts/lib/utils/md5.js | 211 ------------------------ app/assets/javascripts/lib/utils/utf8_encode.js | 70 -------- 2 files changed, 281 deletions(-) delete mode 100644 app/assets/javascripts/lib/utils/md5.js delete mode 100644 app/assets/javascripts/lib/utils/utf8_encode.js diff --git a/app/assets/javascripts/lib/utils/md5.js b/app/assets/javascripts/lib/utils/md5.js deleted file mode 100644 index b63716eaad2..00000000000 --- a/app/assets/javascripts/lib/utils/md5.js +++ /dev/null @@ -1,211 +0,0 @@ -function md5 (str) { - // http://kevin.vanzonneveld.net - // + original by: Webtoolkit.info (http://www.webtoolkit.info/) - // + namespaced by: Michael White (http://getsprink.com) - // + tweaked by: Jack - // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - // + input by: Brett Zamir (http://brett-zamir.me) - // + bugfixed by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - // - depends on: utf8_encode - // * example 1: md5('Kevin van Zonneveld'); - // * returns 1: '6e658d4bfcb59cc13f96c14450ac40b9' - var xl; - - var rotateLeft = function (lValue, iShiftBits) { - return (lValue << iShiftBits) | (lValue >>> (32 - iShiftBits)); - }; - - var addUnsigned = function (lX, lY) { - var lX4, lY4, lX8, lY8, lResult; - lX8 = (lX & 0x80000000); - lY8 = (lY & 0x80000000); - lX4 = (lX & 0x40000000); - lY4 = (lY & 0x40000000); - lResult = (lX & 0x3FFFFFFF) + (lY & 0x3FFFFFFF); - if (lX4 & lY4) { - return (lResult ^ 0x80000000 ^ lX8 ^ lY8); - } - if (lX4 | lY4) { - if (lResult & 0x40000000) { - return (lResult ^ 0xC0000000 ^ lX8 ^ lY8); - } else { - return (lResult ^ 0x40000000 ^ lX8 ^ lY8); - } - } else { - return (lResult ^ lX8 ^ lY8); - } - }; - - var _F = function (x, y, z) { - return (x & y) | ((~x) & z); - }; - var _G = function (x, y, z) { - return (x & z) | (y & (~z)); - }; - var _H = function (x, y, z) { - return (x ^ y ^ z); - }; - var _I = function (x, y, z) { - return (y ^ (x | (~z))); - }; - - var _FF = function (a, b, c, d, x, s, ac) { - a = addUnsigned(a, addUnsigned(addUnsigned(_F(b, c, d), x), ac)); - return addUnsigned(rotateLeft(a, s), b); - }; - - var _GG = function (a, b, c, d, x, s, ac) { - a = addUnsigned(a, addUnsigned(addUnsigned(_G(b, c, d), x), ac)); - return addUnsigned(rotateLeft(a, s), b); - }; - - var _HH = function (a, b, c, d, x, s, ac) { - a = addUnsigned(a, addUnsigned(addUnsigned(_H(b, c, d), x), ac)); - return addUnsigned(rotateLeft(a, s), b); - }; - - var _II = function (a, b, c, d, x, s, ac) { - a = addUnsigned(a, addUnsigned(addUnsigned(_I(b, c, d), x), ac)); - return addUnsigned(rotateLeft(a, s), b); - }; - - var convertToWordArray = function (str) { - var lWordCount; - var lMessageLength = str.length; - var lNumberOfWords_temp1 = lMessageLength + 8; - var lNumberOfWords_temp2 = (lNumberOfWords_temp1 - (lNumberOfWords_temp1 % 64)) / 64; - var lNumberOfWords = (lNumberOfWords_temp2 + 1) * 16; - var lWordArray = new Array(lNumberOfWords - 1); - var lBytePosition = 0; - var lByteCount = 0; - while (lByteCount < lMessageLength) { - lWordCount = (lByteCount - (lByteCount % 4)) / 4; - lBytePosition = (lByteCount % 4) * 8; - lWordArray[lWordCount] = (lWordArray[lWordCount] | (str.charCodeAt(lByteCount) << lBytePosition)); - lByteCount++; - } - lWordCount = (lByteCount - (lByteCount % 4)) / 4; - lBytePosition = (lByteCount % 4) * 8; - lWordArray[lWordCount] = lWordArray[lWordCount] | (0x80 << lBytePosition); - lWordArray[lNumberOfWords - 2] = lMessageLength << 3; - lWordArray[lNumberOfWords - 1] = lMessageLength >>> 29; - return lWordArray; - }; - - var wordToHex = function (lValue) { - var wordToHexValue = "", - wordToHexValue_temp = "", - lByte, lCount; - for (lCount = 0; lCount <= 3; lCount++) { - lByte = (lValue >>> (lCount * 8)) & 255; - wordToHexValue_temp = "0" + lByte.toString(16); - wordToHexValue = wordToHexValue + wordToHexValue_temp.substr(wordToHexValue_temp.length - 2, 2); - } - return wordToHexValue; - }; - - var x = [], - k, AA, BB, CC, DD, a, b, c, d, S11 = 7, - S12 = 12, - S13 = 17, - S14 = 22, - S21 = 5, - S22 = 9, - S23 = 14, - S24 = 20, - S31 = 4, - S32 = 11, - S33 = 16, - S34 = 23, - S41 = 6, - S42 = 10, - S43 = 15, - S44 = 21; - - str = this.utf8_encode(str); - x = convertToWordArray(str); - a = 0x67452301; - b = 0xEFCDAB89; - c = 0x98BADCFE; - d = 0x10325476; - - xl = x.length; - for (k = 0; k < xl; k += 16) { - AA = a; - BB = b; - CC = c; - DD = d; - a = _FF(a, b, c, d, x[k + 0], S11, 0xD76AA478); - d = _FF(d, a, b, c, x[k + 1], S12, 0xE8C7B756); - c = _FF(c, d, a, b, x[k + 2], S13, 0x242070DB); - b = _FF(b, c, d, a, x[k + 3], S14, 0xC1BDCEEE); - a = _FF(a, b, c, d, x[k + 4], S11, 0xF57C0FAF); - d = _FF(d, a, b, c, x[k + 5], S12, 0x4787C62A); - c = _FF(c, d, a, b, x[k + 6], S13, 0xA8304613); - b = _FF(b, c, d, a, x[k + 7], S14, 0xFD469501); - a = _FF(a, b, c, d, x[k + 8], S11, 0x698098D8); - d = _FF(d, a, b, c, x[k + 9], S12, 0x8B44F7AF); - c = _FF(c, d, a, b, x[k + 10], S13, 0xFFFF5BB1); - b = _FF(b, c, d, a, x[k + 11], S14, 0x895CD7BE); - a = _FF(a, b, c, d, x[k + 12], S11, 0x6B901122); - d = _FF(d, a, b, c, x[k + 13], S12, 0xFD987193); - c = _FF(c, d, a, b, x[k + 14], S13, 0xA679438E); - b = _FF(b, c, d, a, x[k + 15], S14, 0x49B40821); - a = _GG(a, b, c, d, x[k + 1], S21, 0xF61E2562); - d = _GG(d, a, b, c, x[k + 6], S22, 0xC040B340); - c = _GG(c, d, a, b, x[k + 11], S23, 0x265E5A51); - b = _GG(b, c, d, a, x[k + 0], S24, 0xE9B6C7AA); - a = _GG(a, b, c, d, x[k + 5], S21, 0xD62F105D); - d = _GG(d, a, b, c, x[k + 10], S22, 0x2441453); - c = _GG(c, d, a, b, x[k + 15], S23, 0xD8A1E681); - b = _GG(b, c, d, a, x[k + 4], S24, 0xE7D3FBC8); - a = _GG(a, b, c, d, x[k + 9], S21, 0x21E1CDE6); - d = _GG(d, a, b, c, x[k + 14], S22, 0xC33707D6); - c = _GG(c, d, a, b, x[k + 3], S23, 0xF4D50D87); - b = _GG(b, c, d, a, x[k + 8], S24, 0x455A14ED); - a = _GG(a, b, c, d, x[k + 13], S21, 0xA9E3E905); - d = _GG(d, a, b, c, x[k + 2], S22, 0xFCEFA3F8); - c = _GG(c, d, a, b, x[k + 7], S23, 0x676F02D9); - b = _GG(b, c, d, a, x[k + 12], S24, 0x8D2A4C8A); - a = _HH(a, b, c, d, x[k + 5], S31, 0xFFFA3942); - d = _HH(d, a, b, c, x[k + 8], S32, 0x8771F681); - c = _HH(c, d, a, b, x[k + 11], S33, 0x6D9D6122); - b = _HH(b, c, d, a, x[k + 14], S34, 0xFDE5380C); - a = _HH(a, b, c, d, x[k + 1], S31, 0xA4BEEA44); - d = _HH(d, a, b, c, x[k + 4], S32, 0x4BDECFA9); - c = _HH(c, d, a, b, x[k + 7], S33, 0xF6BB4B60); - b = _HH(b, c, d, a, x[k + 10], S34, 0xBEBFBC70); - a = _HH(a, b, c, d, x[k + 13], S31, 0x289B7EC6); - d = _HH(d, a, b, c, x[k + 0], S32, 0xEAA127FA); - c = _HH(c, d, a, b, x[k + 3], S33, 0xD4EF3085); - b = _HH(b, c, d, a, x[k + 6], S34, 0x4881D05); - a = _HH(a, b, c, d, x[k + 9], S31, 0xD9D4D039); - d = _HH(d, a, b, c, x[k + 12], S32, 0xE6DB99E5); - c = _HH(c, d, a, b, x[k + 15], S33, 0x1FA27CF8); - b = _HH(b, c, d, a, x[k + 2], S34, 0xC4AC5665); - a = _II(a, b, c, d, x[k + 0], S41, 0xF4292244); - d = _II(d, a, b, c, x[k + 7], S42, 0x432AFF97); - c = _II(c, d, a, b, x[k + 14], S43, 0xAB9423A7); - b = _II(b, c, d, a, x[k + 5], S44, 0xFC93A039); - a = _II(a, b, c, d, x[k + 12], S41, 0x655B59C3); - d = _II(d, a, b, c, x[k + 3], S42, 0x8F0CCC92); - c = _II(c, d, a, b, x[k + 10], S43, 0xFFEFF47D); - b = _II(b, c, d, a, x[k + 1], S44, 0x85845DD1); - a = _II(a, b, c, d, x[k + 8], S41, 0x6FA87E4F); - d = _II(d, a, b, c, x[k + 15], S42, 0xFE2CE6E0); - c = _II(c, d, a, b, x[k + 6], S43, 0xA3014314); - b = _II(b, c, d, a, x[k + 13], S44, 0x4E0811A1); - a = _II(a, b, c, d, x[k + 4], S41, 0xF7537E82); - d = _II(d, a, b, c, x[k + 11], S42, 0xBD3AF235); - c = _II(c, d, a, b, x[k + 2], S43, 0x2AD7D2BB); - b = _II(b, c, d, a, x[k + 9], S44, 0xEB86D391); - a = addUnsigned(a, AA); - b = addUnsigned(b, BB); - c = addUnsigned(c, CC); - d = addUnsigned(d, DD); - } - - var temp = wordToHex(a) + wordToHex(b) + wordToHex(c) + wordToHex(d); - - return temp.toLowerCase(); -} diff --git a/app/assets/javascripts/lib/utils/utf8_encode.js b/app/assets/javascripts/lib/utils/utf8_encode.js deleted file mode 100644 index 39ffe44dae0..00000000000 --- a/app/assets/javascripts/lib/utils/utf8_encode.js +++ /dev/null @@ -1,70 +0,0 @@ -function utf8_encode (argString) { - // http://kevin.vanzonneveld.net - // + original by: Webtoolkit.info (http://www.webtoolkit.info/) - // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) - // + improved by: sowberry - // + tweaked by: Jack - // + bugfixed by: Onno Marsman - // + improved by: Yves Sucaet - // + bugfixed by: Onno Marsman - // + bugfixed by: Ulrich - // + bugfixed by: Rafal Kukawski - // + improved by: kirilloid - // + bugfixed by: kirilloid - // * example 1: utf8_encode('Kevin van Zonneveld'); - // * returns 1: 'Kevin van Zonneveld' - - if (argString === null || typeof argString === "undefined") { - return ""; - } - - var string = (argString + ''); // .replace(/\r\n/g, "\n").replace(/\r/g, "\n"); - var utftext = '', - start, end, stringl = 0; - - start = end = 0; - stringl = string.length; - for (var n = 0; n < stringl; n++) { - var c1 = string.charCodeAt(n); - var enc = null; - - if (c1 < 128) { - end++; - } else if (c1 > 127 && c1 < 2048) { - enc = String.fromCharCode( - (c1 >> 6) | 192, - ( c1 & 63) | 128 - ); - } else if (c1 & 0xF800 != 0xD800) { - enc = String.fromCharCode( - (c1 >> 12) | 224, - ((c1 >> 6) & 63) | 128, - ( c1 & 63) | 128 - ); - } else { // surrogate pairs - if (c1 & 0xFC00 != 0xD800) { throw new RangeError("Unmatched trail surrogate at " + n); } - var c2 = string.charCodeAt(++n); - if (c2 & 0xFC00 != 0xDC00) { throw new RangeError("Unmatched lead surrogate at " + (n-1)); } - c1 = ((c1 & 0x3FF) << 10) + (c2 & 0x3FF) + 0x10000; - enc = String.fromCharCode( - (c1 >> 18) | 240, - ((c1 >> 12) & 63) | 128, - ((c1 >> 6) & 63) | 128, - ( c1 & 63) | 128 - ); - } - if (enc !== null) { - if (end > start) { - utftext += string.slice(start, end); - } - utftext += enc; - start = end = n + 1; - } - } - - if (end > start) { - utftext += string.slice(start, stringl); - } - - return utftext; -} -- cgit v1.2.1 From 4768afbdbf85abbb5e2281c8855e7d27c07a581e Mon Sep 17 00:00:00 2001 From: Keith Pope Date: Tue, 2 Aug 2016 06:56:23 +0100 Subject: Add simple identifier to public SSH keys --- CHANGELOG | 1 + app/models/key.rb | 5 +++-- spec/models/key_spec.rb | 5 +++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9b66108c160..86bf05bfc08 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.11.0 (unreleased) - Fix renaming repository when name contains invalid chararacters under project settings - Optimize checking if a user has read access to a list of issues !5370 - Nokogiri's various parsing methods are now instrumented + - Add simple identifier to public SSH keys (muteor) - Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363 - Add build event color in HipChat messages (David Eisner) - Make fork counter always clickable. !5463 (winniehell) diff --git a/app/models/key.rb b/app/models/key.rb index b9bc38a0436..568a60b8af3 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -26,8 +26,9 @@ class Key < ActiveRecord::Base end def publishable_key - # Removes anything beyond the keytype and key itself - self.key.split[0..1].join(' ') + # Strip out the keys comment so we don't leak email addresses + # Replace with simple ident of user_name (hostname) + self.key.split[0..1].push("#{self.user_name} (#{Gitlab.config.gitlab.host})").join(' ') end # projects that has this key diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 49cf3d8633a..a4d46ca84de 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -16,12 +16,13 @@ describe Key, models: true do end describe "Methods" do + let(:user) { create(:user) } it { is_expected.to respond_to :projects } it { is_expected.to respond_to :publishable_key } describe "#publishable_keys" do - it 'strips all personal information' do - expect(build(:key).publishable_key).not_to match(/dummy@gitlab/) + it 'replaces SSH key comment with simple identifier of username + hostname' do + expect(build(:key, user: user).publishable_key).to match(/#{Regexp.escape(user.name)} \(localhost\)/) end end end -- cgit v1.2.1 From ab0aedef5b5b41135ce28490cedfaab13095f650 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 2 Aug 2016 11:56:47 +0200 Subject: Always compare with FETCH_HEAD in downtime_check This ensures this CI step works properly even when doing a shallow clone. --- lib/tasks/downtime_check.rake | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/lib/tasks/downtime_check.rake b/lib/tasks/downtime_check.rake index 30a2e9be5ce..afe5d42910c 100644 --- a/lib/tasks/downtime_check.rake +++ b/lib/tasks/downtime_check.rake @@ -1,26 +1,12 @@ desc 'Checks if migrations in a branch require downtime' task downtime_check: :environment do - # First we'll want to make sure we're comparing with the right upstream - # repository/branch. - current_branch = `git rev-parse --abbrev-ref HEAD`.strip - - # Either the developer ran this task directly on the master branch, or they're - # making changes directly on the master branch. - if current_branch == 'master' - if defined?(Gitlab::License) - repo = 'gitlab-ee' - else - repo = 'gitlab-ce' - end - - `git fetch https://gitlab.com/gitlab-org/#{repo}.git --depth 1` - - compare_with = 'FETCH_HEAD' - # The developer is working on a different branch, in this case we can just - # compare with the master branch. + if defined?(Gitlab::License) + repo = 'gitlab-ee' else - compare_with = 'master' + repo = 'gitlab-ce' end - Rake::Task['gitlab:db:downtime_check'].invoke(compare_with) + `git fetch https://gitlab.com/gitlab-org/#{repo}.git --depth 1` + + Rake::Task['gitlab:db:downtime_check'].invoke('FETCH_HEAD') end -- cgit v1.2.1 From f0b73f81198a9cba8961774f45e0b96f0cb73c78 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Tue, 31 May 2016 11:06:45 +0530 Subject: Add help document describing wiki linking behavior. --- app/views/projects/wikis/_form.html.haml | 2 + doc/markdown/markdown.md | 4 ++ doc/markdown/wiki.md | 77 ++++++++++++++++++++++++++++++++ 3 files changed, 83 insertions(+) create mode 100644 doc/markdown/wiki.md diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 797a1a59e9f..893bea3c759 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -20,6 +20,8 @@ .help-block To link to a (new) page, simply type %code [Link Title](page-slug) + \. More examples are in the + = link_to 'documentation', help_page_path(category: 'markdown', file: 'wiki', format: 'md') \. .form-group = f.label :commit_message, class: 'control-label' diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index c6c7ac81c0d..e4101fa5388 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -28,6 +28,10 @@ * [Line Breaks](#line-breaks) * [Tables](#tables) +**[Wiki-Specific Markdown](wiki.md)** + +* [Links](wiki.md#links-to-other-wiki-pages) + **[References](#references)** ## GitLab Flavored Markdown (GFM) diff --git a/doc/markdown/wiki.md b/doc/markdown/wiki.md new file mode 100644 index 00000000000..b58107abea3 --- /dev/null +++ b/doc/markdown/wiki.md @@ -0,0 +1,77 @@ +# Wiki-Specific Markdown + +## Table of Contents + +* [Links to Other Wiki Pages](#links-to-other-wiki-pages) + * [Direct Page Link](#direct-page-link) + * [Direct File Link](#direct-file-link) + * [Hierarchical Link](#hierarchical-link) + * [Root Link](#root-link) + +## Links to Other Wiki Pages + +You can link to other pages on your wiki in a few different ways. + +### Direct Page Link + +A link which just includes the slug for a page will point to that page, _at the base level of the wiki_. + +1. This snippet would link to a `documentation` page at the root of your wiki. + +```markdown +[Link to Documentation](documentation) +``` + +### Direct File Link + +Links with a file extension point to that file, _relative to the current page_. + +1. If this snippet was placed on a page at `/documentation/related`, it would link to `/documentation/file.md`. + + ```markdown + [Link to File](file.md) + ``` + +### Hierarchical Link + +A link can be constructed relative to the current wiki page using `./`, `../`, etc. + +1. If this snippet was placed on a page at `/documentation/main`, it would link to `/documentation/related`. + + ```markdown + [Link to Related Page](./related) + ``` + +1. If this snippet was placed on a page at `/documentation/related/content`, it would link to `/documentation/main`. + + ```markdown + [Link to Related Page](../main) + ``` + +1. If this snippet was placed on a page at `/documentation/main`, it would link to `/documentation/related.md`. + + ```markdown + [Link to Related Page](./related.md) + ``` + +1. If this snippet was placed on a page at `/documentation/related/content`, it would link to `/documentation/main.md`. + + ```markdown + [Link to Related Page](../main.md) + ``` + +### Root Link + +A link starting with a `/` is relative to the wiki root, for non-file links. + +1. This snippet links to `/documentation` + + ```markdown + [Link to Related Page](/documentation) + ``` + +1. This snippet links to `/miscellaneous.md` + + ```markdown + [Link to Related Page](/miscellaneous.md) + ``` -- cgit v1.2.1 From d55e83addecda6c6a3632826b9bb61437d18290f Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Tue, 31 May 2016 15:59:16 +0530 Subject: Use `succeed` to add periods to help text. --- app/views/projects/wikis/_form.html.haml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 893bea3c759..6360371804a 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -18,11 +18,14 @@ .error-alert .help-block - To link to a (new) page, simply type - %code [Link Title](page-slug) - \. More examples are in the - = link_to 'documentation', help_page_path(category: 'markdown', file: 'wiki', format: 'md') - \. + = succeed '.' do + To link to a (new) page, simply type + %code [Link Title](page-slug) + + = succeed '.' do + More examples are in the + = link_to 'documentation', help_page_path(category: 'markdown', file: 'wiki', format: 'md') + .form-group = f.label :commit_message, class: 'control-label' .col-sm-10= f.text_field :message, class: 'form-control', rows: 18 -- cgit v1.2.1 From dc396dc4f9622e96c0a1564260e106643eb48a4a Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Thu, 2 Jun 2016 14:25:17 +0530 Subject: Remove the `non-file` qualifier for root links. --- doc/markdown/wiki.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/markdown/wiki.md b/doc/markdown/wiki.md index b58107abea3..fa9b3298edb 100644 --- a/doc/markdown/wiki.md +++ b/doc/markdown/wiki.md @@ -62,7 +62,7 @@ A link can be constructed relative to the current wiki page using `./`, `. ### Root Link -A link starting with a `/` is relative to the wiki root, for non-file links. +A link starting with a `/` is relative to the wiki root. 1. This snippet links to `/documentation` -- cgit v1.2.1 From f611b7b3573213956d8cd8f8580434dbcf3a41ea Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 2 Aug 2016 11:08:22 +0200 Subject: fix TODO comment [ci skip] --- lib/gitlab/import_export/relation_factory.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index e9c1b79fa45..5e56b3d1aa7 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -44,7 +44,7 @@ module Gitlab if @relation_name == :notes set_note_author - # TODO: note attatchments not supported yet + # attachment is deprecated and note uploads are handled by Markdown uploader @relation_hash['attachment'] = nil end -- cgit v1.2.1 From 97c61900e4bd764fa772f349a2996d8d940795b1 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 2 Aug 2016 13:17:04 +0300 Subject: Refactor wiki Markdown documentation --- doc/markdown/wiki.md | 60 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/doc/markdown/wiki.md b/doc/markdown/wiki.md index fa9b3298edb..d9e6d071b9f 100644 --- a/doc/markdown/wiki.md +++ b/doc/markdown/wiki.md @@ -1,76 +1,86 @@ -# Wiki-Specific Markdown +# Wiki-specific Markdown -## Table of Contents +This page has information related to wiki-specific Markdown. For more +information on GitLab's Markdown, see the [main Markdown document](./markdown.md). -* [Links to Other Wiki Pages](#links-to-other-wiki-pages) - * [Direct Page Link](#direct-page-link) - * [Direct File Link](#direct-file-link) - * [Hierarchical Link](#hierarchical-link) - * [Root Link](#root-link) +## Table of contents -## Links to Other Wiki Pages +* [Links to other wiki pages](#links-to-other-wiki-pages) + * [Direct page link](#direct-page-link) + * [Direct file link](#direct-file-link) + * [Hierarchical link](#hierarchical-link) + * [Root link](#root-link) + +## Links to other wiki pages You can link to other pages on your wiki in a few different ways. -### Direct Page Link +### Direct page link -A link which just includes the slug for a page will point to that page, _at the base level of the wiki_. +A link which just includes the slug for a page will point to that page, +_at the base level of the wiki_. -1. This snippet would link to a `documentation` page at the root of your wiki. +This snippet would link to a `documentation` page at the root of your wiki: ```markdown [Link to Documentation](documentation) ``` -### Direct File Link +### Direct file link Links with a file extension point to that file, _relative to the current page_. -1. If this snippet was placed on a page at `/documentation/related`, it would link to `/documentation/file.md`. +If this snippet was placed on a page at `/documentation/related`, +it would link to `/documentation/file.md`: - ```markdown - [Link to File](file.md) - ``` +```markdown +[Link to File](file.md) +``` -### Hierarchical Link +### Hierarchical link -A link can be constructed relative to the current wiki page using `./`, `../`, etc. +A link can be constructed relative to the current wiki page using `./`, +`../`, etc. -1. If this snippet was placed on a page at `/documentation/main`, it would link to `/documentation/related`. +- If this snippet was placed on a page at `/documentation/main`, + it would link to `/documentation/related`: ```markdown [Link to Related Page](./related) ``` -1. If this snippet was placed on a page at `/documentation/related/content`, it would link to `/documentation/main`. +- If this snippet was placed on a page at `/documentation/related/content`, + it would link to `/documentation/main`: ```markdown [Link to Related Page](../main) ``` -1. If this snippet was placed on a page at `/documentation/main`, it would link to `/documentation/related.md`. +- If this snippet was placed on a page at `/documentation/main`, + it would link to `/documentation/related.md`: ```markdown [Link to Related Page](./related.md) ``` -1. If this snippet was placed on a page at `/documentation/related/content`, it would link to `/documentation/main.md`. +- If this snippet was placed on a page at `/documentation/related/content`, + it would link to `/documentation/main.md`: ```markdown [Link to Related Page](../main.md) ``` -### Root Link +### Root link A link starting with a `/` is relative to the wiki root. -1. This snippet links to `/documentation` +- This snippet links to `/documentation`: ```markdown [Link to Related Page](/documentation) ``` -1. This snippet links to `/miscellaneous.md` +- This snippet links to `/miscellaneous.md`: ```markdown [Link to Related Page](/miscellaneous.md) -- cgit v1.2.1 From 4611191785f2ca0eca2ba5be3b24224879bc5c50 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 2 Aug 2016 13:34:49 +0300 Subject: Merge wiki-specific Markdown in main Markdown file --- doc/markdown/markdown.md | 82 +++++++++++++++++++++++++++++++++++++++++++-- doc/markdown/wiki.md | 87 ------------------------------------------------ 2 files changed, 80 insertions(+), 89 deletions(-) delete mode 100644 doc/markdown/wiki.md diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index e4101fa5388..d7f09ec070c 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -28,9 +28,12 @@ * [Line Breaks](#line-breaks) * [Tables](#tables) -**[Wiki-Specific Markdown](wiki.md)** +**[Wiki-Specific Markdown](#wiki-specific-markdown)** -* [Links](wiki.md#links-to-other-wiki-pages) +* [Wiki - Direct page link](#wiki-direct-page-link) +* [Wiki - Direct file link](#wiki-direct-file-link) +* [Wiki - Hierarchical link](#wiki-hierarchical-link) +* [Wiki - Root link](#wiki-root-link) **[References](#references)** @@ -696,6 +699,81 @@ By including colons in the header row, you can align the text within that column | Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | | Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | + +## Wiki-specific Markdown + +The following examples show how links inside wikis behave. + +### Wiki - Direct page link + +A link which just includes the slug for a page will point to that page, +_at the base level of the wiki_. + +This snippet would link to a `documentation` page at the root of your wiki: + +```markdown +[Link to Documentation](documentation) +``` + +### Wiki - Direct file link + +Links with a file extension point to that file, _relative to the current page_. + +If this snippet was placed on a page at `/documentation/related`, +it would link to `/documentation/file.md`: + +```markdown +[Link to File](file.md) +``` + +### Wiki - Hierarchical link + +A link can be constructed relative to the current wiki page using `./`, +`../`, etc. + +- If this snippet was placed on a page at `/documentation/main`, + it would link to `/documentation/related`: + + ```markdown + [Link to Related Page](./related) + ``` + +- If this snippet was placed on a page at `/documentation/related/content`, + it would link to `/documentation/main`: + + ```markdown + [Link to Related Page](../main) + ``` + +- If this snippet was placed on a page at `/documentation/main`, + it would link to `/documentation/related.md`: + + ```markdown + [Link to Related Page](./related.md) + ``` + +- If this snippet was placed on a page at `/documentation/related/content`, + it would link to `/documentation/main.md`: + + ```markdown + [Link to Related Page](../main.md) + ``` + +### Wiki - Root link + +A link starting with a `/` is relative to the wiki root. + +- This snippet links to `/documentation`: + + ```markdown + [Link to Related Page](/documentation) + ``` + +- This snippet links to `/miscellaneous.md`: + + ```markdown + [Link to Related Page](/miscellaneous.md) + ``` ## References - This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). diff --git a/doc/markdown/wiki.md b/doc/markdown/wiki.md deleted file mode 100644 index d9e6d071b9f..00000000000 --- a/doc/markdown/wiki.md +++ /dev/null @@ -1,87 +0,0 @@ -# Wiki-specific Markdown - -This page has information related to wiki-specific Markdown. For more -information on GitLab's Markdown, see the [main Markdown document](./markdown.md). - -## Table of contents - -* [Links to other wiki pages](#links-to-other-wiki-pages) - * [Direct page link](#direct-page-link) - * [Direct file link](#direct-file-link) - * [Hierarchical link](#hierarchical-link) - * [Root link](#root-link) - -## Links to other wiki pages - -You can link to other pages on your wiki in a few different ways. - -### Direct page link - -A link which just includes the slug for a page will point to that page, -_at the base level of the wiki_. - -This snippet would link to a `documentation` page at the root of your wiki: - -```markdown -[Link to Documentation](documentation) -``` - -### Direct file link - -Links with a file extension point to that file, _relative to the current page_. - -If this snippet was placed on a page at `/documentation/related`, -it would link to `/documentation/file.md`: - -```markdown -[Link to File](file.md) -``` - -### Hierarchical link - -A link can be constructed relative to the current wiki page using `./`, -`../`, etc. - -- If this snippet was placed on a page at `/documentation/main`, - it would link to `/documentation/related`: - - ```markdown - [Link to Related Page](./related) - ``` - -- If this snippet was placed on a page at `/documentation/related/content`, - it would link to `/documentation/main`: - - ```markdown - [Link to Related Page](../main) - ``` - -- If this snippet was placed on a page at `/documentation/main`, - it would link to `/documentation/related.md`: - - ```markdown - [Link to Related Page](./related.md) - ``` - -- If this snippet was placed on a page at `/documentation/related/content`, - it would link to `/documentation/main.md`: - - ```markdown - [Link to Related Page](../main.md) - ``` - -### Root link - -A link starting with a `/` is relative to the wiki root. - -- This snippet links to `/documentation`: - - ```markdown - [Link to Related Page](/documentation) - ``` - -- This snippet links to `/miscellaneous.md`: - - ```markdown - [Link to Related Page](/miscellaneous.md) - ``` -- cgit v1.2.1 From fc492c890596c996ad270dcbcd5d76c2b748cbeb Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 2 Aug 2016 13:42:14 +0300 Subject: Change Markdown document location --- app/views/projects/wikis/_form.html.haml | 2 +- doc/README.md | 2 +- doc/markdown/img/logo.png | Bin 9509 -> 0 bytes doc/markdown/img/video.mp4 | Bin 383631 -> 0 bytes doc/markdown/markdown.md | 786 ------------------------------- doc/user/project/img/markdown_logo.png | Bin 0 -> 9509 bytes doc/user/project/img/markdown_video.mp4 | Bin 0 -> 383631 bytes doc/user/project/markdown.md | 786 +++++++++++++++++++++++++++++++ 8 files changed, 788 insertions(+), 788 deletions(-) delete mode 100644 doc/markdown/img/logo.png delete mode 100644 doc/markdown/img/video.mp4 delete mode 100644 doc/markdown/markdown.md create mode 100644 doc/user/project/img/markdown_logo.png create mode 100644 doc/user/project/img/markdown_video.mp4 create mode 100644 doc/user/project/markdown.md diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 6360371804a..643f7c589e6 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -24,7 +24,7 @@ = succeed '.' do More examples are in the - = link_to 'documentation', help_page_path(category: 'markdown', file: 'wiki', format: 'md') + = link_to 'documentation', help_page_path("user/project/markdown", anchor: "wiki-specific-markdown") .form-group = f.label :commit_message, class: 'control-label' diff --git a/doc/README.md b/doc/README.md index b5b377822e6..751e685b19b 100644 --- a/doc/README.md +++ b/doc/README.md @@ -9,7 +9,7 @@ - [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. - [Importing to GitLab](workflow/importing/README.md). - [Importing and exporting projects between instances](user/project/settings/import_export.md). -- [Markdown](markdown/markdown.md) GitLab's advanced formatting system. +- [Markdown](user/project/markdown.md) GitLab's advanced formatting system. - [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab. - [Permissions](user/permissions.md) Learn what each role in a project (external/guest/reporter/developer/master/owner) can do. - [Profile Settings](profile/README.md) diff --git a/doc/markdown/img/logo.png b/doc/markdown/img/logo.png deleted file mode 100644 index 05c8b0d0ccf..00000000000 Binary files a/doc/markdown/img/logo.png and /dev/null differ diff --git a/doc/markdown/img/video.mp4 b/doc/markdown/img/video.mp4 deleted file mode 100644 index 1fc478842f5..00000000000 Binary files a/doc/markdown/img/video.mp4 and /dev/null differ diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md deleted file mode 100644 index d7f09ec070c..00000000000 --- a/doc/markdown/markdown.md +++ /dev/null @@ -1,786 +0,0 @@ -# Markdown - -## Table of Contents - -**[GitLab Flavored Markdown](#gitlab-flavored-markdown-gfm)** - -* [Newlines](#newlines) -* [Multiple underscores in words](#multiple-underscores-in-words) -* [URL auto-linking](#url-auto-linking) -* [Multiline Blockquote](#multiline-blockquote) -* [Code and Syntax Highlighting](#code-and-syntax-highlighting) -* [Inline Diff](#inline-diff) -* [Emoji](#emoji) -* [Special GitLab references](#special-gitlab-references) -* [Task Lists](#task-lists) -* [Videos](#videos) - -**[Standard Markdown](#standard-markdown)** - -* [Headers](#headers) -* [Emphasis](#emphasis) -* [Lists](#lists) -* [Links](#links) -* [Images](#images) -* [Blockquotes](#blockquotes) -* [Inline HTML](#inline-html) -* [Horizontal Rule](#horizontal-rule) -* [Line Breaks](#line-breaks) -* [Tables](#tables) - -**[Wiki-Specific Markdown](#wiki-specific-markdown)** - -* [Wiki - Direct page link](#wiki-direct-page-link) -* [Wiki - Direct file link](#wiki-direct-file-link) -* [Wiki - Hierarchical link](#wiki-hierarchical-link) -* [Wiki - Root link](#wiki-root-link) - -**[References](#references)** - -## GitLab Flavored Markdown (GFM) - -> **Note:** -Not all of the GitLab-specific extensions to Markdown that are described in -this document currently work on our documentation website. -> -For the best result, we encourage you to check this document out as rendered -by GitLab: [markdown.md] - -_GitLab uses the [Redcarpet Ruby library][redcarpet] for Markdown processing._ - -GitLab uses "GitLab Flavored Markdown" (GFM). It extends the standard Markdown in a few significant ways to add some useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). - -You can use GFM in the following areas: - -- comments -- issues -- merge requests -- milestones -- snippets (the snippet must be named with a `.md` extension) -- wiki pages -- markdown documents inside the repository - -You can also use other rich text files in GitLab. You might have to install a -dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information. - -## Newlines - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#newlines - -GFM honors the markdown specification in how [paragraphs and line breaks are handled](https://daringfireball.net/projects/markdown/syntax#p). - -A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. -Line-breaks, or softreturns, are rendered if you end a line with two or more spaces: - - Roses are red [followed by two or more spaces] - Violets are blue - - Sugar is sweet - -Roses are red -Violets are blue - -Sugar is sweet - -## Multiple underscores in words - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiple-underscores-in-words - -It is not reasonable to italicize just _part_ of a word, especially when you're dealing with code and names that often appear with multiple underscores. Therefore, GFM ignores multiple underscores in words: - - perform_complicated_task - - do_this_and_do_that_and_another_thing - -perform_complicated_task - -do_this_and_do_that_and_another_thing - -## URL auto-linking - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#url-auto-linking - -GFM will autolink almost any URL you copy and paste into your text: - - * https://www.google.com - * https://google.com/ - * ftp://ftp.us.debian.org/debian/ - * smb://foo/bar/baz - * irc://irc.freenode.net/gitlab - * http://localhost:3000 - -* https://www.google.com -* https://google.com/ -* ftp://ftp.us.debian.org/debian/ -* smb://foo/bar/baz -* irc://irc.freenode.net/gitlab -* http://localhost:3000 - -## Multiline Blockquote - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiline-blockquote - -On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines, -GFM supports multiline blockquotes fenced by >>>: - -```no-highlight ->>> -If you paste a message from somewhere else - -that - -spans - -multiple lines, - -you can quote that without having to manually prepend `>` to every line! ->>> -``` - ->>> -If you paste a message from somewhere else - -that - -spans - -multiple lines, - -you can quote that without having to manually prepend `>` to every line! ->>> - -## Code and Syntax Highlighting - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#code-and-syntax-highlighting - -_GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a -list of supported languages visit the Rouge website._ - -Blocks of code are either fenced by lines with three back-ticks ```, -or are indented with four spaces. Only the fenced code blocks support syntax -highlighting: - -```no-highlight -Inline `code` has `back-ticks around` it. -``` - -Inline `code` has `back-ticks around` it. - -Example: - - ```javascript - var s = "JavaScript syntax highlighting"; - alert(s); - ``` - - ```python - def function(): - #indenting works just fine in the fenced code block - s = "Python syntax highlighting" - print s - ``` - - ```ruby - require 'redcarpet' - markdown = Redcarpet.new("Hello World!") - puts markdown.to_html - ``` - - ``` - No language indicated, so no syntax highlighting. - s = "There is no highlighting for this." - But let's throw in a tag. - ``` - -becomes: - -```javascript -var s = "JavaScript syntax highlighting"; -alert(s); -``` - -```python -def function(): - #indenting works just fine in the fenced code block - s = "Python syntax highlighting" - print s -``` - -```ruby -require 'redcarpet' -markdown = Redcarpet.new("Hello World!") -puts markdown.to_html -``` - -``` -No language indicated, so no syntax highlighting. -s = "There is no highlighting for this." -But let's throw in a tag. -``` - -## Inline Diff - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#inline-diff - -With inline diffs tags you can display {+ additions +} or [- deletions -]. - -The wrapping tags can be either curly braces or square brackets [+ additions +] or {- deletions -}. - -However the wrapping tags cannot be mixed as such: - -- {+ additions +] -- [+ additions +} -- {- deletions -] -- [- deletions -} - -## Emoji - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#emoji - - Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: - - :zap: You can use emoji anywhere GFM is supported. :v: - - You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. - - If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. - - Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup: - -Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: - -:zap: You can use emoji anywhere GFM is supported. :v: - -You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. - -If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. - -Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup: - -## Special GitLab References - -GFM recognizes special references. - -You can easily reference e.g. an issue, a commit, a team member or even the whole team within a project. - -GFM will turn that reference into a link so you can navigate between them easily. - -GFM will recognize the following: - -| input | references | -|:-----------------------|:--------------------------- | -| `@user_name` | specific user | -| `@group_name` | specific group | -| `@all` | entire team | -| `#123` | issue | -| `!123` | merge request | -| `$123` | snippet | -| `~123` | label by ID | -| `~bug` | one-word label by name | -| `~"feature request"` | multi-word label by name | -| `%123` | milestone by ID | -| `%v1.23` | one-word milestone by name | -| `%"release candidate"` | multi-word milestone by name | -| `9ba12248` | specific commit | -| `9ba12248...b19a04f5` | commit range comparison | -| `[README](doc/README)` | repository file references | - -GFM also recognizes certain cross-project references: - -| input | references | -|:----------------------------------------|:------------------------| -| `namespace/project#123` | issue | -| `namespace/project!123` | merge request | -| `namespace/project%123` | milestone | -| `namespace/project$123` | snippet | -| `namespace/project@9ba12248` | specific commit | -| `namespace/project@9ba12248...b19a04f5` | commit range comparison | -| `namespace/project~"Some label"` | issues with given label | - -## Task Lists - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#task-lists - -You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so: - -```no-highlight -- [x] Completed task -- [ ] Incomplete task - - [ ] Sub-task 1 - - [x] Sub-task 2 - - [ ] Sub-task 3 -``` - -- [x] Completed task -- [ ] Incomplete task - - [ ] Sub-task 1 - - [x] Sub-task 2 - - [ ] Sub-task 3 - -Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes. - -## Videos - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#videos - -Image tags with a video extension are automatically converted to a video player. - -The valid video extensions are `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`. - - Here's a sample video: - - ![Sample Video](img/video.mp4) - -Here's a sample video: - -![Sample Video](img/video.mp4) - -# Standard Markdown - -## Headers - -```no-highlight -# H1 -## H2 -### H3 -#### H4 -##### H5 -###### H6 - -Alternatively, for H1 and H2, an underline-ish style: - -Alt-H1 -====== - -Alt-H2 ------- -``` - -# H1 -## H2 -### H3 -#### H4 -##### H5 -###### H6 - -Alternatively, for H1 and H2, an underline-ish style: - -Alt-H1 -====== - -Alt-H2 ------- - -### Header IDs and links - -All Markdown-rendered headers automatically get IDs, except in comments. - -On hover a link to those IDs becomes visible to make it easier to copy the link to the header to give it to someone else. - -The IDs are generated from the content of the header according to the following rules: - -1. All text is converted to lowercase -1. All non-word text (e.g., punctuation, HTML) is removed -1. All spaces are converted to hyphens -1. Two or more hyphens in a row are converted to one -1. If a header with the same ID has already been generated, a unique - incrementing number is appended, starting at 1. - -For example: - -``` -# This header has spaces in it -## This header has a :thumbsup: in it -# This header has Unicode in it: 한글 -## This header has spaces in it -### This header has spaces in it -``` - -Would generate the following link IDs: - -1. `this-header-has-spaces-in-it` -1. `this-header-has-a-in-it` -1. `this-header-has-unicode-in-it-한글` -1. `this-header-has-spaces-in-it` -1. `this-header-has-spaces-in-it-1` - -Note that the Emoji processing happens before the header IDs are generated, so the Emoji is converted to an image which then gets removed from the ID. - -## Emphasis - -```no-highlight -Emphasis, aka italics, with *asterisks* or _underscores_. - -Strong emphasis, aka bold, with **asterisks** or __underscores__. - -Combined emphasis with **asterisks and _underscores_**. - -Strikethrough uses two tildes. ~~Scratch this.~~ -``` - -Emphasis, aka italics, with *asterisks* or _underscores_. - -Strong emphasis, aka bold, with **asterisks** or __underscores__. - -Combined emphasis with **asterisks and _underscores_**. - -Strikethrough uses two tildes. ~~Scratch this.~~ - -## Lists - -```no-highlight -1. First ordered list item -2. Another item - * Unordered sub-list. -1. Actual numbers don't matter, just that it's a number - 1. Ordered sub-list -4. And another item. - -* Unordered list can use asterisks -- Or minuses -+ Or pluses -``` - -1. First ordered list item -2. Another item - * Unordered sub-list. -1. Actual numbers don't matter, just that it's a number - 1. Ordered sub-list -4. And another item. - -* Unordered list can use asterisks -- Or minuses -+ Or pluses - -If a list item contains multiple paragraphs, -each subsequent paragraph should be indented with four spaces. - -```no-highlight -1. First ordered list item - - Second paragraph of first item. -2. Another item -``` - -1. First ordered list item - - Second paragraph of first item. -2. Another item - -If the second paragraph isn't indented with four spaces, -the second list item will be incorrectly labeled as `1`. - -```no-highlight -1. First ordered list item - - Second paragraph of first item. -2. Another item -``` - -1. First ordered list item - - Second paragraph of first item. -2. Another item - -## Links - -There are two ways to create links, inline-style and reference-style. - - [I'm an inline-style link](https://www.google.com) - - [I'm a reference-style link][Arbitrary case-insensitive reference text] - - [I'm a relative reference to a repository file](LICENSE) - - [You can use numbers for reference-style link definitions][1] - - Or leave it empty and use the [link text itself][] - - Some text to show that the reference links can follow later. - - [arbitrary case-insensitive reference text]: https://www.mozilla.org - [1]: http://slashdot.org - [link text itself]: https://www.reddit.com - -[I'm an inline-style link](https://www.google.com) - -[I'm a reference-style link][Arbitrary case-insensitive reference text] - -[I'm a relative reference to a repository file](LICENSE)[^1] - -[You can use numbers for reference-style link definitions][1] - -Or leave it empty and use the [link text itself][] - -Some text to show that the reference links can follow later. - -[arbitrary case-insensitive reference text]: https://www.mozilla.org -[1]: http://slashdot.org -[link text itself]: https://www.reddit.com - -**Note** - -Relative links do not allow referencing project files in a wiki page or wiki page in a project file. The reason for this is that, in GitLab, wiki is always a separate git repository. For example: - -`[I'm a reference-style link](style)` - -will point the link to `wikis/style` when the link is inside of a wiki markdown file. - -## Images - - Here's our logo (hover to see the title text): - - Inline-style: - ![alt text](img/logo.png) - - Reference-style: - ![alt text1][logo] - - [logo]: img/logo.png - -Here's our logo: - -Inline-style: - -![alt text](img/logo.png) - -Reference-style: - -![alt text][logo] - -[logo]: img/logo.png - -## Blockquotes - -```no-highlight -> Blockquotes are very handy in email to emulate reply text. -> This line is part of the same quote. - -Quote break. - -> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. -``` - -> Blockquotes are very handy in email to emulate reply text. -> This line is part of the same quote. - -Quote break. - -> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. - -## Inline HTML - -You can also use raw HTML in your Markdown, and it'll mostly work pretty well. - -See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes. In addition to the default `SanitizationFilter` whitelist, GitLab allows `span` elements. - -```no-highlight -
-
Definition list
-
Is something people use sometimes.
- -
Markdown in HTML
-
Does *not* work **very** well. Use HTML tags.
-
-``` - -
-
Definition list
-
Is something people use sometimes.
- -
Markdown in HTML
-
Does *not* work **very** well. Use HTML tags.
-
- -## Horizontal Rule - -``` -Three or more... - ---- - -Hyphens - -*** - -Asterisks - -___ - -Underscores -``` - -Three or more... - ---- - -Hyphens - -*** - -Asterisks - -___ - -Underscores - -## Line Breaks - -My basic recommendation for learning how line breaks work is to experiment and discover -- hit <Enter> once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. "Markdown Toggle" is your friend. - -Here are some things to try out: - -``` -Here's a line for us to start with. - -This line is separated from the one above by two newlines, so it will be a *separate paragraph*. - -This line is also a separate paragraph, but... -This line is only separated by a single newline, so it's a separate line in the *same paragraph*. - -This line is also a separate paragraph, and... -This line is on its own line, because the previous line ends with two -spaces. -``` - -Here's a line for us to start with. - -This line is separated from the one above by two newlines, so it will be a *separate paragraph*. - -This line is also begins a separate paragraph, but... -This line is only separated by a single newline, so it's a separate line in the *same paragraph*. - -This line is also a separate paragraph, and... -This line is on its own line, because the previous line ends with two -spaces. - -## Tables - -Tables aren't part of the core Markdown spec, but they are part of GFM and Markdown Here supports them. - -``` -| header 1 | header 2 | -| -------- | -------- | -| cell 1 | cell 2 | -| cell 3 | cell 4 | -``` - -Code above produces next output: - -| header 1 | header 2 | -| -------- | -------- | -| cell 1 | cell 2 | -| cell 3 | cell 4 | - -**Note** - -The row of dashes between the table header and body must have at least three dashes in each column. - -By including colons in the header row, you can align the text within that column: - -``` -| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned | -| :----------- | :------: | ------------: | :----------- | :------: | ------------: | -| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | -| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | -``` - -| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned | -| :----------- | :------: | ------------: | :----------- | :------: | ------------: | -| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | -| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | - - -## Wiki-specific Markdown - -The following examples show how links inside wikis behave. - -### Wiki - Direct page link - -A link which just includes the slug for a page will point to that page, -_at the base level of the wiki_. - -This snippet would link to a `documentation` page at the root of your wiki: - -```markdown -[Link to Documentation](documentation) -``` - -### Wiki - Direct file link - -Links with a file extension point to that file, _relative to the current page_. - -If this snippet was placed on a page at `/documentation/related`, -it would link to `/documentation/file.md`: - -```markdown -[Link to File](file.md) -``` - -### Wiki - Hierarchical link - -A link can be constructed relative to the current wiki page using `./`, -`../`, etc. - -- If this snippet was placed on a page at `/documentation/main`, - it would link to `/documentation/related`: - - ```markdown - [Link to Related Page](./related) - ``` - -- If this snippet was placed on a page at `/documentation/related/content`, - it would link to `/documentation/main`: - - ```markdown - [Link to Related Page](../main) - ``` - -- If this snippet was placed on a page at `/documentation/main`, - it would link to `/documentation/related.md`: - - ```markdown - [Link to Related Page](./related.md) - ``` - -- If this snippet was placed on a page at `/documentation/related/content`, - it would link to `/documentation/main.md`: - - ```markdown - [Link to Related Page](../main.md) - ``` - -### Wiki - Root link - -A link starting with a `/` is relative to the wiki root. - -- This snippet links to `/documentation`: - - ```markdown - [Link to Related Page](/documentation) - ``` - -- This snippet links to `/miscellaneous.md`: - - ```markdown - [Link to Related Page](/miscellaneous.md) - ``` -## References - -- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). -- The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown. -- [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown. - -[markdown.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md -[rouge]: http://rouge.jneen.net/ "Rouge website" -[redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website" -[^1]: This link will be broken if you see this document from the Help page or docs.gitlab.com diff --git a/doc/user/project/img/markdown_logo.png b/doc/user/project/img/markdown_logo.png new file mode 100644 index 00000000000..05c8b0d0ccf Binary files /dev/null and b/doc/user/project/img/markdown_logo.png differ diff --git a/doc/user/project/img/markdown_video.mp4 b/doc/user/project/img/markdown_video.mp4 new file mode 100644 index 00000000000..1fc478842f5 Binary files /dev/null and b/doc/user/project/img/markdown_video.mp4 differ diff --git a/doc/user/project/markdown.md b/doc/user/project/markdown.md new file mode 100644 index 00000000000..7fe96e67dbb --- /dev/null +++ b/doc/user/project/markdown.md @@ -0,0 +1,786 @@ +# Markdown + +## Table of Contents + +**[GitLab Flavored Markdown](#gitlab-flavored-markdown-gfm)** + +* [Newlines](#newlines) +* [Multiple underscores in words](#multiple-underscores-in-words) +* [URL auto-linking](#url-auto-linking) +* [Multiline Blockquote](#multiline-blockquote) +* [Code and Syntax Highlighting](#code-and-syntax-highlighting) +* [Inline Diff](#inline-diff) +* [Emoji](#emoji) +* [Special GitLab references](#special-gitlab-references) +* [Task Lists](#task-lists) +* [Videos](#videos) + +**[Standard Markdown](#standard-markdown)** + +* [Headers](#headers) +* [Emphasis](#emphasis) +* [Lists](#lists) +* [Links](#links) +* [Images](#images) +* [Blockquotes](#blockquotes) +* [Inline HTML](#inline-html) +* [Horizontal Rule](#horizontal-rule) +* [Line Breaks](#line-breaks) +* [Tables](#tables) + +**[Wiki-Specific Markdown](#wiki-specific-markdown)** + +* [Wiki - Direct page link](#wiki-direct-page-link) +* [Wiki - Direct file link](#wiki-direct-file-link) +* [Wiki - Hierarchical link](#wiki-hierarchical-link) +* [Wiki - Root link](#wiki-root-link) + +**[References](#references)** + +## GitLab Flavored Markdown (GFM) + +> **Note:** +Not all of the GitLab-specific extensions to Markdown that are described in +this document currently work on our documentation website. +> +For the best result, we encourage you to check this document out as rendered +by GitLab: [markdown.md] + +_GitLab uses the [Redcarpet Ruby library][redcarpet] for Markdown processing._ + +GitLab uses "GitLab Flavored Markdown" (GFM). It extends the standard Markdown in a few significant ways to add some useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). + +You can use GFM in the following areas: + +- comments +- issues +- merge requests +- milestones +- snippets (the snippet must be named with a `.md` extension) +- wiki pages +- markdown documents inside the repository + +You can also use other rich text files in GitLab. You might have to install a +dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information. + +## Newlines + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#newlines + +GFM honors the markdown specification in how [paragraphs and line breaks are handled](https://daringfireball.net/projects/markdown/syntax#p). + +A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. +Line-breaks, or softreturns, are rendered if you end a line with two or more spaces: + + Roses are red [followed by two or more spaces] + Violets are blue + + Sugar is sweet + +Roses are red +Violets are blue + +Sugar is sweet + +## Multiple underscores in words + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiple-underscores-in-words + +It is not reasonable to italicize just _part_ of a word, especially when you're dealing with code and names that often appear with multiple underscores. Therefore, GFM ignores multiple underscores in words: + + perform_complicated_task + + do_this_and_do_that_and_another_thing + +perform_complicated_task + +do_this_and_do_that_and_another_thing + +## URL auto-linking + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#url-auto-linking + +GFM will autolink almost any URL you copy and paste into your text: + + * https://www.google.com + * https://google.com/ + * ftp://ftp.us.debian.org/debian/ + * smb://foo/bar/baz + * irc://irc.freenode.net/gitlab + * http://localhost:3000 + +* https://www.google.com +* https://google.com/ +* ftp://ftp.us.debian.org/debian/ +* smb://foo/bar/baz +* irc://irc.freenode.net/gitlab +* http://localhost:3000 + +## Multiline Blockquote + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiline-blockquote + +On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines, +GFM supports multiline blockquotes fenced by >>>: + +```no-highlight +>>> +If you paste a message from somewhere else + +that + +spans + +multiple lines, + +you can quote that without having to manually prepend `>` to every line! +>>> +``` + +>>> +If you paste a message from somewhere else + +that + +spans + +multiple lines, + +you can quote that without having to manually prepend `>` to every line! +>>> + +## Code and Syntax Highlighting + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#code-and-syntax-highlighting + +_GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a +list of supported languages visit the Rouge website._ + +Blocks of code are either fenced by lines with three back-ticks ```, +or are indented with four spaces. Only the fenced code blocks support syntax +highlighting: + +```no-highlight +Inline `code` has `back-ticks around` it. +``` + +Inline `code` has `back-ticks around` it. + +Example: + + ```javascript + var s = "JavaScript syntax highlighting"; + alert(s); + ``` + + ```python + def function(): + #indenting works just fine in the fenced code block + s = "Python syntax highlighting" + print s + ``` + + ```ruby + require 'redcarpet' + markdown = Redcarpet.new("Hello World!") + puts markdown.to_html + ``` + + ``` + No language indicated, so no syntax highlighting. + s = "There is no highlighting for this." + But let's throw in a tag. + ``` + +becomes: + +```javascript +var s = "JavaScript syntax highlighting"; +alert(s); +``` + +```python +def function(): + #indenting works just fine in the fenced code block + s = "Python syntax highlighting" + print s +``` + +```ruby +require 'redcarpet' +markdown = Redcarpet.new("Hello World!") +puts markdown.to_html +``` + +``` +No language indicated, so no syntax highlighting. +s = "There is no highlighting for this." +But let's throw in a tag. +``` + +## Inline Diff + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#inline-diff + +With inline diffs tags you can display {+ additions +} or [- deletions -]. + +The wrapping tags can be either curly braces or square brackets [+ additions +] or {- deletions -}. + +However the wrapping tags cannot be mixed as such: + +- {+ additions +] +- [+ additions +} +- {- deletions -] +- [- deletions -} + +## Emoji + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#emoji + + Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: + + :zap: You can use emoji anywhere GFM is supported. :v: + + You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. + + If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. + + Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup: + +Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: + +:zap: You can use emoji anywhere GFM is supported. :v: + +You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. + +If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. + +Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup: + +## Special GitLab References + +GFM recognizes special references. + +You can easily reference e.g. an issue, a commit, a team member or even the whole team within a project. + +GFM will turn that reference into a link so you can navigate between them easily. + +GFM will recognize the following: + +| input | references | +|:-----------------------|:--------------------------- | +| `@user_name` | specific user | +| `@group_name` | specific group | +| `@all` | entire team | +| `#123` | issue | +| `!123` | merge request | +| `$123` | snippet | +| `~123` | label by ID | +| `~bug` | one-word label by name | +| `~"feature request"` | multi-word label by name | +| `%123` | milestone by ID | +| `%v1.23` | one-word milestone by name | +| `%"release candidate"` | multi-word milestone by name | +| `9ba12248` | specific commit | +| `9ba12248...b19a04f5` | commit range comparison | +| `[README](doc/README)` | repository file references | + +GFM also recognizes certain cross-project references: + +| input | references | +|:----------------------------------------|:------------------------| +| `namespace/project#123` | issue | +| `namespace/project!123` | merge request | +| `namespace/project%123` | milestone | +| `namespace/project$123` | snippet | +| `namespace/project@9ba12248` | specific commit | +| `namespace/project@9ba12248...b19a04f5` | commit range comparison | +| `namespace/project~"Some label"` | issues with given label | + +## Task Lists + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#task-lists + +You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so: + +```no-highlight +- [x] Completed task +- [ ] Incomplete task + - [ ] Sub-task 1 + - [x] Sub-task 2 + - [ ] Sub-task 3 +``` + +- [x] Completed task +- [ ] Incomplete task + - [ ] Sub-task 1 + - [x] Sub-task 2 + - [ ] Sub-task 3 + +Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes. + +## Videos + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#videos + +Image tags with a video extension are automatically converted to a video player. + +The valid video extensions are `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`. + + Here's a sample video: + + ![Sample Video](img/markdown_video.mp4) + +Here's a sample video: + +![Sample Video](img/markdown_video.mp4) + +# Standard Markdown + +## Headers + +```no-highlight +# H1 +## H2 +### H3 +#### H4 +##### H5 +###### H6 + +Alternatively, for H1 and H2, an underline-ish style: + +Alt-H1 +====== + +Alt-H2 +------ +``` + +# H1 +## H2 +### H3 +#### H4 +##### H5 +###### H6 + +Alternatively, for H1 and H2, an underline-ish style: + +Alt-H1 +====== + +Alt-H2 +------ + +### Header IDs and links + +All Markdown-rendered headers automatically get IDs, except in comments. + +On hover a link to those IDs becomes visible to make it easier to copy the link to the header to give it to someone else. + +The IDs are generated from the content of the header according to the following rules: + +1. All text is converted to lowercase +1. All non-word text (e.g., punctuation, HTML) is removed +1. All spaces are converted to hyphens +1. Two or more hyphens in a row are converted to one +1. If a header with the same ID has already been generated, a unique + incrementing number is appended, starting at 1. + +For example: + +``` +# This header has spaces in it +## This header has a :thumbsup: in it +# This header has Unicode in it: 한글 +## This header has spaces in it +### This header has spaces in it +``` + +Would generate the following link IDs: + +1. `this-header-has-spaces-in-it` +1. `this-header-has-a-in-it` +1. `this-header-has-unicode-in-it-한글` +1. `this-header-has-spaces-in-it` +1. `this-header-has-spaces-in-it-1` + +Note that the Emoji processing happens before the header IDs are generated, so the Emoji is converted to an image which then gets removed from the ID. + +## Emphasis + +```no-highlight +Emphasis, aka italics, with *asterisks* or _underscores_. + +Strong emphasis, aka bold, with **asterisks** or __underscores__. + +Combined emphasis with **asterisks and _underscores_**. + +Strikethrough uses two tildes. ~~Scratch this.~~ +``` + +Emphasis, aka italics, with *asterisks* or _underscores_. + +Strong emphasis, aka bold, with **asterisks** or __underscores__. + +Combined emphasis with **asterisks and _underscores_**. + +Strikethrough uses two tildes. ~~Scratch this.~~ + +## Lists + +```no-highlight +1. First ordered list item +2. Another item + * Unordered sub-list. +1. Actual numbers don't matter, just that it's a number + 1. Ordered sub-list +4. And another item. + +* Unordered list can use asterisks +- Or minuses ++ Or pluses +``` + +1. First ordered list item +2. Another item + * Unordered sub-list. +1. Actual numbers don't matter, just that it's a number + 1. Ordered sub-list +4. And another item. + +* Unordered list can use asterisks +- Or minuses ++ Or pluses + +If a list item contains multiple paragraphs, +each subsequent paragraph should be indented with four spaces. + +```no-highlight +1. First ordered list item + + Second paragraph of first item. +2. Another item +``` + +1. First ordered list item + + Second paragraph of first item. +2. Another item + +If the second paragraph isn't indented with four spaces, +the second list item will be incorrectly labeled as `1`. + +```no-highlight +1. First ordered list item + + Second paragraph of first item. +2. Another item +``` + +1. First ordered list item + + Second paragraph of first item. +2. Another item + +## Links + +There are two ways to create links, inline-style and reference-style. + + [I'm an inline-style link](https://www.google.com) + + [I'm a reference-style link][Arbitrary case-insensitive reference text] + + [I'm a relative reference to a repository file](LICENSE) + + [You can use numbers for reference-style link definitions][1] + + Or leave it empty and use the [link text itself][] + + Some text to show that the reference links can follow later. + + [arbitrary case-insensitive reference text]: https://www.mozilla.org + [1]: http://slashdot.org + [link text itself]: https://www.reddit.com + +[I'm an inline-style link](https://www.google.com) + +[I'm a reference-style link][Arbitrary case-insensitive reference text] + +[I'm a relative reference to a repository file](LICENSE)[^1] + +[You can use numbers for reference-style link definitions][1] + +Or leave it empty and use the [link text itself][] + +Some text to show that the reference links can follow later. + +[arbitrary case-insensitive reference text]: https://www.mozilla.org +[1]: http://slashdot.org +[link text itself]: https://www.reddit.com + +**Note** + +Relative links do not allow referencing project files in a wiki page or wiki page in a project file. The reason for this is that, in GitLab, wiki is always a separate git repository. For example: + +`[I'm a reference-style link](style)` + +will point the link to `wikis/style` when the link is inside of a wiki markdown file. + +## Images + + Here's our logo (hover to see the title text): + + Inline-style: + ![alt text](img/markdown_logo.png) + + Reference-style: + ![alt text1][logo] + + [logo]: img/markdown_logo.png + +Here's our logo: + +Inline-style: + +![alt text](img/markdown_logo.png) + +Reference-style: + +![alt text][logo] + +[logo]: img/markdown_logo.png + +## Blockquotes + +```no-highlight +> Blockquotes are very handy in email to emulate reply text. +> This line is part of the same quote. + +Quote break. + +> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. +``` + +> Blockquotes are very handy in email to emulate reply text. +> This line is part of the same quote. + +Quote break. + +> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. + +## Inline HTML + +You can also use raw HTML in your Markdown, and it'll mostly work pretty well. + +See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes. In addition to the default `SanitizationFilter` whitelist, GitLab allows `span` elements. + +```no-highlight +
+
Definition list
+
Is something people use sometimes.
+ +
Markdown in HTML
+
Does *not* work **very** well. Use HTML tags.
+
+``` + +
+
Definition list
+
Is something people use sometimes.
+ +
Markdown in HTML
+
Does *not* work **very** well. Use HTML tags.
+
+ +## Horizontal Rule + +``` +Three or more... + +--- + +Hyphens + +*** + +Asterisks + +___ + +Underscores +``` + +Three or more... + +--- + +Hyphens + +*** + +Asterisks + +___ + +Underscores + +## Line Breaks + +My basic recommendation for learning how line breaks work is to experiment and discover -- hit <Enter> once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. "Markdown Toggle" is your friend. + +Here are some things to try out: + +``` +Here's a line for us to start with. + +This line is separated from the one above by two newlines, so it will be a *separate paragraph*. + +This line is also a separate paragraph, but... +This line is only separated by a single newline, so it's a separate line in the *same paragraph*. + +This line is also a separate paragraph, and... +This line is on its own line, because the previous line ends with two +spaces. +``` + +Here's a line for us to start with. + +This line is separated from the one above by two newlines, so it will be a *separate paragraph*. + +This line is also begins a separate paragraph, but... +This line is only separated by a single newline, so it's a separate line in the *same paragraph*. + +This line is also a separate paragraph, and... +This line is on its own line, because the previous line ends with two +spaces. + +## Tables + +Tables aren't part of the core Markdown spec, but they are part of GFM and Markdown Here supports them. + +``` +| header 1 | header 2 | +| -------- | -------- | +| cell 1 | cell 2 | +| cell 3 | cell 4 | +``` + +Code above produces next output: + +| header 1 | header 2 | +| -------- | -------- | +| cell 1 | cell 2 | +| cell 3 | cell 4 | + +**Note** + +The row of dashes between the table header and body must have at least three dashes in each column. + +By including colons in the header row, you can align the text within that column: + +``` +| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned | +| :----------- | :------: | ------------: | :----------- | :------: | ------------: | +| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | +| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | +``` + +| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned | +| :----------- | :------: | ------------: | :----------- | :------: | ------------: | +| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | +| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | + + +## Wiki-specific Markdown + +The following examples show how links inside wikis behave. + +### Wiki - Direct page link + +A link which just includes the slug for a page will point to that page, +_at the base level of the wiki_. + +This snippet would link to a `documentation` page at the root of your wiki: + +```markdown +[Link to Documentation](documentation) +``` + +### Wiki - Direct file link + +Links with a file extension point to that file, _relative to the current page_. + +If this snippet was placed on a page at `/documentation/related`, +it would link to `/documentation/file.md`: + +```markdown +[Link to File](file.md) +``` + +### Wiki - Hierarchical link + +A link can be constructed relative to the current wiki page using `./`, +`../`, etc. + +- If this snippet was placed on a page at `/documentation/main`, + it would link to `/documentation/related`: + + ```markdown + [Link to Related Page](./related) + ``` + +- If this snippet was placed on a page at `/documentation/related/content`, + it would link to `/documentation/main`: + + ```markdown + [Link to Related Page](../main) + ``` + +- If this snippet was placed on a page at `/documentation/main`, + it would link to `/documentation/related.md`: + + ```markdown + [Link to Related Page](./related.md) + ``` + +- If this snippet was placed on a page at `/documentation/related/content`, + it would link to `/documentation/main.md`: + + ```markdown + [Link to Related Page](../main.md) + ``` + +### Wiki - Root link + +A link starting with a `/` is relative to the wiki root. + +- This snippet links to `/documentation`: + + ```markdown + [Link to Related Page](/documentation) + ``` + +- This snippet links to `/miscellaneous.md`: + + ```markdown + [Link to Related Page](/miscellaneous.md) + ``` +## References + +- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). +- The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown. +- [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown. + +[markdown.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md +[rouge]: http://rouge.jneen.net/ "Rouge website" +[redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website" +[^1]: This link will be broken if you see this document from the Help page or docs.gitlab.com -- cgit v1.2.1 From a33a2ba0a60930cf617675186f7c59139232b44c Mon Sep 17 00:00:00 2001 From: Elias Werberich Date: Sun, 31 Jul 2016 17:34:27 +0200 Subject: Fix confusing description of a blocked user. --- CHANGELOG | 1 + app/views/admin/users/show.html.haml | 8 +++----- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c099c63ce86..b8fca1fe40a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,6 +29,7 @@ v 8.11.0 (unreleased) - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le) - Load project invited groups and members eagerly in `ProjectTeam#fetch_members` - Bump gitlab_git to speedup DiffCollection iterations + - Rewrite description of a blocked user in admin settings. (Elias Werberich) - Make branches sortable without push permission !5462 (winniehell) - Check for Ci::Build artifacts at database level on pipeline partial - Make "New issue" button in Issue page less obtrusive !5457 (winniehell) diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index d37489bebea..76c9ed0ee8b 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -140,12 +140,10 @@ .panel-heading This user is blocked .panel-body - %p Blocking user has the following effects: + %p A blocked user cannot: %ul - %li User will not be able to login - %li User will not be able to access git repositories - %li Personal projects will be left - %li Owned groups will be left + %li Log in + %li Access Git repositories %br = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' } - else -- cgit v1.2.1 From 946d3b132e6f8e95b7d8b95e32fd8231b835137d Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 2 Aug 2016 13:29:43 +0200 Subject: Instrument the Repository class Since this isn't an ActiveRecord::Base descendant it wasn't instrumented. --- CHANGELOG | 1 + config/initializers/metrics.rb | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index c099c63ce86..42476248256 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.11.0 (unreleased) - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell) - Fix CI status icon link underline (ClemMakesApps) + - The Repository class is now instrumented - Cache the commit author in RequestStore to avoid extra lookups in PostReceive - Fix of 'Commits being passed to custom hooks are already reachable when using the UI' - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index f3cddac5b36..b68a09ce730 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -144,6 +144,7 @@ if Gitlab::Metrics.enabled? end config.instrument_methods(Rinku) + config.instrument_instance_methods(Repository) end GC::Profiler.enable -- cgit v1.2.1 From b371d751287fd8a01126c7aa5f156f868d177ef2 Mon Sep 17 00:00:00 2001 From: Keith Pope Date: Tue, 2 Aug 2016 12:49:59 +0100 Subject: Tidy the key spec and fix failing user spec --- spec/models/key_spec.rb | 2 +- spec/models/user_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index a4d46ca84de..6d68e52a822 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -22,7 +22,7 @@ describe Key, models: true do describe "#publishable_keys" do it 'replaces SSH key comment with simple identifier of username + hostname' do - expect(build(:key, user: user).publishable_key).to match(/#{Regexp.escape(user.name)} \(localhost\)/) + expect(build(:key, user: user).publishable_key).to include("#{user.name} (localhost)") end end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 2a5a7fb2fc6..9f432501c59 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -643,7 +643,7 @@ describe User, models: true do user = create :user key = create :key, key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD33bWLBxu48Sev9Fert1yzEO4WGcWglWF7K/AwblIUFselOt/QdOL9DSjpQGxLagO1s9wl53STIO8qGS4Ms0EJZyIXOEFMjFJ5xmjSy+S37By4sG7SsltQEHMxtbtFOaW5LV2wCrX+rUsRNqLMamZjgjcPO0/EgGCXIGMAYW4O7cwGZdXWYIhQ1Vwy+CsVMDdPkPgBXqK7nR/ey8KMs8ho5fMNgB5hBw/AL9fNGhRw3QTD6Q12Nkhl4VZES2EsZqlpNnJttnPdp847DUsT6yuLRlfiQfz5Cn9ysHFdXObMN5VYIiPFwHeYCZp1X2S4fDZooRE8uOLTfxWHPXwrhqSH", user_id: user.id - expect(user.all_ssh_keys).to include(key.key) + expect(user.all_ssh_keys).to include(a_string_starting_with(key.key)) end end -- cgit v1.2.1 From 09257ce34c74a56bc08fd385d844a6d32b01e507 Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Tue, 2 Aug 2016 13:40:41 +0100 Subject: link the engineering workflow document from process and contributing --- CONTRIBUTING.md | 2 ++ PROCESS.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 14ff05c9aa3..a885e706810 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,6 +41,8 @@ abbreviation. If you have read this guide and want to know how the GitLab [core team] operates please see [the GitLab contributing process](PROCESS.md). +- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/) + ## Contributor license agreement By submitting code as an individual you agree to the diff --git a/PROCESS.md b/PROCESS.md index fe3a963110d..8e1a3f7360f 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -8,6 +8,8 @@ treatment, etc.). And so that maintainers know what to expect from contributors (use the latest version, ensure that the issue is addressed, friendly treatment, etc.). +- [GitLab Inc engineers should refer to the engineering workflow document](https://about.gitlab.com/handbook/engineering/workflow/) + ## Common actions ### Issue team -- cgit v1.2.1 From 98a21cd4be0c2cf0f796fd8d602af0bef86cece3 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 2 Aug 2016 16:16:38 +0300 Subject: Remove outdated notes from the update guide --- doc/update/8.10-to-8.11.md | 32 ++++---------------------------- 1 file changed, 4 insertions(+), 28 deletions(-) diff --git a/doc/update/8.10-to-8.11.md b/doc/update/8.10-to-8.11.md index fc6262dd108..25343d484ba 100644 --- a/doc/update/8.10-to-8.11.md +++ b/doc/update/8.10-to-8.11.md @@ -62,23 +62,7 @@ sudo -u git -H git checkout v0.7.8 sudo -u git -H make ``` -### 6. Update MySQL permissions - -If you are using MySQL you need to grant the GitLab user the necessary -permissions on the database: - -```bash -# Login to MySQL -mysql -u root -p - -# Grant the GitLab user the REFERENCES permission on the database -GRANT REFERENCES ON `gitlabhq_production`.* TO 'git'@'localhost'; - -# Quit the database session -mysql> \q -``` - -### 7. Install libs, migrations, etc. +### 6. Install libs, migrations, etc. ```bash cd /home/git/gitlab @@ -100,7 +84,7 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS ``` -### 8. Update configuration files +### 7. Update configuration files #### New configuration options for `gitlab.yml` @@ -110,14 +94,6 @@ There are new configuration options available for [`gitlab.yml`](config/gitlab.y git diff origin/8-10-stable:config/gitlab.yml.example origin/8-11-stable:config/gitlab.yml.example ``` -#### Git configuration - -Disable `git gc --auto` because GitLab runs `git gc` for us already. - -```sh -sudo -u git -H git config --global gc.auto 0 -``` - #### Nginx configuration Ensure you're still up-to-date with the latest NGINX configuration changes: @@ -157,12 +133,12 @@ Ensure you're still up-to-date with the latest init script changes: sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab -### 9. Start application +### 8. Start application sudo service gitlab start sudo service nginx restart -### 10. Check application status +### 9. Check application status Check if GitLab and its environment are configured correctly: -- cgit v1.2.1 From 8716ff7f63fff0b056e110bef930c32a98e86c63 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Mon, 1 Aug 2016 16:55:51 +0200 Subject: Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible - Preloading noteable we share the same noteable instance when more than one discussion refers to the same noteable. - Any other call to that object that is cached in that object will be for any discussion. - In those cases where merge_request_diff has all the sha stored to build a diff_refs get that diff_refs using directly those sha instead accessing to the git repository to first get the commits and later the sha. --- CHANGELOG | 1 + .../projects/merge_requests_controller.rb | 2 ++ app/helpers/notes_helper.rb | 4 ++++ app/models/diff_note.rb | 12 +++++++++-- app/models/discussion.rb | 6 ++++++ app/models/merge_request.rb | 15 +++++++++++++- app/models/merge_request_diff.rb | 4 ++++ spec/models/diff_note_spec.rb | 4 ++-- spec/models/merge_request_spec.rb | 24 ++++++++++++++++++++++ 9 files changed, 67 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 42476248256..ece0a5c1e78 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -40,6 +40,7 @@ v 8.11.0 (unreleased) - Multiple trigger variables show in separate lines (Katarzyna Kobierska Ula Budziszewska) - Profile requests when a header is passed - Avoid calculation of line_code and position for _line partial when showing diff notes on discussion tab. + - Speedup DiffNote#active? on discussions, preloading noteables and avoid touching git repository to return diff_refs when possible - Add commit stats in commit api. !5517 (dixpac) - Make error pages responsive (Takuya Noguchi) - Change requests_profiles resource constraint to catch virtually any file diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 03166294ddd..116e7904a4e 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -378,6 +378,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController fresh. discussions + preload_noteable_for_regular_notes(@discussions.flat_map(&:notes)) + # This is not executed lazily @notes = Banzai::NoteRenderer.render( @discussions.flat_map(&:notes), diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 0c47abe0fba..26bde2230a9 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -92,6 +92,10 @@ module NotesHelper project.team.max_member_access_for_user_ids(user_ids) end + def preload_noteable_for_regular_notes(notes) + ActiveRecord::Associations::Preloader.new.preload(notes.select { |note| !note.for_commit? }, :noteable) + end + def note_max_access_for_user(note) note.project.team.human_max_access(note.author_id) end diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 9671955db36..c816deb4e0c 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -67,7 +67,7 @@ class DiffNote < Note return false unless supported? return true if for_commit? - diff_refs ||= self.noteable.diff_refs + diff_refs ||= noteable_diff_refs self.position.diff_refs == diff_refs end @@ -78,6 +78,14 @@ class DiffNote < Note !self.for_merge_request? || self.noteable.support_new_diff_notes? end + def noteable_diff_refs + if noteable.respond_to?(:diff_sha_refs) + noteable.diff_sha_refs + else + noteable.diff_refs + end + end + def set_original_position self.original_position = self.position.dup end @@ -96,7 +104,7 @@ class DiffNote < Note self.project, nil, old_diff_refs: self.position.diff_refs, - new_diff_refs: self.noteable.diff_refs, + new_diff_refs: noteable_diff_refs, paths: self.position.paths ).execute(self) end diff --git a/app/models/discussion.rb b/app/models/discussion.rb index 74facfd1c9c..e2218a5f02b 100644 --- a/app/models/discussion.rb +++ b/app/models/discussion.rb @@ -49,6 +49,12 @@ class Discussion self.noteable == target && !diff_discussion? end + def active? + return @active if defined?(@active) + + @active = first_note.active? + end + def expanded? !diff_discussion? || active? end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index f1b9c1d6feb..a99c4ba52a4 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -255,6 +255,19 @@ class MergeRequest < ActiveRecord::Base ) end + # Return diff_refs instance trying to not touch the git repository + def diff_sha_refs + if merge_request_diff && merge_request_diff.diff_refs_by_sha? + return Gitlab::Diff::DiffRefs.new( + base_sha: merge_request_diff.base_commit_sha, + start_sha: merge_request_diff.start_commit_sha, + head_sha: merge_request_diff.head_commit_sha + ) + else + diff_refs + end + end + def validate_branches if target_project == source_project && target_branch == source_branch errors.add :branch_conflict, "You can not use same project/branch for source and target" @@ -659,7 +672,7 @@ class MergeRequest < ActiveRecord::Base end def support_new_diff_notes? - diff_refs && diff_refs.complete? + diff_sha_refs && diff_sha_refs.complete? end def update_diff_notes_positions(old_diff_refs:, new_diff_refs:) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 3f520c8f3ff..119266f2d2c 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -82,6 +82,10 @@ class MergeRequestDiff < ActiveRecord::Base project.commit(self.head_commit_sha) end + def diff_refs_by_sha? + base_commit_sha? && head_commit_sha? && start_commit_sha? + end + def compare @compare ||= begin diff --git a/spec/models/diff_note_spec.rb b/spec/models/diff_note_spec.rb index af8e890ca95..1fa96eb1f15 100644 --- a/spec/models/diff_note_spec.rb +++ b/spec/models/diff_note_spec.rb @@ -119,7 +119,7 @@ describe DiffNote, models: true do context "when the merge request's diff refs don't match that of the diff note" do before do - allow(subject.noteable).to receive(:diff_refs).and_return(commit.diff_refs) + allow(subject.noteable).to receive(:diff_sha_refs).and_return(commit.diff_refs) end it "returns false" do @@ -168,7 +168,7 @@ describe DiffNote, models: true do context "when the note is outdated" do before do - allow(merge_request).to receive(:diff_refs).and_return(commit.diff_refs) + allow(merge_request).to receive(:diff_sha_refs).and_return(commit.diff_refs) end it "uses the DiffPositionUpdateService" do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index a0e3c26e542..21d22c776e9 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -686,4 +686,28 @@ describe MergeRequest, models: true do subject.reload_diff end end + + describe "#diff_sha_refs" do + context "with diffs" do + subject { create(:merge_request, :with_diffs) } + + it "does not touch the repository" do + subject # Instantiate the object + + expect_any_instance_of(Repository).not_to receive(:commit) + + subject.diff_sha_refs + end + + it "returns expected diff_refs" do + expected_diff_refs = Gitlab::Diff::DiffRefs.new( + base_sha: subject.merge_request_diff.base_commit_sha, + start_sha: subject.merge_request_diff.start_commit_sha, + head_sha: subject.merge_request_diff.head_commit_sha + ) + + expect(subject.diff_sha_refs).to eq(expected_diff_refs) + end + end + end end -- cgit v1.2.1 From d707c91f706a26ff30b9d4862bb2c50b45a35fff Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 1 Aug 2016 22:57:15 -0700 Subject: Add guide for debugging issues with the Docker container registry [ci skip] --- doc/container_registry/README.md | 4 + doc/container_registry/img/mitmproxy-docker.png | Bin 0 -> 407004 bytes doc/container_registry/troubleshooting.md | 139 ++++++++++++++++++++++++ 3 files changed, 143 insertions(+) create mode 100644 doc/container_registry/img/mitmproxy-docker.png create mode 100644 doc/container_registry/troubleshooting.md diff --git a/doc/container_registry/README.md b/doc/container_registry/README.md index 55077197ff9..3db351811a8 100644 --- a/doc/container_registry/README.md +++ b/doc/container_registry/README.md @@ -90,6 +90,10 @@ your `.gitlab-ci.yml`, you have to follow the [Using a private Docker Registry][private-docker] documentation. This workflow will be simplified in the future. +## Troubleshooting + +See [the GitLab Docker registry troubleshooting guide](troubleshooting.md). + [ce-4040]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4040 [docker-docs]: https://docs.docker.com/engine/userguide/intro/ [private-docker]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/blob/master/docs/configuration/advanced-configuration.md#using-a-private-docker-registry diff --git a/doc/container_registry/img/mitmproxy-docker.png b/doc/container_registry/img/mitmproxy-docker.png new file mode 100644 index 00000000000..4e3e37b413d Binary files /dev/null and b/doc/container_registry/img/mitmproxy-docker.png differ diff --git a/doc/container_registry/troubleshooting.md b/doc/container_registry/troubleshooting.md new file mode 100644 index 00000000000..e21fc9a2c61 --- /dev/null +++ b/doc/container_registry/troubleshooting.md @@ -0,0 +1,139 @@ +# Troubleshooting the GitLab Container Registry + +## Basic Troubleshooting + +1. Check to make sure that the system clock on your Docker client and GitLab server have + been synchronized (e.g. via NTP). + +2. If you are using an S3-backed registry, double check that the IAM + permissions and the S3 credentials (including region) are correct. See [the + sample IAM policy](https://docs.docker.com/registry/storage-drivers/s3/) + for more details. + +3. Check the registry logs (e.g. `/var/log/gitlab/registry/current`) and the GitLab production logs + for errors (e.g. `/var/log/gitlab/gitlab-rails/production.log`). You may be able to find clues + there. + +# Advanced Troubleshooting + +NOTE: The following section is only recommended for experts. + +Sometimes it's not obvious what is wrong, and you may need to dive deeper into +the communication between the Docker client and the registry to find out +what's wrong. We will use a concrete example in the past to illustrate how to +diagnose a problem with the S3 setup. + +## Example: Unexpected 403 error during push + +A user attempted to enable an S3-backed registry. The `docker login` step went +fine. However, when pushing an image, the output showed: + +``` +The push refers to a repository [s3-testing.myregistry.com:4567/root/docker-test] +dc5e59c14160: Pushing [==================================================>] 14.85 kB +03c20c1a019a: Pushing [==================================================>] 2.048 kB +a08f14ef632e: Pushing [==================================================>] 2.048 kB +228950524c88: Pushing 2.048 kB +6a8ecde4cc03: Pushing [==> ] 9.901 MB/205.7 MB +5f70bf18a086: Pushing 1.024 kB +737f40e80b7f: Waiting +82b57dbc5385: Waiting +19429b698a22: Waiting +9436069b92a3: Waiting +error parsing HTTP 403 response body: unexpected end of JSON input: "" +``` + +This error is ambiguous, as it's not clear whether the 403 is coming from the GitLab Rails +application, the Docker registry, or something else. In this case, we know that since +the login succeeded, we probably need to look at the communication between the client +and the registry. + +The REST API between the Docker client and registry is [described +here](https://docs.docker.com/registry/spec/api/). Normally, one would just +use Wireshark or tcpdump to capture the traffic and see where things went +wrong. However, since all communication between Docker clients and servers +are done over HTTPS, it's a bit difficult to decrypt the traffic quickly even +if you know the private key. What can we do instead? + +## mitmproxy + +Enter [mitmproxy](https://mitmproxy.org/). This tool allows you to place a +proxy between your client and server to inspect all traffic. One wrinkle is +that your system needs to trust the mitmproxy SSL certificates for this +to work. + +The following installation instructions assume you are running Ubuntu: + +1. Install mitmproxy (see http://docs.mitmproxy.org/en/stable/install.html) + +2. Run `mitmproxy --port 9000` to generate its certificates. Enter CTRL-C to quit. + +3. Install the certificate from ~/.mitmproxy to your system: + + ```sh + sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt + sudo update-ca-certificates + ``` + +If successful, the output should indicate that a certificate was added: + +```sh +Updating certificates in /etc/ssl/certs... 1 added, 0 removed; done. +Running hooks in /etc/ca-certificates/update.d....done. +``` + +## Verifying mitmproxy certifiactes + +To verify that the certificates are properly install, run: + +```sh +mitmproxy --port 9000 +``` + +This will run mitmproxy on port 9000. In another window, run: + +```sh +curl --proxy http://localhost:9000 https://httpbin.org/status/200 +``` + +If everything is setup correctly, then you will see information on the mitmproxy window and +no errors from the curl commands. + +## Running the Docker daemon with a proxy + +For Docker to connect through a proxy, you must start the Docker daemon with the +proper environment variables. The easiest way is to shutdown Docker (e.g. `sudo initctl stop docker`) +and then run Docker by hand. As root, run: + +```sh +export HTTP_PROXY="http://localhost:9000" +export HTTPS_PROXY="https://localhost:9000" +docker daemon --debug +``` + +This will launch the Docker daemon and proxy all connections through mitmproxy. + +## Running the Docker client + +Now that we have mitmproxy and Docker running, we can now attempt to login and push a container +image. You may need to run as root to do this. For example: + +```sh +docker login s3-testing.myregistry.com:4567 +docker push s3-testing.myregistry.com:4567/root/docker-test +``` + +In the example above, we see the following trace on the mitmproxy window: + +![mitmproxy output from Docker](img/mitmproxy-docker.png) + +The above image shows: + +* The initial PUT requests went through fine with a 201 status code. +* The 201 redirected the client to the S3 bucket. +* The HEAD request to the AWS bucket reported a 403 Unauthorized. + +What does this mean? This strongly suggests that the S3 user does not have the right +[permissions to perform a HEAD request](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html). +The solution: check the [IAM permissions again](https://docs.docker.com/registry/storage-drivers/s3/). +Once the right permissions were set, the error went away. -- cgit v1.2.1 From 0ee33149fc7375201e14e79a6027e577d59c66a0 Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Tue, 2 Aug 2016 09:47:05 -0500 Subject: Add and update templates for 8.11 --- vendor/gitignore/Elm.gitignore | 2 +- vendor/gitignore/Go.gitignore | 3 ++ vendor/gitignore/Leiningen.gitignore | 3 +- vendor/gitignore/Objective-C.gitignore | 2 +- vendor/gitignore/Scala.gitignore | 4 +++ vendor/gitignore/SugarCRM.gitignore | 2 ++ vendor/gitignore/TeX.gitignore | 11 +++++++ vendor/gitignore/Terraform.gitignore | 3 ++ vendor/gitignore/Unity.gitignore | 3 +- vendor/gitlab-ci-yml/C++.gitlab-ci.yml | 26 +++++++++++++++++ vendor/gitlab-ci-yml/Grails.gitlab-ci.yml | 40 ++++++++++++++++++++++++++ vendor/gitlab-ci-yml/LaTeX.gitlab-ci.yml | 11 +++++++ vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml | 32 +++++++++++++++++++++ vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml | 2 ++ 14 files changed, 140 insertions(+), 4 deletions(-) create mode 100644 vendor/gitlab-ci-yml/C++.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Grails.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/LaTeX.gitlab-ci.yml create mode 100644 vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml diff --git a/vendor/gitignore/Elm.gitignore b/vendor/gitignore/Elm.gitignore index a594364e2c0..8b631e7de00 100644 --- a/vendor/gitignore/Elm.gitignore +++ b/vendor/gitignore/Elm.gitignore @@ -1,4 +1,4 @@ # elm-package generated files -elm-stuff/ +elm-stuff # elm-repl generated files repl-temp-* diff --git a/vendor/gitignore/Go.gitignore b/vendor/gitignore/Go.gitignore index daf913b1b34..cd0d5d1e2f4 100644 --- a/vendor/gitignore/Go.gitignore +++ b/vendor/gitignore/Go.gitignore @@ -22,3 +22,6 @@ _testmain.go *.exe *.test *.prof + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out diff --git a/vendor/gitignore/Leiningen.gitignore b/vendor/gitignore/Leiningen.gitignore index 47fed6c20d9..a9fe6fba80d 100644 --- a/vendor/gitignore/Leiningen.gitignore +++ b/vendor/gitignore/Leiningen.gitignore @@ -1,6 +1,7 @@ pom.xml pom.xml.asc -*jar +*.jar +*.class /lib/ /classes/ /target/ diff --git a/vendor/gitignore/Objective-C.gitignore b/vendor/gitignore/Objective-C.gitignore index 86f21d8e0ff..20592083931 100644 --- a/vendor/gitignore/Objective-C.gitignore +++ b/vendor/gitignore/Objective-C.gitignore @@ -52,7 +52,7 @@ Carthage/Build fastlane/report.xml fastlane/screenshots -#Code Injection +# Code Injection # # After new code Injection tools there's a generated folder /iOSInjectionProject # https://github.com/johnno1962/injectionforxcode diff --git a/vendor/gitignore/Scala.gitignore b/vendor/gitignore/Scala.gitignore index c58d83b3189..a02d882cb88 100644 --- a/vendor/gitignore/Scala.gitignore +++ b/vendor/gitignore/Scala.gitignore @@ -15,3 +15,7 @@ project/plugins/project/ # Scala-IDE specific .scala_dependencies .worksheet + +# ENSIME specific +.ensime_cache/ +.ensime diff --git a/vendor/gitignore/SugarCRM.gitignore b/vendor/gitignore/SugarCRM.gitignore index 842c3ec518b..e9270205fd5 100644 --- a/vendor/gitignore/SugarCRM.gitignore +++ b/vendor/gitignore/SugarCRM.gitignore @@ -7,6 +7,7 @@ # For development the cache directory can be safely ignored and # therefore it is ignored. /cache/ +!/cache/index.html # Ignore some files and directories from the custom directory. /custom/history/ /custom/modulebuilder/ @@ -22,4 +23,5 @@ *.log # Ignore the new upload directories. /upload/ +!/upload/index.html /upload_backup/ diff --git a/vendor/gitignore/TeX.gitignore b/vendor/gitignore/TeX.gitignore index 3cb097c9d5e..34f999df3e7 100644 --- a/vendor/gitignore/TeX.gitignore +++ b/vendor/gitignore/TeX.gitignore @@ -19,6 +19,9 @@ # *.eps # *.pdf +## Generated if empty string is given at "Please type another file name for output:" +.pdf + ## Bibliography auxiliary files (bibtex/biblatex/biber): *.bbl *.bcf @@ -31,6 +34,7 @@ ## Build tool auxiliary files: *.fdb_latexmk *.synctex +*.synctex(busy) *.synctex.gz *.synctex.gz(busy) *.pdfsync @@ -84,6 +88,10 @@ acs-*.bib # gnuplottex *-gnuplottex-* +# gregoriotex +*.gaux +*.gtex + # hyperref *.brf @@ -128,6 +136,9 @@ _minted* *.sagetex.py *.sagetex.scmd +# scrwfile +*.wrt + # sympy *.sout *.sympy diff --git a/vendor/gitignore/Terraform.gitignore b/vendor/gitignore/Terraform.gitignore index 7868d16d216..41859c81f1c 100644 --- a/vendor/gitignore/Terraform.gitignore +++ b/vendor/gitignore/Terraform.gitignore @@ -1,3 +1,6 @@ # Compiled files *.tfstate *.tfstate.backup + +# Module directory +.terraform/ diff --git a/vendor/gitignore/Unity.gitignore b/vendor/gitignore/Unity.gitignore index 5aafcbb7f1d..1c10388911b 100644 --- a/vendor/gitignore/Unity.gitignore +++ b/vendor/gitignore/Unity.gitignore @@ -5,8 +5,9 @@ /[Bb]uilds/ /Assets/AssetStoreTools* -# Autogenerated VS/MD solution and project files +# Autogenerated VS/MD/Consulo solution and project files ExportedObj/ +.consulo/ *.csproj *.unityproj *.sln diff --git a/vendor/gitlab-ci-yml/C++.gitlab-ci.yml b/vendor/gitlab-ci-yml/C++.gitlab-ci.yml new file mode 100644 index 00000000000..c83c49d8c95 --- /dev/null +++ b/vendor/gitlab-ci-yml/C++.gitlab-ci.yml @@ -0,0 +1,26 @@ +# use the official gcc image, based on debian +# can use verions as well, like gcc:5.2 +# see https://hub.docker.com/_/gcc/ +image: gcc + +build: + stage: build + # instead of calling g++ directly you can also use some build toolkit like make + # install the necessary build tools when needed + # before_script: + # - apt update && apt -y install make autoconf + script: + - g++ helloworld.cpp -o mybinary + artifacts: + paths: + - mybinary + # depending on your build setup it's most likely a good idea to cache outputs to reduce the build time + # cache: + # paths: + # - "*.o" + +# run tests using the binary built before +test: + stage: test + script: + - ./runmytests.sh diff --git a/vendor/gitlab-ci-yml/Grails.gitlab-ci.yml b/vendor/gitlab-ci-yml/Grails.gitlab-ci.yml new file mode 100644 index 00000000000..7fc698d50cf --- /dev/null +++ b/vendor/gitlab-ci-yml/Grails.gitlab-ci.yml @@ -0,0 +1,40 @@ +# This template uses the java:8 docker image because there isn't any +# official Grails image at this moment +# +# Grails Framework https://grails.org/ is a powerful Groovy-based web application framework for the JVM +# +# This yml works with Grails 3.x only +# Feel free to change GRAILS_VERSION version with your project version (3.0.1, 3.1.1,...) +# Feel free to change GRADLE_VERSION version with your gradle project version (2.13, 2.14,...) +# If you use Angular profile, this yml it's prepared to work with it + +image: java:8 + +variables: + GRAILS_VERSION: "3.1.9" + GRADLE_VERSION: "2.13" + +# We use SDKMan as tool for managing versions +before_script: + - apt-get update -qq && apt-get install -y -qq unzip + - curl -sSL https://get.sdkman.io | bash + - echo sdkman_auto_answer=true > /root/.sdkman/etc/config + - source /root/.sdkman/bin/sdkman-init.sh + - sdk install gradle $GRADLE_VERSION < /dev/null + - sdk use gradle $GRADLE_VERSION +# As it's not a good idea to version gradle.properties feel free to add your +# environments variable here + - echo grailsVersion=$GRAILS_VERSION > gradle.properties + - echo gradleWrapperVersion=2.14 >> gradle.properties +# refresh dependencies from your project + - ./gradlew --refresh-dependencies +# Be aware that if you are using Angular profile, +# Bower cannot be run as root if you don't allow it before. +# Feel free to remove next line if you are not using Bower + - echo {\"allow_root\":true} > /root/.bowerrc + +# This build job does the full grails pipeline +# (compile, test, integrationTest, war, assemble). +build: + script: + - ./gradlew build \ No newline at end of file diff --git a/vendor/gitlab-ci-yml/LaTeX.gitlab-ci.yml b/vendor/gitlab-ci-yml/LaTeX.gitlab-ci.yml new file mode 100644 index 00000000000..a4aed36889e --- /dev/null +++ b/vendor/gitlab-ci-yml/LaTeX.gitlab-ci.yml @@ -0,0 +1,11 @@ +# use docker image with latex preinstalled +# since there is no official latex image, use https://github.com/blang/latex-docker +# possible alternative: https://github.com/natlownes/docker-latex +image: blang/latex + +build: + script: + - latexmk -pdf + artifacts: + paths: + - "*.pdf" diff --git a/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml b/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml new file mode 100644 index 00000000000..bc36a4e6966 --- /dev/null +++ b/vendor/gitlab-ci-yml/Pages/JBake.gitlab-ci.yml @@ -0,0 +1,32 @@ +# This template uses the java:8 docker image because there isn't any +# official JBake image at this moment +# +# JBake https://jbake.org/ is a Java based, open source, static site/blog generator for developers & designers +# +# This yml works with jBake 2.4.0 +# Feel free to change JBAKE_VERSION version +# +# HowTo at: https://jorge.aguilera.gitlab.io/howtojbake/ + +image: java:8 + +variables: + JBAKE_VERSION: 2.4.0 + + +# We use SDKMan as tool for managing versions +before_script: + - apt-get update -qq && apt-get install -y -qq unzip + - curl -sSL https://get.sdkman.io | bash + - echo sdkman_auto_answer=true > /root/.sdkman/etc/config + - source /root/.sdkman/bin/sdkman-init.sh + - sdk install jbake $JBAKE_VERSION < /dev/null + - sdk use jbake $JBAKE_VERSION + +# This build job produced the output directory of your site +pages: + script: + - jbake . public + artifacts: + paths: + - public \ No newline at end of file diff --git a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml index 2a761bbd127..16a685ee03d 100644 --- a/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Ruby.gitlab-ci.yml @@ -19,6 +19,8 @@ cache: # services such as redis or postgres before_script: - ruby -v # Print out ruby version for debugging + # Uncomment next line if your rails app needs a JS runtime: + # - apt-get update -q && apt-get install nodejs -yqq - gem install bundler --no-ri --no-rdoc # Bundler is not installed with the image - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby -- cgit v1.2.1 From e4c517a635b6a45a9afc65b37682cc4b4951e922 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 25 Jul 2016 23:49:06 -0500 Subject: Expand commit message width in repo view --- CHANGELOG | 1 + app/assets/stylesheets/pages/tree.scss | 4 ++++ app/models/commit.rb | 14 ++++++++------ .../projects/tree/_tree_commit_column.html.haml | 2 +- spec/models/commit_spec.rb | 21 +++++++++++++++++++++ 5 files changed, 35 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 963fec597d5..b44627a9a71 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 8.11.0 (unreleased) - Fix CI status icon link underline (ClemMakesApps) - The Repository class is now instrumented - Cache the commit author in RequestStore to avoid extra lookups in PostReceive + - Expand commit message width in repo view (ClemMakesApps) - Fix of 'Commits being passed to custom hooks are already reachable when using the UI' - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable - Optimize maximum user access level lookup in loading of notes diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index 390977297fb..9da40fe2b09 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -58,6 +58,10 @@ .tree_commit { max-width: 320px; + + .str-truncated { + max-width: 100%; + } } .tree_time_ago { diff --git a/app/models/commit.rb b/app/models/commit.rb index 486ad6714d9..c52b4a051c2 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -123,15 +123,17 @@ class Commit # In case this first line is longer than 100 characters, it is cut off # after 80 characters and ellipses (`&hellp;`) are appended. def title - title = safe_message + full_title.length > 100 ? full_title[0..79] << "…" : full_title + end - return no_commit_message if title.blank? + # Returns the full commits title + def full_title + return @full_title if @full_title - title_end = title.index("\n") - if (!title_end && title.length > 100) || (title_end && title_end > 100) - title[0..79] << "…" + if safe_message.blank? + @full_title = no_commit_message else - title.split("\n", 2).first + @full_title = safe_message.split("\n", 2).first end end diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml index a3a4bd4f752..84da16b6bb1 100644 --- a/app/views/projects/tree/_tree_commit_column.html.haml +++ b/app/views/projects/tree/_tree_commit_column.html.haml @@ -1,2 +1,2 @@ %span.str-truncated - = link_to_gfm commit.title, namespace_project_commit_path(@project.namespace, @project, commit.id), class: "tree-commit-link" + = link_to_gfm commit.full_title, namespace_project_commit_path(@project.namespace, @project, commit.id), class: "tree-commit-link" diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index c3392ee7440..d3e6a6648cc 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -86,6 +86,27 @@ eos end end + describe '#full_title' do + it "returns no_commit_message when safe_message is blank" do + allow(commit).to receive(:safe_message).and_return('') + expect(commit.full_title).to eq("--no commit message") + end + + it "returns entire message if there is no newline" do + message = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sodales id felis id blandit. Vivamus egestas lacinia lacus, sed rutrum mauris.' + + allow(commit).to receive(:safe_message).and_return(message) + expect(commit.full_title).to eq(message) + end + + it "returns first line of message if there is a newLine" do + message = commit.safe_message.split(" ").first + + allow(commit).to receive(:safe_message).and_return(message + "\n" + message) + expect(commit.full_title).to eq(message) + end + end + describe "delegation" do subject { commit } -- cgit v1.2.1 From 46385e4e5a88a4ac614f680094b9226778cee64a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 2 Aug 2016 15:20:36 -0700 Subject: Add a note about setting up an insecure registry [ci skip] --- doc/container_registry/troubleshooting.md | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/doc/container_registry/troubleshooting.md b/doc/container_registry/troubleshooting.md index e21fc9a2c61..c24c80518dd 100644 --- a/doc/container_registry/troubleshooting.md +++ b/doc/container_registry/troubleshooting.md @@ -55,12 +55,17 @@ wrong. However, since all communication between Docker clients and servers are done over HTTPS, it's a bit difficult to decrypt the traffic quickly even if you know the private key. What can we do instead? +One way would be to disable HTTPS by setting up an [insecure +registry](https://docs.docker.com/registry/insecure/). This could introduce a +security hole and is only recommended for local testing. If you have a +production system and can't or don't want to do this, there is another way: +use mitmproxy, which stands for Man-in-the-Middle Proxy. + ## mitmproxy -Enter [mitmproxy](https://mitmproxy.org/). This tool allows you to place a -proxy between your client and server to inspect all traffic. One wrinkle is -that your system needs to trust the mitmproxy SSL certificates for this -to work. +[mitmproxy](https://mitmproxy.org/) allows you to place a proxy between your +client and server to inspect all traffic. One wrinkle is that your system +needs to trust the mitmproxy SSL certificates for this to work. The following installation instructions assume you are running Ubuntu: -- cgit v1.2.1 From 2c9cce0feb8bd4e10f3406493eff30e783782d15 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 2 Aug 2016 15:24:15 -0700 Subject: Grammar improvements [ci skip] --- doc/container_registry/troubleshooting.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/container_registry/troubleshooting.md b/doc/container_registry/troubleshooting.md index c24c80518dd..8008bf29935 100644 --- a/doc/container_registry/troubleshooting.md +++ b/doc/container_registry/troubleshooting.md @@ -43,10 +43,10 @@ a08f14ef632e: Pushing [==================================================>] 2.04 error parsing HTTP 403 response body: unexpected end of JSON input: "" ``` -This error is ambiguous, as it's not clear whether the 403 is coming from the GitLab Rails -application, the Docker registry, or something else. In this case, we know that since -the login succeeded, we probably need to look at the communication between the client -and the registry. +This error is ambiguous, as it's not clear whether the 403 is coming from the +GitLab Rails application, the Docker registry, or something else. In this +case, since we know that since the login succeeded, we probably need to look +at the communication between the client and the registry. The REST API between the Docker client and registry is [described here](https://docs.docker.com/registry/spec/api/). Normally, one would just -- cgit v1.2.1 From 51bcdfb7850a642c1062d5ab73417a6c6d2edb51 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 2 Aug 2016 18:01:14 -0500 Subject: Fix filter input alignment --- CHANGELOG | 1 + app/assets/stylesheets/framework/nav.scss | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 31e5d982cdf..21e02d61af9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ v 8.11.0 (unreleased) - Optimize checking if a user has read access to a list of issues !5370 - Nokogiri's various parsing methods are now instrumented - Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363 + - Fix filter input alignment (ClemMakesApps) - Include old revision in merge request update hooks (Ben Boeckel) - Add build event color in HipChat messages (David Eisner) - Make fork counter always clickable. !5463 (winniehell) diff --git a/app/assets/stylesheets/framework/nav.scss b/app/assets/stylesheets/framework/nav.scss index 364952d3b4a..7852fc9a424 100644 --- a/app/assets/stylesheets/framework/nav.scss +++ b/app/assets/stylesheets/framework/nav.scss @@ -182,7 +182,6 @@ > form { display: inline-block; - margin-top: -1px; } .icon-label { @@ -193,7 +192,6 @@ height: 35px; display: inline-block; position: relative; - top: 2px; margin-right: $gl-padding-top; /* Medium devices (desktops, 992px and up) */ -- cgit v1.2.1 From cd7c2cb6ddd4d9c9f9bdae00c887c0022c121c17 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Wed, 20 Jul 2016 18:25:36 +0200 Subject: Cache highlighted diff lines for merge requests Introducing the concept of SafeDiffs which relates diffs with UI highlighting. --- CHANGELOG | 1 + app/controllers/concerns/diff_for_path.rb | 6 +-- app/controllers/projects/commit_controller.rb | 4 +- app/controllers/projects/compare_controller.rb | 6 +-- .../projects/merge_requests_controller.rb | 10 ++-- app/helpers/commits_helper.rb | 4 +- app/helpers/diff_helper.rb | 11 ++--- app/models/merge_request.rb | 2 + app/models/safe_diffs.rb | 5 ++ app/models/safe_diffs/base.rb | 55 ++++++++++++++++++++++ app/models/safe_diffs/commit.rb | 10 ++++ app/models/safe_diffs/compare.rb | 10 ++++ app/models/safe_diffs/merge_request.rb | 52 ++++++++++++++++++++ .../merge_request_diff_cache_service.rb | 8 ++++ app/views/notify/repository_push_email.html.haml | 2 +- app/views/projects/commit/show.html.haml | 2 +- app/views/projects/compare/show.html.haml | 2 +- app/views/projects/diffs/_diffs.html.haml | 2 - app/views/projects/diffs/_file.html.haml | 4 +- app/views/projects/diffs/_text_file.html.haml | 2 +- .../projects/merge_requests/_new_submit.html.haml | 2 +- .../projects/merge_requests/show/_diffs.html.haml | 5 +- lib/gitlab/diff/file.rb | 5 +- lib/gitlab/diff/highlight.rb | 7 +-- lib/gitlab/diff/line.rb | 14 ++++++ lib/gitlab/email/message/repository_push.rb | 2 +- .../controllers/projects/commit_controller_spec.rb | 13 ++--- .../projects/compare_controller_spec.rb | 14 +++--- .../projects/merge_requests_controller_spec.rb | 18 +++---- 29 files changed, 221 insertions(+), 57 deletions(-) create mode 100644 app/models/safe_diffs.rb create mode 100644 app/models/safe_diffs/base.rb create mode 100644 app/models/safe_diffs/commit.rb create mode 100644 app/models/safe_diffs/compare.rb create mode 100644 app/models/safe_diffs/merge_request.rb create mode 100644 app/services/merge_requests/merge_request_diff_cache_service.rb diff --git a/CHANGELOG b/CHANGELOG index db2617dcbd7..581b8b09af9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.11.0 (unreleased) - The Repository class is now instrumented - Cache the commit author in RequestStore to avoid extra lookups in PostReceive - Expand commit message width in repo view (ClemMakesApps) + - Cache highlighted diff lines for merge requests - Fix of 'Commits being passed to custom hooks are already reachable when using the UI' - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable - Optimize maximum user access level lookup in loading of notes diff --git a/app/controllers/concerns/diff_for_path.rb b/app/controllers/concerns/diff_for_path.rb index 026d8b2e1e0..aeec3009f15 100644 --- a/app/controllers/concerns/diff_for_path.rb +++ b/app/controllers/concerns/diff_for_path.rb @@ -1,8 +1,8 @@ module DiffForPath extend ActiveSupport::Concern - def render_diff_for_path(diffs, diff_refs, project) - diff_file = safe_diff_files(diffs, diff_refs: diff_refs, repository: project.repository).find do |diff| + def render_diff_for_path(diffs) + diff_file = diffs.diff_files.find do |diff| diff.old_path == params[:old_path] && diff.new_path == params[:new_path] end @@ -14,7 +14,7 @@ module DiffForPath locals = { diff_file: diff_file, diff_commit: diff_commit, - diff_refs: diff_refs, + diff_refs: diffs.diff_refs, blob: blob, project: project } diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 7ae034f9398..6060b6e55bc 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -28,7 +28,7 @@ class Projects::CommitController < Projects::ApplicationController end def diff_for_path - render_diff_for_path(@diffs, @commit.diff_refs, @project) + render_diff_for_path(SafeDiffs::Commit.new(@commit, diff_options: diff_options)) end def builds @@ -110,7 +110,7 @@ class Projects::CommitController < Projects::ApplicationController opts = diff_options opts[:ignore_whitespace_change] = true if params[:format] == 'diff' - @diffs = commit.diffs(opts) + @diffs = SafeDiffs::Commit.new(commit, diff_options: opts) @notes_count = commit.notes.count end diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 8c004724f02..2eda950a1bd 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -21,7 +21,7 @@ class Projects::CompareController < Projects::ApplicationController def diff_for_path return render_404 unless @compare - render_diff_for_path(@diffs, @diff_refs, @project) + render_diff_for_path(SafeDiffs::Compare.new(@compare, project: @project, diff_options: diff_options)) end def create @@ -46,12 +46,12 @@ class Projects::CompareController < Projects::ApplicationController @commit = @project.commit(@head_ref) @base_commit = @project.merge_base_commit(@start_ref, @head_ref) - @diffs = @compare.diffs(diff_options) - @diff_refs = Gitlab::Diff::DiffRefs.new( + diff_refs = Gitlab::Diff::DiffRefs.new( base_sha: @base_commit.try(:sha), start_sha: @start_commit.try(:sha), head_sha: @commit.try(:sha) ) + @diffs = SafeDiffs::Compare.new(@compare, project: @project, diff_options: diff_options, diff_refs: diff_refs) @diff_notes_disabled = true @grouped_diff_discussions = {} diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 116e7904a4e..78a6a3c5715 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -103,9 +103,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController end define_commit_vars - diffs = @merge_request.diffs(diff_options) - render_diff_for_path(diffs, @merge_request.diff_refs, @merge_request.project) + render_diff_for_path(SafeDiffs::MergeRequest.new(merge_request, diff_options: diff_options)) end def commits @@ -153,7 +152,12 @@ class Projects::MergeRequestsController < Projects::ApplicationController @commits = @merge_request.compare_commits.reverse @commit = @merge_request.diff_head_commit @base_commit = @merge_request.diff_base_commit - @diffs = @merge_request.compare.diffs(diff_options) if @merge_request.compare + if @merge_request.compare + @diffs = SafeDiffs::Compare.new(@merge_request.compare, + project: @merge_request.project, + diff_refs: @merge_request.diff_refs, + diff_options: diff_options) + end @diff_notes_disabled = true @pipeline = @merge_request.pipeline diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index f497626e21a..7a02d0b10d9 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -206,10 +206,10 @@ module CommitsHelper end end - def view_file_btn(commit_sha, diff, project) + def view_file_btn(commit_sha, diff_new_path, project) link_to( namespace_project_blob_path(project.namespace, project, - tree_join(commit_sha, diff.new_path)), + tree_join(commit_sha, diff_new_path)), class: 'btn view-file js-view-file btn-file-option' ) do raw('View file @') + content_tag(:span, commit_sha[0..6], diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index f35e2f6ddcd..6497282af57 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -23,18 +23,17 @@ module DiffHelper end def diff_options - options = { ignore_whitespace_change: hide_whitespace?, no_collapse: expand_all_diffs? } + options = SafeDiffs.default_options.merge( + ignore_whitespace_change: hide_whitespace?, + no_collapse: expand_all_diffs? + ) if action_name == 'diff_for_path' options[:no_collapse] = true options[:paths] = params.values_at(:old_path, :new_path) end - Commit.max_diff_options.merge(options) - end - - def safe_diff_files(diffs, diff_refs: nil, repository: nil) - diffs.decorate! { |diff| Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) } + options end def unfold_bottom_class(bottom) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a99c4ba52a4..774851cc90f 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -313,6 +313,8 @@ class MergeRequest < ActiveRecord::Base merge_request_diff.reload_content + MergeRequests::MergeRequestDiffCacheService.new.execute(self) + new_diff_refs = self.diff_refs update_diff_notes_positions( diff --git a/app/models/safe_diffs.rb b/app/models/safe_diffs.rb new file mode 100644 index 00000000000..8ca9ec4cc39 --- /dev/null +++ b/app/models/safe_diffs.rb @@ -0,0 +1,5 @@ +module SafeDiffs + def self.default_options + ::Commit.max_diff_options.merge(ignore_whitespace_change: false, no_collapse: false) + end +end diff --git a/app/models/safe_diffs/base.rb b/app/models/safe_diffs/base.rb new file mode 100644 index 00000000000..dfc4708e293 --- /dev/null +++ b/app/models/safe_diffs/base.rb @@ -0,0 +1,55 @@ +module SafeDiffs + class Base + attr_reader :project, :diff_options, :diff_view, :diff_refs + + delegate :count, :real_size, to: :diff_files + + def initialize(diffs, project:, diff_options:, diff_refs: nil) + @diffs = diffs + @project = project + @diff_options = diff_options + @diff_refs = diff_refs + end + + def diff_files + @diff_files ||= begin + diffs = @diffs.decorate! do |diff| + Gitlab::Diff::File.new(diff, diff_refs: @diff_refs, repository: @project.repository) + end + + highlight!(diffs) + diffs + end + end + + private + + def highlight!(diff_files) + if cacheable? + cache_highlight!(diff_files) + else + diff_files.each { |diff_file| highlight_diff_file!(diff_file) } + end + end + + def cacheable? + false + end + + def cache_highlight! + raise NotImplementedError + end + + def highlight_diff_file_from_cache!(diff_file, cache_diff_lines) + diff_file.diff_lines = cache_diff_lines.map do |line| + Gitlab::Diff::Line.init_from_hash(line) + end + end + + def highlight_diff_file!(diff_file) + diff_file.diff_lines = Gitlab::Diff::Highlight.new(diff_file, repository: diff_file.repository).highlight + diff_file.highlighted_diff_lines = diff_file.diff_lines # To be used on parallel diff + diff_file + end + end +end diff --git a/app/models/safe_diffs/commit.rb b/app/models/safe_diffs/commit.rb new file mode 100644 index 00000000000..338878f32e0 --- /dev/null +++ b/app/models/safe_diffs/commit.rb @@ -0,0 +1,10 @@ +module SafeDiffs + class Commit < Base + def initialize(commit, diff_options:) + super(commit.diffs(diff_options), + project: commit.project, + diff_options: diff_options, + diff_refs: commit.diff_refs) + end + end +end diff --git a/app/models/safe_diffs/compare.rb b/app/models/safe_diffs/compare.rb new file mode 100644 index 00000000000..6b64b81137d --- /dev/null +++ b/app/models/safe_diffs/compare.rb @@ -0,0 +1,10 @@ +module SafeDiffs + class Compare < Base + def initialize(compare, project:, diff_options:, diff_refs: nil) + super(compare.diffs(diff_options), + project: project, + diff_options: diff_options, + diff_refs: diff_refs) + end + end +end diff --git a/app/models/safe_diffs/merge_request.rb b/app/models/safe_diffs/merge_request.rb new file mode 100644 index 00000000000..111b9a54f91 --- /dev/null +++ b/app/models/safe_diffs/merge_request.rb @@ -0,0 +1,52 @@ +module SafeDiffs + class MergeRequest < Base + def initialize(merge_request, diff_options:) + @merge_request = merge_request + + super(merge_request.diffs(diff_options), + project: merge_request.project, + diff_options: diff_options, + diff_refs: merge_request.diff_refs) + end + + private + + # + # If we find the highlighted diff files lines on the cache we replace existing diff_files lines (no highlighted) + # for the highlighted ones, so we just skip their execution. + # If the highlighted diff files lines are not cached we calculate and cache them. + # + # The content of the cache is and Hash where the key correspond to the file_path and the values are Arrays of + # hashes than represent serialized diff lines. + # + def cache_highlight!(diff_files) + highlighted_cache = Rails.cache.read(cache_key) || {} + highlighted_cache_was_empty = highlighted_cache.empty? + + diff_files.each do |diff_file| + file_path = diff_file.file_path + + if highlighted_cache[file_path] + highlight_diff_file_from_cache!(diff_file, highlighted_cache[file_path]) + else + highlight_diff_file!(diff_file) + highlighted_cache[file_path] = diff_file.diff_lines.map(&:to_hash) + end + end + + if highlighted_cache_was_empty + Rails.cache.write(cache_key, highlighted_cache) + end + + diff_files + end + + def cacheable? + @merge_request.merge_request_diff.present? + end + + def cache_key + [@merge_request.merge_request_diff, 'highlighted-safe-diff-files', diff_options] + end + end +end diff --git a/app/services/merge_requests/merge_request_diff_cache_service.rb b/app/services/merge_requests/merge_request_diff_cache_service.rb new file mode 100644 index 00000000000..0a1905f7137 --- /dev/null +++ b/app/services/merge_requests/merge_request_diff_cache_service.rb @@ -0,0 +1,8 @@ +module MergeRequests + class MergeRequestDiffCacheService + def execute(merge_request) + # Executing the iteration we cache all the highlighted diff information + SafeDiffs::MergeRequest.new(merge_request, diff_options: SafeDiffs.default_options).diff_files.to_a + end + end +end diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index c161ecc3463..2d1a98caeaa 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -75,7 +75,7 @@ - blob = diff_file.blob - if blob && blob.respond_to?(:text?) && blob_text_viewable?(blob) %table.code.white - - diff_file.highlighted_diff_lines.each do |line| + - diff_file.diff_lines.each do |line| = render "projects/diffs/line", line: line, diff_file: diff_file, plain: true - else No preview for this file type diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index d0da2606587..11b2020f99b 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -7,7 +7,7 @@ = render "ci_menu" - else %div.block-connector -= render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @commit.diff_refs += render "projects/diffs/diffs", diff_files: @diffs.diff_files, project: @diffs.project, diff_refs: @diffs.diff_refs = render "projects/notes/notes_with_form" - if can_collaborate_with_project? - %w(revert cherry-pick).each do |type| diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 28a50e7031a..eb8a1bd5289 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -8,7 +8,7 @@ - if @commits.present? = render "projects/commits/commit_list" - = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @diff_refs + = render "projects/diffs/diffs", diff_files: @diffs.diff_files, project: @diffs.project, diff_refs: @diffs.diff_refs - else .light-well .center diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 4bf3ccace20..45895a9a3de 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -2,8 +2,6 @@ - if diff_view == 'parallel' - fluid_layout true -- diff_files = safe_diff_files(diffs, diff_refs: diff_refs, repository: project.repository) - .content-block.oneline-block.files-changed .inline-parallel-buttons - if !expand_all_diffs? && diff_files.any? { |diff_file| diff_file.collapsed? } diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index 1854c64cbd7..f914e13a1ec 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -15,6 +15,6 @@ from_merge_request_id: @merge_request.id, skip_visible_check: true) - = view_file_btn(diff_commit.id, diff_file, project) + = view_file_btn(diff_commit.id, diff_file.new_path, project) - = render 'projects/diffs/content', diff_file: diff_file, diff_commit: diff_commit, diff_refs: diff_refs, blob: blob, project: project + = render 'projects/diffs/content', diff_file: diff_file, diff_commit: diff_commit, blob: blob, project: project diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml index 5970b9abf2b..a483927671e 100644 --- a/app/views/projects/diffs/_text_file.html.haml +++ b/app/views/projects/diffs/_text_file.html.haml @@ -5,7 +5,7 @@ %table.text-file.code.js-syntax-highlight{ data: diff_view_data, class: too_big ? 'hide' : '' } - last_line = 0 - - diff_file.highlighted_diff_lines.each do |line| + - diff_file.diff_lines.each do |line| - last_line = line.new_pos = render "projects/diffs/line", line: line, diff_file: diff_file diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index a5e67b95727..cb2b623691c 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -42,7 +42,7 @@ %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. %p To preserve performance the line changes are not shown. - else - = render "projects/diffs/diffs", diffs: @diffs, project: @project, diff_refs: @merge_request.diff_refs, show_whitespace_toggle: false + = render "projects/diffs/diffs", diff_files: @diffs.diff_files, project: @diffs.project, diff_refs: @merge_request.diff_refs, show_whitespace_toggle: false - if @pipeline #builds.builds.tab-pane = render "projects/merge_requests/show/builds" diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index 1b0bae86ad4..ed2765356db 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -1,6 +1,7 @@ - if @merge_request_diff.collected? - = render "projects/diffs/diffs", diffs: @merge_request.diffs(diff_options), - project: @merge_request.project, diff_refs: @merge_request.diff_refs + - diffs = SafeDiffs::MergeRequest.new(@merge_request, diff_options: diff_options) + = render "projects/diffs/diffs", diff_files: diffs.diff_files, + diff_refs: diffs.diff_refs, project: diffs.project - elsif @merge_request_diff.empty? .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} - else diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index b09ca1fb8b0..77b3798d78f 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -63,15 +63,18 @@ module Gitlab diff_refs.try(:head_sha) end + attr_writer :diff_lines, :highlighted_diff_lines + # Array of Gitlab::Diff::Line objects def diff_lines - @lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a + @diff_lines ||= Gitlab::Diff::Parser.new.parse(raw_diff.each_line).to_a end def highlighted_diff_lines @highlighted_diff_lines ||= Gitlab::Diff::Highlight.new(self, repository: self.repository).highlight end + # Array[] with right/left keys that contains Gitlab::Diff::Line objects which text is hightlighted def parallel_diff_lines @parallel_diff_lines ||= Gitlab::Diff::ParallelDiff.new(self).parallelize end diff --git a/lib/gitlab/diff/highlight.rb b/lib/gitlab/diff/highlight.rb index 649a265a02c..9ea976e18fa 100644 --- a/lib/gitlab/diff/highlight.rb +++ b/lib/gitlab/diff/highlight.rb @@ -40,8 +40,6 @@ module Gitlab def highlight_line(diff_line) return unless diff_file && diff_file.diff_refs - line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' ' - rich_line = if diff_line.unchanged? || diff_line.added? new_lines[diff_line.new_pos - 1] @@ -51,7 +49,10 @@ module Gitlab # Only update text if line is found. This will prevent # issues with submodules given the line only exists in diff content. - "#{line_prefix}#{rich_line}".html_safe if rich_line + if rich_line + line_prefix = diff_line.text.match(/\A(.)/) ? $1 : ' ' + "#{line_prefix}#{rich_line}".html_safe + end end def inline_diffs diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb index c6189d660c2..cf097e0d0de 100644 --- a/lib/gitlab/diff/line.rb +++ b/lib/gitlab/diff/line.rb @@ -9,6 +9,20 @@ module Gitlab @old_pos, @new_pos = old_pos, new_pos end + def self.init_from_hash(hash) + new(hash[:text], hash[:type], hash[:index], hash[:old_pos], hash[:new_pos]) + end + + def serialize_keys + @serialize_keys ||= %i(text type index old_pos new_pos) + end + + def to_hash + hash = {} + serialize_keys.each { |key| hash[key] = send(key) } + hash + end + def old_line old_pos unless added? || meta? end diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb index 97701b0cd42..48946ba355b 100644 --- a/lib/gitlab/email/message/repository_push.rb +++ b/lib/gitlab/email/message/repository_push.rb @@ -41,7 +41,7 @@ module Gitlab def diffs return unless compare - @diffs ||= safe_diff_files(compare.diffs(max_files: 30), diff_refs: diff_refs, repository: project.repository) + @diffs ||= SafeDiffs::Compare.new(compare, diff_options: { max_files: 30 }, project: project, diff_refs: diff_refs).diff_files end def diffs_count diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index df902da86f8..30121facd7d 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -83,7 +83,7 @@ describe Projects::CommitController do let(:format) { :diff } it "should really only be a git diff" do - go(id: commit.id, format: format) + go(id: '66eceea0db202bb39c4e445e8ca28689645366c5', format: format) expect(response.body).to start_with("diff --git") end @@ -92,8 +92,9 @@ describe Projects::CommitController do go(id: '66eceea0db202bb39c4e445e8ca28689645366c5', format: format, w: 1) expect(response.body).to start_with("diff --git") - # without whitespace option, there are more than 2 diff_splits - diff_splits = assigns(:diffs).first.diff.split("\n") + + # without whitespace option, there are more than 2 diff_splits for other formats + diff_splits = assigns(:diffs).diff_files.first.diff.diff.split("\n") expect(diff_splits.length).to be <= 2 end end @@ -266,9 +267,9 @@ describe Projects::CommitController do end it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project| - expect(diffs.map(&:new_path)).to contain_exactly(existing_path) - meth.call(diffs, diff_refs, project) + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, safe_diffs| + expect(safe_diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(safe_diffs) end diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path) diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index 4058d5e2453..6272a5f111d 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -19,7 +19,7 @@ describe Projects::CompareController do to: ref_to) expect(response).to be_success - expect(assigns(:diffs).first).not_to be_nil + expect(assigns(:diffs).diff_files.first).not_to be_nil expect(assigns(:commits).length).to be >= 1 end @@ -32,10 +32,10 @@ describe Projects::CompareController do w: 1) expect(response).to be_success - expect(assigns(:diffs).first).not_to be_nil + expect(assigns(:diffs).diff_files.first).not_to be_nil expect(assigns(:commits).length).to be >= 1 # without whitespace option, there are more than 2 diff_splits - diff_splits = assigns(:diffs).first.diff.split("\n") + diff_splits = assigns(:diffs).diff_files.first.diff.diff.split("\n") expect(diff_splits.length).to be <= 2 end @@ -48,7 +48,7 @@ describe Projects::CompareController do to: ref_to) expect(response).to be_success - expect(assigns(:diffs).to_a).to eq([]) + expect(assigns(:diffs).diff_files.to_a).to eq([]) expect(assigns(:commits)).to eq([]) end @@ -87,9 +87,9 @@ describe Projects::CompareController do end it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project| - expect(diffs.map(&:new_path)).to contain_exactly(existing_path) - meth.call(diffs, diff_refs, project) + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, safe_diffs| + expect(safe_diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(safe_diffs) end diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path) diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 210085e3b1a..9da43c9cee7 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -392,9 +392,9 @@ describe Projects::MergeRequestsController do end it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project| - expect(diffs.map(&:new_path)).to contain_exactly(existing_path) - meth.call(diffs, diff_refs, project) + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, safe_diffs| + expect(safe_diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(safe_diffs) end diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path) @@ -455,9 +455,9 @@ describe Projects::MergeRequestsController do end it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project| - expect(diffs.map(&:new_path)).to contain_exactly(existing_path) - meth.call(diffs, diff_refs, project) + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, safe_diffs| + expect(safe_diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(safe_diffs) end diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' }) @@ -477,9 +477,9 @@ describe Projects::MergeRequestsController do end it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs, diff_refs, project| - expect(diffs.map(&:new_path)).to contain_exactly(existing_path) - meth.call(diffs, diff_refs, project) + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, safe_diffs| + expect(safe_diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(safe_diffs) end diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) -- cgit v1.2.1 From 8f359ea9170b984ad43d126e17628c31ac3a1f14 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Tue, 26 Jul 2016 09:21:42 +0200 Subject: Move to Gitlab::Diff::FileCollection Instead calling diff_collection.count use diff_collection.size which is cache on the diff_collection --- app/controllers/projects/commit_controller.rb | 4 +- app/controllers/projects/compare_controller.rb | 4 +- .../projects/merge_requests_controller.rb | 16 ++-- app/helpers/diff_helper.rb | 2 +- app/models/commit.rb | 4 + app/models/compare.rb | 23 ++++++ app/models/merge_request.rb | 4 + app/models/safe_diffs.rb | 5 -- app/models/safe_diffs/base.rb | 55 -------------- app/models/safe_diffs/commit.rb | 10 --- app/models/safe_diffs/compare.rb | 10 --- app/models/safe_diffs/merge_request.rb | 52 ------------- .../merge_request_diff_cache_service.rb | 2 +- app/views/notify/repository_push_email.html.haml | 2 +- app/views/projects/commit/_ci_menu.html.haml | 2 +- app/views/projects/diffs/_text_file.html.haml | 2 +- .../projects/merge_requests/show/_diffs.html.haml | 5 +- app/workers/irker_worker.rb | 2 +- lib/gitlab/diff/file.rb | 2 +- lib/gitlab/diff/file_collection.rb | 9 +++ lib/gitlab/diff/file_collection/base.rb | 30 ++++++++ lib/gitlab/diff/file_collection/commit.rb | 14 ++++ lib/gitlab/diff/file_collection/compare.rb | 14 ++++ lib/gitlab/diff/file_collection/merge_request.rb | 88 ++++++++++++++++++++++ lib/gitlab/email/message/repository_push.rb | 10 ++- .../controllers/projects/commit_controller_spec.rb | 6 +- .../projects/compare_controller_spec.rb | 11 +-- .../projects/merge_requests_controller_spec.rb | 18 ++--- .../gitlab/email/message/repository_push_spec.rb | 4 +- spec/models/merge_request_spec.rb | 6 ++ .../merge_request_diff_cache_service_spec.rb | 18 +++++ 31 files changed, 259 insertions(+), 175 deletions(-) create mode 100644 app/models/compare.rb delete mode 100644 app/models/safe_diffs.rb delete mode 100644 app/models/safe_diffs/base.rb delete mode 100644 app/models/safe_diffs/commit.rb delete mode 100644 app/models/safe_diffs/compare.rb delete mode 100644 app/models/safe_diffs/merge_request.rb create mode 100644 lib/gitlab/diff/file_collection.rb create mode 100644 lib/gitlab/diff/file_collection/base.rb create mode 100644 lib/gitlab/diff/file_collection/commit.rb create mode 100644 lib/gitlab/diff/file_collection/compare.rb create mode 100644 lib/gitlab/diff/file_collection/merge_request.rb create mode 100644 spec/services/merge_requests/merge_request_diff_cache_service_spec.rb diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 6060b6e55bc..771a86530cd 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -28,7 +28,7 @@ class Projects::CommitController < Projects::ApplicationController end def diff_for_path - render_diff_for_path(SafeDiffs::Commit.new(@commit, diff_options: diff_options)) + render_diff_for_path(@commit.diff_file_collection(diff_options)) end def builds @@ -110,7 +110,7 @@ class Projects::CommitController < Projects::ApplicationController opts = diff_options opts[:ignore_whitespace_change] = true if params[:format] == 'diff' - @diffs = SafeDiffs::Commit.new(commit, diff_options: opts) + @diffs = commit.diff_file_collection(opts) @notes_count = commit.notes.count end diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 2eda950a1bd..252ddfa429a 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -21,7 +21,7 @@ class Projects::CompareController < Projects::ApplicationController def diff_for_path return render_404 unless @compare - render_diff_for_path(SafeDiffs::Compare.new(@compare, project: @project, diff_options: diff_options)) + render_diff_for_path(Compare.decorate(@compare, @project).diff_file_collection(diff_options: diff_options)) end def create @@ -51,7 +51,7 @@ class Projects::CompareController < Projects::ApplicationController start_sha: @start_commit.try(:sha), head_sha: @commit.try(:sha) ) - @diffs = SafeDiffs::Compare.new(@compare, project: @project, diff_options: diff_options, diff_refs: diff_refs) + @diffs = Compare.decorate(@compare, @project).diff_file_collection(diff_options: diff_options, diff_refs: diff_refs) @diff_notes_disabled = true @grouped_diff_discussions = {} diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 78a6a3c5715..39e7d0f6182 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -85,7 +85,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController respond_to do |format| format.html { define_discussion_vars } - format.json { render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } } + format.json do + @diffs = @merge_request.diff_file_collection(diff_options) if @merge_request_diff.collected? + + render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } + end end end @@ -104,7 +108,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController define_commit_vars - render_diff_for_path(SafeDiffs::MergeRequest.new(merge_request, diff_options: diff_options)) + render_diff_for_path(@merge_request.diff_file_collection(diff_options)) end def commits @@ -153,10 +157,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController @commit = @merge_request.diff_head_commit @base_commit = @merge_request.diff_base_commit if @merge_request.compare - @diffs = SafeDiffs::Compare.new(@merge_request.compare, - project: @merge_request.project, - diff_refs: @merge_request.diff_refs, - diff_options: diff_options) + @diffs = Compare.decorate(@merge_request.compare, @project).diff_file_collection( + diff_options: diff_options, + diff_refs: @merge_request.diff_refs + ) end @diff_notes_disabled = true diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 6497282af57..2abe24b78bf 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -23,7 +23,7 @@ module DiffHelper end def diff_options - options = SafeDiffs.default_options.merge( + options = Gitlab::Diff::FileCollection.default_options.merge( ignore_whitespace_change: hide_whitespace?, no_collapse: expand_all_diffs? ) diff --git a/app/models/commit.rb b/app/models/commit.rb index c52b4a051c2..d22ecb222e5 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -317,6 +317,10 @@ class Commit nil end + def diff_file_collection(diff_options) + Gitlab::Diff::FileCollection::Commit.new(self, diff_options: diff_options) + end + private def find_author_by_any_email diff --git a/app/models/compare.rb b/app/models/compare.rb new file mode 100644 index 00000000000..6672d1bf059 --- /dev/null +++ b/app/models/compare.rb @@ -0,0 +1,23 @@ +class Compare + delegate :commits, :same, :head, :base, to: :@compare + + def self.decorate(compare, project) + if compare.is_a?(Compare) + compare + else + self.new(compare, project) + end + end + + def initialize(compare, project) + @compare = compare + @project = project + end + + def diff_file_collection(diff_options:, diff_refs: nil) + Gitlab::Diff::FileCollection::Compare.new(@compare, + project: @project, + diff_options: diff_options, + diff_refs: diff_refs) + end +end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 774851cc90f..abc8bacbe59 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -168,6 +168,10 @@ class MergeRequest < ActiveRecord::Base merge_request_diff ? merge_request_diff.diffs(*args) : compare.diffs(*args) end + def diff_file_collection(diff_options) + Gitlab::Diff::FileCollection::MergeRequest.new(self, diff_options: diff_options) + end + def diff_size merge_request_diff.size end diff --git a/app/models/safe_diffs.rb b/app/models/safe_diffs.rb deleted file mode 100644 index 8ca9ec4cc39..00000000000 --- a/app/models/safe_diffs.rb +++ /dev/null @@ -1,5 +0,0 @@ -module SafeDiffs - def self.default_options - ::Commit.max_diff_options.merge(ignore_whitespace_change: false, no_collapse: false) - end -end diff --git a/app/models/safe_diffs/base.rb b/app/models/safe_diffs/base.rb deleted file mode 100644 index dfc4708e293..00000000000 --- a/app/models/safe_diffs/base.rb +++ /dev/null @@ -1,55 +0,0 @@ -module SafeDiffs - class Base - attr_reader :project, :diff_options, :diff_view, :diff_refs - - delegate :count, :real_size, to: :diff_files - - def initialize(diffs, project:, diff_options:, diff_refs: nil) - @diffs = diffs - @project = project - @diff_options = diff_options - @diff_refs = diff_refs - end - - def diff_files - @diff_files ||= begin - diffs = @diffs.decorate! do |diff| - Gitlab::Diff::File.new(diff, diff_refs: @diff_refs, repository: @project.repository) - end - - highlight!(diffs) - diffs - end - end - - private - - def highlight!(diff_files) - if cacheable? - cache_highlight!(diff_files) - else - diff_files.each { |diff_file| highlight_diff_file!(diff_file) } - end - end - - def cacheable? - false - end - - def cache_highlight! - raise NotImplementedError - end - - def highlight_diff_file_from_cache!(diff_file, cache_diff_lines) - diff_file.diff_lines = cache_diff_lines.map do |line| - Gitlab::Diff::Line.init_from_hash(line) - end - end - - def highlight_diff_file!(diff_file) - diff_file.diff_lines = Gitlab::Diff::Highlight.new(diff_file, repository: diff_file.repository).highlight - diff_file.highlighted_diff_lines = diff_file.diff_lines # To be used on parallel diff - diff_file - end - end -end diff --git a/app/models/safe_diffs/commit.rb b/app/models/safe_diffs/commit.rb deleted file mode 100644 index 338878f32e0..00000000000 --- a/app/models/safe_diffs/commit.rb +++ /dev/null @@ -1,10 +0,0 @@ -module SafeDiffs - class Commit < Base - def initialize(commit, diff_options:) - super(commit.diffs(diff_options), - project: commit.project, - diff_options: diff_options, - diff_refs: commit.diff_refs) - end - end -end diff --git a/app/models/safe_diffs/compare.rb b/app/models/safe_diffs/compare.rb deleted file mode 100644 index 6b64b81137d..00000000000 --- a/app/models/safe_diffs/compare.rb +++ /dev/null @@ -1,10 +0,0 @@ -module SafeDiffs - class Compare < Base - def initialize(compare, project:, diff_options:, diff_refs: nil) - super(compare.diffs(diff_options), - project: project, - diff_options: diff_options, - diff_refs: diff_refs) - end - end -end diff --git a/app/models/safe_diffs/merge_request.rb b/app/models/safe_diffs/merge_request.rb deleted file mode 100644 index 111b9a54f91..00000000000 --- a/app/models/safe_diffs/merge_request.rb +++ /dev/null @@ -1,52 +0,0 @@ -module SafeDiffs - class MergeRequest < Base - def initialize(merge_request, diff_options:) - @merge_request = merge_request - - super(merge_request.diffs(diff_options), - project: merge_request.project, - diff_options: diff_options, - diff_refs: merge_request.diff_refs) - end - - private - - # - # If we find the highlighted diff files lines on the cache we replace existing diff_files lines (no highlighted) - # for the highlighted ones, so we just skip their execution. - # If the highlighted diff files lines are not cached we calculate and cache them. - # - # The content of the cache is and Hash where the key correspond to the file_path and the values are Arrays of - # hashes than represent serialized diff lines. - # - def cache_highlight!(diff_files) - highlighted_cache = Rails.cache.read(cache_key) || {} - highlighted_cache_was_empty = highlighted_cache.empty? - - diff_files.each do |diff_file| - file_path = diff_file.file_path - - if highlighted_cache[file_path] - highlight_diff_file_from_cache!(diff_file, highlighted_cache[file_path]) - else - highlight_diff_file!(diff_file) - highlighted_cache[file_path] = diff_file.diff_lines.map(&:to_hash) - end - end - - if highlighted_cache_was_empty - Rails.cache.write(cache_key, highlighted_cache) - end - - diff_files - end - - def cacheable? - @merge_request.merge_request_diff.present? - end - - def cache_key - [@merge_request.merge_request_diff, 'highlighted-safe-diff-files', diff_options] - end - end -end diff --git a/app/services/merge_requests/merge_request_diff_cache_service.rb b/app/services/merge_requests/merge_request_diff_cache_service.rb index 0a1905f7137..982540ba7f5 100644 --- a/app/services/merge_requests/merge_request_diff_cache_service.rb +++ b/app/services/merge_requests/merge_request_diff_cache_service.rb @@ -2,7 +2,7 @@ module MergeRequests class MergeRequestDiffCacheService def execute(merge_request) # Executing the iteration we cache all the highlighted diff information - SafeDiffs::MergeRequest.new(merge_request, diff_options: SafeDiffs.default_options).diff_files.to_a + merge_request.diff_file_collection(Gitlab::Diff::FileCollection.default_options).diff_files.to_a end end end diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index 2d1a98caeaa..c161ecc3463 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -75,7 +75,7 @@ - blob = diff_file.blob - if blob && blob.respond_to?(:text?) && blob_text_viewable?(blob) %table.code.white - - diff_file.diff_lines.each do |line| + - diff_file.highlighted_diff_lines.each do |line| = render "projects/diffs/line", line: line, diff_file: diff_file, plain: true - else No preview for this file type diff --git a/app/views/projects/commit/_ci_menu.html.haml b/app/views/projects/commit/_ci_menu.html.haml index ea33aa472a6..935433306ea 100644 --- a/app/views/projects/commit/_ci_menu.html.haml +++ b/app/views/projects/commit/_ci_menu.html.haml @@ -2,7 +2,7 @@ = nav_link(path: 'commit#show') do = link_to namespace_project_commit_path(@project.namespace, @project, @commit.id) do Changes - %span.badge= @diffs.count + %span.badge= @diffs.size = nav_link(path: 'commit#builds') do = link_to builds_namespace_project_commit_path(@project.namespace, @project, @commit.id) do Builds diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml index a483927671e..5970b9abf2b 100644 --- a/app/views/projects/diffs/_text_file.html.haml +++ b/app/views/projects/diffs/_text_file.html.haml @@ -5,7 +5,7 @@ %table.text-file.code.js-syntax-highlight{ data: diff_view_data, class: too_big ? 'hide' : '' } - last_line = 0 - - diff_file.diff_lines.each do |line| + - diff_file.highlighted_diff_lines.each do |line| - last_line = line.new_pos = render "projects/diffs/line", line: line, diff_file: diff_file diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index ed2765356db..5b842dd9280 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -1,7 +1,6 @@ - if @merge_request_diff.collected? - - diffs = SafeDiffs::MergeRequest.new(@merge_request, diff_options: diff_options) - = render "projects/diffs/diffs", diff_files: diffs.diff_files, - diff_refs: diffs.diff_refs, project: diffs.project + = render "projects/diffs/diffs", diff_files: @diffs.diff_files, + diff_refs: @diffs.diff_refs, project: @diffs.project - elsif @merge_request_diff.empty? .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} - else diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb index 605ec4f04e5..a3c34e02baa 100644 --- a/app/workers/irker_worker.rb +++ b/app/workers/irker_worker.rb @@ -142,7 +142,7 @@ class IrkerWorker def files_count(commit) files = "#{commit.diffs.real_size} file" - files += 's' if commit.diffs.count > 1 + files += 's' if commit.diffs.size > 1 files end diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index 77b3798d78f..e47df508ca2 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -63,7 +63,7 @@ module Gitlab diff_refs.try(:head_sha) end - attr_writer :diff_lines, :highlighted_diff_lines + attr_writer :highlighted_diff_lines # Array of Gitlab::Diff::Line objects def diff_lines diff --git a/lib/gitlab/diff/file_collection.rb b/lib/gitlab/diff/file_collection.rb new file mode 100644 index 00000000000..ce6717c7205 --- /dev/null +++ b/lib/gitlab/diff/file_collection.rb @@ -0,0 +1,9 @@ +module Gitlab + module Diff + module FileCollection + def self.default_options + ::Commit.max_diff_options.merge(ignore_whitespace_change: false, no_collapse: false) + end + end + end +end diff --git a/lib/gitlab/diff/file_collection/base.rb b/lib/gitlab/diff/file_collection/base.rb new file mode 100644 index 00000000000..20562773c14 --- /dev/null +++ b/lib/gitlab/diff/file_collection/base.rb @@ -0,0 +1,30 @@ +module Gitlab + module Diff + module FileCollection + + class Base + attr_reader :project, :diff_options, :diff_view, :diff_refs + + delegate :count, :size, :real_size, to: :diff_files + + def initialize(diffs, project:, diff_options:, diff_refs: nil) + @diffs = diffs + @project = project + @diff_options = diff_options + @diff_refs = diff_refs + end + + def diff_files + @diffs.decorate! { |diff| decorate_diff!(diff) } + end + + private + + def decorate_diff!(diff) + return diff if diff.is_a?(Gitlab::Diff::File) + Gitlab::Diff::File.new(diff, diff_refs: @diff_refs, repository: @project.repository) + end + end + end + end +end diff --git a/lib/gitlab/diff/file_collection/commit.rb b/lib/gitlab/diff/file_collection/commit.rb new file mode 100644 index 00000000000..2a46109ad99 --- /dev/null +++ b/lib/gitlab/diff/file_collection/commit.rb @@ -0,0 +1,14 @@ +module Gitlab + module Diff + module FileCollection + class Commit < Base + def initialize(commit, diff_options:) + super(commit.diffs(diff_options), + project: commit.project, + diff_options: diff_options, + diff_refs: commit.diff_refs) + end + end + end + end +end diff --git a/lib/gitlab/diff/file_collection/compare.rb b/lib/gitlab/diff/file_collection/compare.rb new file mode 100644 index 00000000000..1bcda145f15 --- /dev/null +++ b/lib/gitlab/diff/file_collection/compare.rb @@ -0,0 +1,14 @@ +module Gitlab + module Diff + module FileCollection + class Compare < Base + def initialize(compare, project:, diff_options:, diff_refs: nil) + super(compare.diffs(diff_options), + project: project, + diff_options: diff_options, + diff_refs: diff_refs) + end + end + end + end +end diff --git a/lib/gitlab/diff/file_collection/merge_request.rb b/lib/gitlab/diff/file_collection/merge_request.rb new file mode 100644 index 00000000000..7c40622d594 --- /dev/null +++ b/lib/gitlab/diff/file_collection/merge_request.rb @@ -0,0 +1,88 @@ +module Gitlab + module Diff + module FileCollection + class MergeRequest < Base + def initialize(merge_request, diff_options:) + @merge_request = merge_request + + super(merge_request.diffs(diff_options), + project: merge_request.project, + diff_options: diff_options, + diff_refs: merge_request.diff_refs) + end + + def diff_files + super.tap { |_| store_highlight_cache } + end + + private + + # Extracted method to highlight in the same iteration to the diff_collection. Iteration in the DiffCollections + # seems particularly slow on big diffs (event when already populated). + def decorate_diff!(diff) + highlight! super + end + + def highlight!(diff_file) + if cacheable? + cache_highlight!(diff_file) + else + highlight_diff_file!(diff_file) + end + end + + def highlight_diff_file!(diff_file) + diff_file.highlighted_diff_lines = Gitlab::Diff::Highlight.new(diff_file, repository: diff_file.repository).highlight + diff_file + end + + def highlight_diff_file_from_cache!(diff_file, cache_diff_lines) + diff_file.highlighted_diff_lines = cache_diff_lines.map do |line| + Gitlab::Diff::Line.init_from_hash(line) + end + end + + # + # If we find the highlighted diff files lines on the cache we replace existing diff_files lines (no highlighted) + # for the highlighted ones, so we just skip their execution. + # If the highlighted diff files lines are not cached we calculate and cache them. + # + # The content of the cache is a Hash where the key correspond to the file_path and the values are Arrays of + # hashes that represent serialized diff lines. + # + def cache_highlight!(diff_file) + file_path = diff_file.file_path + + if highlight_cache[file_path] + highlight_diff_file_from_cache!(diff_file, highlight_cache[file_path]) + else + highlight_diff_file!(diff_file) + highlight_cache[file_path] = diff_file.highlighted_diff_lines.map(&:to_hash) + end + + diff_file + end + + def highlight_cache + return @highlight_cache if defined?(@highlight_cache) + + @highlight_cache = Rails.cache.read(cache_key) || {} + @highlight_cache_was_empty = highlight_cache.empty? + @highlight_cache + end + + def store_highlight_cache + Rails.cache.write(cache_key, highlight_cache) if @highlight_cache_was_empty + end + + def cacheable? + @merge_request.merge_request_diff.present? + end + + def cache_key + [@merge_request.merge_request_diff, 'highlighted-diff-files', diff_options] + end + end + end + end +end diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb index 48946ba355b..71213813e17 100644 --- a/lib/gitlab/email/message/repository_push.rb +++ b/lib/gitlab/email/message/repository_push.rb @@ -40,16 +40,18 @@ module Gitlab def diffs return unless compare - - @diffs ||= SafeDiffs::Compare.new(compare, diff_options: { max_files: 30 }, project: project, diff_refs: diff_refs).diff_files + + @diffs ||= compare.diff_file_collection(diff_options: { max_files: 30 }, diff_refs: diff_refs).diff_files end def diffs_count - diffs.count if diffs + diffs.size if diffs end def compare - @opts[:compare] + if @opts[:compare] + Compare.decorate(@opts[:compare], project) + end end def diff_refs diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 30121facd7d..940019b708b 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -267,9 +267,9 @@ describe Projects::CommitController do end it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, safe_diffs| - expect(safe_diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) - meth.call(safe_diffs) + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| + expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs) end diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path) diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb index 6272a5f111d..ed4cc36de58 100644 --- a/spec/controllers/projects/compare_controller_spec.rb +++ b/spec/controllers/projects/compare_controller_spec.rb @@ -32,10 +32,11 @@ describe Projects::CompareController do w: 1) expect(response).to be_success - expect(assigns(:diffs).diff_files.first).not_to be_nil + diff_file = assigns(:diffs).diff_files.first + expect(diff_file).not_to be_nil expect(assigns(:commits).length).to be >= 1 # without whitespace option, there are more than 2 diff_splits - diff_splits = assigns(:diffs).diff_files.first.diff.diff.split("\n") + diff_splits = diff_file.diff.diff.split("\n") expect(diff_splits.length).to be <= 2 end @@ -87,9 +88,9 @@ describe Projects::CompareController do end it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, safe_diffs| - expect(safe_diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) - meth.call(safe_diffs) + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| + expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs) end diff_for_path(from: ref_from, to: ref_to, old_path: existing_path, new_path: existing_path) diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 9da43c9cee7..1f6bc84dfe8 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -392,9 +392,9 @@ describe Projects::MergeRequestsController do end it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, safe_diffs| - expect(safe_diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) - meth.call(safe_diffs) + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| + expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs) end diff_for_path(id: merge_request.iid, old_path: existing_path, new_path: existing_path) @@ -455,9 +455,9 @@ describe Projects::MergeRequestsController do end it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, safe_diffs| - expect(safe_diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) - meth.call(safe_diffs) + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| + expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs) end diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_branch: 'feature', target_branch: 'master' }) @@ -477,9 +477,9 @@ describe Projects::MergeRequestsController do end it 'only renders the diffs for the path given' do - expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, safe_diffs| - expect(safe_diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) - meth.call(safe_diffs) + expect(controller).to receive(:render_diff_for_path).and_wrap_original do |meth, diffs| + expect(diffs.diff_files.map(&:new_path)).to contain_exactly(existing_path) + meth.call(diffs) end diff_for_path(old_path: existing_path, new_path: existing_path, merge_request: { source_project: other_project, source_branch: 'feature', target_branch: 'master' }) diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb index c19f33e2224..c1d07329983 100644 --- a/spec/lib/gitlab/email/message/repository_push_spec.rb +++ b/spec/lib/gitlab/email/message/repository_push_spec.rb @@ -62,12 +62,12 @@ describe Gitlab::Email::Message::RepositoryPush do describe '#diffs_count' do subject { message.diffs_count } - it { is_expected.to eq compare.diffs.count } + it { is_expected.to eq compare.diffs.size } end describe '#compare' do subject { message.compare } - it { is_expected.to be_an_instance_of Gitlab::Git::Compare } + it { is_expected.to be_an_instance_of Compare } end describe '#compare_timeout' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 21d22c776e9..fa1f7edae8e 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -660,6 +660,12 @@ describe MergeRequest, models: true do subject.reload_diff end + it "executs diff cache service" do + expect_any_instance_of(MergeRequests::MergeRequestDiffCacheService).to receive(:execute).with(subject) + + subject.reload_diff + end + it "updates diff note positions" do old_diff_refs = subject.diff_refs diff --git a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb new file mode 100644 index 00000000000..c6cceed31ad --- /dev/null +++ b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe MergeRequests::MergeRequestDiffCacheService do + + let(:subject) { MergeRequests::MergeRequestDiffCacheService.new } + + describe '#execute' do + it 'retrieve the diff files to cache the highlighted result' do + merge_request = create(:merge_request) + cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection.default_options] + + expect(Rails.cache).to receive(:read).with(cache_key).and_return({}) + expect(Rails.cache).to receive(:write).with(cache_key, anything) + + subject.execute(merge_request) + end + end +end -- cgit v1.2.1 From 1d0c7b74920a94e488e6a2c090abb3e525438053 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Wed, 27 Jul 2016 13:09:52 +0200 Subject: Introduce Compare model in the codebase. This object will manage Gitlab::Git::Compare instances --- app/controllers/projects/compare_controller.rb | 18 +++------ .../projects/merge_requests_controller.rb | 7 +--- app/models/commit.rb | 2 +- app/models/compare.rb | 47 +++++++++++++++++++++- app/models/merge_request.rb | 8 +++- app/services/compare_service.rb | 7 +++- app/services/merge_requests/build_service.rb | 2 +- .../merge_request_diff_cache_service.rb | 2 +- app/views/projects/commit/show.html.haml | 2 +- app/views/projects/compare/show.html.haml | 2 +- app/views/projects/diffs/_diffs.html.haml | 7 ++-- .../projects/merge_requests/_new_submit.html.haml | 2 +- .../projects/merge_requests/show/_diffs.html.haml | 2 +- app/workers/emails_on_push_worker.rb | 20 ++++----- lib/gitlab/diff/file_collection/base.rb | 2 +- lib/gitlab/diff/file_collection/commit.rb | 3 ++ lib/gitlab/diff/file_collection/compare.rb | 3 ++ lib/gitlab/diff/file_collection/merge_request.rb | 10 ++--- lib/gitlab/email/message/repository_push.rb | 18 ++++----- .../gitlab/email/message/repository_push_spec.rb | 11 +++-- spec/mailers/notify_spec.rb | 10 +++-- spec/services/merge_requests/build_service_spec.rb | 8 ++-- 22 files changed, 118 insertions(+), 75 deletions(-) diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 252ddfa429a..7fca5e77f32 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -21,7 +21,7 @@ class Projects::CompareController < Projects::ApplicationController def diff_for_path return render_404 unless @compare - render_diff_for_path(Compare.decorate(@compare, @project).diff_file_collection(diff_options: diff_options)) + render_diff_for_path(@compare.diff_file_collection(diff_options: diff_options)) end def create @@ -40,18 +40,12 @@ class Projects::CompareController < Projects::ApplicationController @compare = CompareService.new.execute(@project, @head_ref, @project, @start_ref) if @compare - @commits = Commit.decorate(@compare.commits, @project) + @commits = @compare.commits + @start_commit = @compare.start_commit + @commit = @compare.commit + @base_commit = @compare.base_commit - @start_commit = @project.commit(@start_ref) - @commit = @project.commit(@head_ref) - @base_commit = @project.merge_base_commit(@start_ref, @head_ref) - - diff_refs = Gitlab::Diff::DiffRefs.new( - base_sha: @base_commit.try(:sha), - start_sha: @start_commit.try(:sha), - head_sha: @commit.try(:sha) - ) - @diffs = Compare.decorate(@compare, @project).diff_file_collection(diff_options: diff_options, diff_refs: diff_refs) + @diffs = @compare.diff_file_collection(diff_options: diff_options) @diff_notes_disabled = true @grouped_diff_discussions = {} diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 39e7d0f6182..20afc6afcb2 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -86,7 +86,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController respond_to do |format| format.html { define_discussion_vars } format.json do - @diffs = @merge_request.diff_file_collection(diff_options) if @merge_request_diff.collected? + @diffs = @merge_request.diff_file_collection(diff_options) render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } end @@ -157,10 +157,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @commit = @merge_request.diff_head_commit @base_commit = @merge_request.diff_base_commit if @merge_request.compare - @diffs = Compare.decorate(@merge_request.compare, @project).diff_file_collection( - diff_options: diff_options, - diff_refs: @merge_request.diff_refs - ) + @diffs = @merge_request.diff_file_collection(diff_options) end @diff_notes_disabled = true diff --git a/app/models/commit.rb b/app/models/commit.rb index d22ecb222e5..a339d47f5f3 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -317,7 +317,7 @@ class Commit nil end - def diff_file_collection(diff_options) + def diff_file_collection(diff_options = nil) Gitlab::Diff::FileCollection::Commit.new(self, diff_options: diff_options) end diff --git a/app/models/compare.rb b/app/models/compare.rb index 6672d1bf059..05c8fbbcc36 100644 --- a/app/models/compare.rb +++ b/app/models/compare.rb @@ -1,5 +1,5 @@ class Compare - delegate :commits, :same, :head, :base, to: :@compare + delegate :same, :head, :base, to: :@compare def self.decorate(compare, project) if compare.is_a?(Compare) @@ -14,10 +14,53 @@ class Compare @project = project end - def diff_file_collection(diff_options:, diff_refs: nil) + def commits + @commits ||= Commit.decorate(@compare.commits, @project) + end + + def start_commit + return @start_commit if defined?(@start_commit) + + commit = @compare.base + @start_commit = commit ? ::Commit.new(commit, @project) : nil + end + + def commit + return @commit if defined?(@commit) + + commit = @compare.head + @commit = commit ? ::Commit.new(commit, @project) : nil + end + alias_method :head_commit, :commit + + # Used only on emails_on_push_worker.rb + def base_commit=(commit) + @base_commit = commit + end + + def base_commit + return @base_commit if defined?(@base_commit) + + @base_commit = if start_commit && commit + @project.merge_base_commit(start_commit.id, commit.id) + else + nil + end + end + + # keyword args until we get ride of diff_refs as argument + def diff_file_collection(diff_options:, diff_refs: self.diff_refs) Gitlab::Diff::FileCollection::Compare.new(@compare, project: @project, diff_options: diff_options, diff_refs: diff_refs) end + + def diff_refs + @diff_refs ||= Gitlab::Diff::DiffRefs.new( + base_sha: base_commit.try(:sha), + start_sha: start_commit.try(:sha), + head_sha: commit.try(:sha) + ) + end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index abc8bacbe59..62e5573dfdc 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -168,8 +168,12 @@ class MergeRequest < ActiveRecord::Base merge_request_diff ? merge_request_diff.diffs(*args) : compare.diffs(*args) end - def diff_file_collection(diff_options) - Gitlab::Diff::FileCollection::MergeRequest.new(self, diff_options: diff_options) + def diff_file_collection(diff_options = nil) + if self.compare + self.compare.diff_file_collection(diff_options: diff_options, diff_refs: diff_refs) + else + Gitlab::Diff::FileCollection::MergeRequest.new(self, diff_options: diff_options) + end end def diff_size diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 149822aa647..bb3aff72b47 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -20,10 +20,13 @@ class CompareService ) end - Gitlab::Git::Compare.new( + raw_compare = Gitlab::Git::Compare.new( target_project.repository.raw_repository, target_branch, - source_sha, + source_sha ) + + # REVIEW be sure if it's target_project or source_project + Compare.new(raw_compare, target_project) end end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 7fe57747265..290742f1506 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -34,7 +34,7 @@ module MergeRequests # At this point we decide if merge request can be created # If we have at least one commit to merge -> creation allowed if commits.present? - merge_request.compare_commits = Commit.decorate(commits, merge_request.source_project) + merge_request.compare_commits = commits merge_request.can_be_created = true merge_request.compare = compare else diff --git a/app/services/merge_requests/merge_request_diff_cache_service.rb b/app/services/merge_requests/merge_request_diff_cache_service.rb index 982540ba7f5..8151c24d1b0 100644 --- a/app/services/merge_requests/merge_request_diff_cache_service.rb +++ b/app/services/merge_requests/merge_request_diff_cache_service.rb @@ -2,7 +2,7 @@ module MergeRequests class MergeRequestDiffCacheService def execute(merge_request) # Executing the iteration we cache all the highlighted diff information - merge_request.diff_file_collection(Gitlab::Diff::FileCollection.default_options).diff_files.to_a + merge_request.diff_file_collection.diff_files.to_a end end end diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 11b2020f99b..ed44d86a687 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -7,7 +7,7 @@ = render "ci_menu" - else %div.block-connector -= render "projects/diffs/diffs", diff_files: @diffs.diff_files, project: @diffs.project, diff_refs: @diffs.diff_refs += render "projects/diffs/diffs", diffs: @diffs = render "projects/notes/notes_with_form" - if can_collaborate_with_project? - %w(revert cherry-pick).each do |type| diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index eb8a1bd5289..819e9bc15ae 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -8,7 +8,7 @@ - if @commits.present? = render "projects/commits/commit_list" - = render "projects/diffs/diffs", diff_files: @diffs.diff_files, project: @diffs.project, diff_refs: @diffs.diff_refs + = render "projects/diffs/diffs", diffs: @diffs - else .light-well .center diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 45895a9a3de..35fdf7d5278 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -1,4 +1,5 @@ - show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true) +- diff_files = diffs.diff_files - if diff_view == 'parallel' - fluid_layout true @@ -26,7 +27,7 @@ - diff_commit = commit_for_diff(diff_file) - blob = diff_file.blob(diff_commit) - next unless blob - - blob.load_all_data!(project.repository) unless blob.only_display_raw? + - blob.load_all_data!(@project.repository) unless blob.only_display_raw? - = render 'projects/diffs/file', i: index, project: project, - diff_file: diff_file, diff_commit: diff_commit, blob: blob, diff_refs: diff_refs + = render 'projects/diffs/file', i: index, project: @project, + diff_file: diff_file, diff_commit: diff_commit, blob: blob, diff_refs: diffs.diff_refs diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index cb2b623691c..598bd743676 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -42,7 +42,7 @@ %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. %p To preserve performance the line changes are not shown. - else - = render "projects/diffs/diffs", diff_files: @diffs.diff_files, project: @diffs.project, diff_refs: @merge_request.diff_refs, show_whitespace_toggle: false + = render "projects/diffs/diffs", diffs: @diffs, show_whitespace_toggle: false - if @pipeline #builds.builds.tab-pane = render "projects/merge_requests/show/builds" diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index 5b842dd9280..c6d2567af35 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -1,5 +1,5 @@ - if @merge_request_diff.collected? - = render "projects/diffs/diffs", diff_files: @diffs.diff_files, + = render "projects/diffs/diffs", diffs: @diffs, diff_refs: @diffs.diff_refs, project: @diffs.project - elsif @merge_request_diff.empty? .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index 0b6a01a3200..0b63913cfd1 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -33,25 +33,19 @@ class EmailsOnPushWorker reverse_compare = false if action == :push - merge_base_sha = project.merge_base_commit(before_sha, after_sha).try(:sha) + base_commit = project.merge_base_commit(before_sha, after_sha) compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) - - diff_refs = Gitlab::Diff::DiffRefs.new( - base_sha: merge_base_sha, - start_sha: before_sha, - head_sha: after_sha - ) + compare = Compare.decorate(compare, project) + compare.base_commit = base_commit + diff_refs = compare.diff_refs return false if compare.same if compare.commits.empty? compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha) - - diff_refs = Gitlab::Diff::DiffRefs.new( - base_sha: merge_base_sha, - start_sha: after_sha, - head_sha: before_sha - ) + compare = Compare.decorate(compare, project) + compare.base_commit = base_commit + diff_refs = compare.diff_refs reverse_compare = true diff --git a/lib/gitlab/diff/file_collection/base.rb b/lib/gitlab/diff/file_collection/base.rb index 20562773c14..a0c88265c45 100644 --- a/lib/gitlab/diff/file_collection/base.rb +++ b/lib/gitlab/diff/file_collection/base.rb @@ -7,7 +7,7 @@ module Gitlab delegate :count, :size, :real_size, to: :diff_files - def initialize(diffs, project:, diff_options:, diff_refs: nil) + def initialize(diffs, project:, diff_options: nil, diff_refs: nil) @diffs = diffs @project = project @diff_options = diff_options diff --git a/lib/gitlab/diff/file_collection/commit.rb b/lib/gitlab/diff/file_collection/commit.rb index 2a46109ad99..19def300b74 100644 --- a/lib/gitlab/diff/file_collection/commit.rb +++ b/lib/gitlab/diff/file_collection/commit.rb @@ -3,6 +3,9 @@ module Gitlab module FileCollection class Commit < Base def initialize(commit, diff_options:) + # Not merge just set defaults + diff_options = diff_options || Gitlab::Diff::FileCollection.default_options + super(commit.diffs(diff_options), project: commit.project, diff_options: diff_options, diff --git a/lib/gitlab/diff/file_collection/compare.rb b/lib/gitlab/diff/file_collection/compare.rb index 1bcda145f15..aba5a28b51f 100644 --- a/lib/gitlab/diff/file_collection/compare.rb +++ b/lib/gitlab/diff/file_collection/compare.rb @@ -3,6 +3,9 @@ module Gitlab module FileCollection class Compare < Base def initialize(compare, project:, diff_options:, diff_refs: nil) + # Not merge just set defaults + diff_options = diff_options || Gitlab::Diff::FileCollection.default_options + super(compare.diffs(diff_options), project: project, diff_options: diff_options, diff --git a/lib/gitlab/diff/file_collection/merge_request.rb b/lib/gitlab/diff/file_collection/merge_request.rb index 7c40622d594..9fde0bba183 100644 --- a/lib/gitlab/diff/file_collection/merge_request.rb +++ b/lib/gitlab/diff/file_collection/merge_request.rb @@ -4,6 +4,8 @@ module Gitlab class MergeRequest < Base def initialize(merge_request, diff_options:) @merge_request = merge_request + # Not merge just set defaults + diff_options = diff_options || Gitlab::Diff::FileCollection.default_options super(merge_request.diffs(diff_options), project: merge_request.project, @@ -27,15 +29,10 @@ module Gitlab if cacheable? cache_highlight!(diff_file) else - highlight_diff_file!(diff_file) + diff_file # Don't need to eager load highlighted diff lines end end - def highlight_diff_file!(diff_file) - diff_file.highlighted_diff_lines = Gitlab::Diff::Highlight.new(diff_file, repository: diff_file.repository).highlight - diff_file - end - def highlight_diff_file_from_cache!(diff_file, cache_diff_lines) diff_file.highlighted_diff_lines = cache_diff_lines.map do |line| Gitlab::Diff::Line.init_from_hash(line) @@ -56,7 +53,6 @@ module Gitlab if highlight_cache[file_path] highlight_diff_file_from_cache!(diff_file, highlight_cache[file_path]) else - highlight_diff_file!(diff_file) highlight_cache[file_path] = diff_file.highlighted_diff_lines.map(&:to_hash) end diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb index 71213813e17..16491ede71b 100644 --- a/lib/gitlab/email/message/repository_push.rb +++ b/lib/gitlab/email/message/repository_push.rb @@ -35,13 +35,13 @@ module Gitlab def commits return unless compare - @commits ||= Commit.decorate(compare.commits, project) + @commits ||= compare.commits end def diffs return unless compare - @diffs ||= compare.diff_file_collection(diff_options: { max_files: 30 }, diff_refs: diff_refs).diff_files + @diffs ||= compare.diff_file_collection(diff_options: { max_files: 30 }).diff_files end def diffs_count @@ -49,9 +49,7 @@ module Gitlab end def compare - if @opts[:compare] - Compare.decorate(@opts[:compare], project) - end + @opts[:compare] if @opts[:compare] end def diff_refs @@ -99,16 +97,18 @@ module Gitlab if commits.length > 1 namespace_project_compare_url(project_namespace, project, - from: Commit.new(compare.base, project), - to: Commit.new(compare.head, project)) + from: compare.start_commit, + to: compare.head_commit) else namespace_project_commit_url(project_namespace, - project, commits.first) + project, + commits.first) end else unless @action == :delete namespace_project_tree_url(project_namespace, - project, ref_name) + project, + ref_name) end end end diff --git a/spec/lib/gitlab/email/message/repository_push_spec.rb b/spec/lib/gitlab/email/message/repository_push_spec.rb index c1d07329983..5b966bddb6a 100644 --- a/spec/lib/gitlab/email/message/repository_push_spec.rb +++ b/spec/lib/gitlab/email/message/repository_push_spec.rb @@ -16,9 +16,12 @@ describe Gitlab::Email::Message::RepositoryPush do { author_id: author.id, ref: 'master', action: :push, compare: compare, send_from_committer_email: true } end - let(:compare) do + let(:raw_compare) do Gitlab::Git::Compare.new(project.repository.raw_repository, - sample_image_commit.id, sample_commit.id) + sample_image_commit.id, sample_commit.id) + end + let(:compare) do + Compare.decorate(raw_compare, project) end describe '#project' do @@ -62,7 +65,7 @@ describe Gitlab::Email::Message::RepositoryPush do describe '#diffs_count' do subject { message.diffs_count } - it { is_expected.to eq compare.diffs.size } + it { is_expected.to eq raw_compare.diffs.size } end describe '#compare' do @@ -72,7 +75,7 @@ describe Gitlab::Email::Message::RepositoryPush do describe '#compare_timeout' do subject { message.compare_timeout } - it { is_expected.to eq compare.diffs.overflow? } + it { is_expected.to eq raw_compare.diffs.overflow? } end describe '#reverse_compare?' do diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 3685b2b17b5..e2866ef160c 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -944,8 +944,9 @@ describe Notify do describe 'email on push with multiple commits' do let(:example_site_path) { root_path } let(:user) { create(:user) } - let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) } - let(:commits) { Commit.decorate(compare.commits, nil) } + let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) } + let(:compare) { Compare.decorate(raw_compare, project) } + let(:commits) { compare.commits } let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base, project), to: Commit.new(compare.head, project)) } let(:send_from_committer_email) { false } let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) } @@ -1046,8 +1047,9 @@ describe Notify do describe 'email on push with a single commit' do let(:example_site_path) { root_path } let(:user) { create(:user) } - let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) } - let(:commits) { Commit.decorate(compare.commits, nil) } + let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) } + let(:compare) { Compare.decorate(raw_compare, project) } + let(:commits) { compare.commits } let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) } let(:diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: project.merge_base_commit(sample_image_commit.id, sample_commit.id).id, head_sha: sample_commit.id) } diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 782d74ec5ec..232508cda23 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -61,7 +61,7 @@ describe MergeRequests::BuildService, services: true do end context 'one commit in the diff' do - let(:commits) { [commit_1] } + let(:commits) { Commit.decorate([commit_1], project) } it 'allows the merge request to be created' do expect(merge_request.can_be_created).to eq(true) @@ -84,7 +84,7 @@ describe MergeRequests::BuildService, services: true do end context 'commit has no description' do - let(:commits) { [commit_2] } + let(:commits) { Commit.decorate([commit_2], project) } it 'uses the title of the commit as the title of the merge request' do expect(merge_request.title).to eq(commit_2.safe_message) @@ -111,7 +111,7 @@ describe MergeRequests::BuildService, services: true do end context 'commit has no description' do - let(:commits) { [commit_2] } + let(:commits) { Commit.decorate([commit_2], project) } it 'sets the description to "Closes #$issue-iid"' do expect(merge_request.description).to eq("Closes ##{issue.iid}") @@ -121,7 +121,7 @@ describe MergeRequests::BuildService, services: true do end context 'more than one commit in the diff' do - let(:commits) { [commit_1, commit_2] } + let(:commits) { Commit.decorate([commit_1, commit_2], project) } it 'allows the merge request to be created' do expect(merge_request.can_be_created).to eq(true) -- cgit v1.2.1 From c86c1905b5574cac234315598d8d715fcaee3ea7 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Wed, 27 Jul 2016 19:00:34 +0200 Subject: switch from diff_file_collection to diffs So we have raw_diffs too --- app/controllers/projects/commit_controller.rb | 4 +- app/controllers/projects/compare_controller.rb | 4 +- .../projects/merge_requests_controller.rb | 8 +-- app/helpers/diff_helper.rb | 5 +- app/models/commit.rb | 10 ++- app/models/compare.rb | 36 +++++----- app/models/legacy_diff_note.rb | 4 +- app/models/merge_request.rb | 8 +-- app/models/repository.rb | 2 +- app/services/compare_service.rb | 1 - .../merge_request_diff_cache_service.rb | 2 +- app/views/projects/diffs/_diffs.html.haml | 14 ++-- .../projects/merge_requests/show/_diffs.html.haml | 3 +- app/workers/emails_on_push_worker.rb | 9 +-- app/workers/irker_worker.rb | 6 +- lib/api/commits.rb | 4 +- lib/api/entities.rb | 2 +- lib/gitlab/diff/file_collection.rb | 9 --- lib/gitlab/diff/file_collection/base.rb | 17 +++-- lib/gitlab/diff/file_collection/commit.rb | 5 +- lib/gitlab/diff/file_collection/compare.rb | 5 +- lib/gitlab/diff/file_collection/merge_request.rb | 23 ++----- lib/gitlab/email/message/repository_push.rb | 3 +- spec/helpers/diff_helper_spec.rb | 24 +++---- spec/lib/gitlab/diff/file_spec.rb | 2 +- spec/lib/gitlab/diff/highlight_spec.rb | 2 +- spec/lib/gitlab/diff/line_mapper_spec.rb | 2 +- spec/lib/gitlab/diff/parallel_diff_spec.rb | 2 +- spec/lib/gitlab/diff/parser_spec.rb | 2 +- spec/models/compare_spec.rb | 77 ++++++++++++++++++++++ spec/models/legacy_diff_note_spec.rb | 2 +- spec/models/merge_request_spec.rb | 29 +++++++- spec/models/repository_spec.rb | 4 +- spec/requests/api/commits_spec.rb | 4 +- .../merge_request_diff_cache_service_spec.rb | 4 +- 35 files changed, 208 insertions(+), 130 deletions(-) delete mode 100644 lib/gitlab/diff/file_collection.rb create mode 100644 spec/models/compare_spec.rb diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index 771a86530cd..fdfe7c65b7b 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -28,7 +28,7 @@ class Projects::CommitController < Projects::ApplicationController end def diff_for_path - render_diff_for_path(@commit.diff_file_collection(diff_options)) + render_diff_for_path(@commit.diffs(diff_options)) end def builds @@ -110,7 +110,7 @@ class Projects::CommitController < Projects::ApplicationController opts = diff_options opts[:ignore_whitespace_change] = true if params[:format] == 'diff' - @diffs = commit.diff_file_collection(opts) + @diffs = commit.diffs(opts) @notes_count = commit.notes.count end diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 7fca5e77f32..4a42a7d091b 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -21,7 +21,7 @@ class Projects::CompareController < Projects::ApplicationController def diff_for_path return render_404 unless @compare - render_diff_for_path(@compare.diff_file_collection(diff_options: diff_options)) + render_diff_for_path(@compare.diffs(diff_options: diff_options)) end def create @@ -45,7 +45,7 @@ class Projects::CompareController < Projects::ApplicationController @commit = @compare.commit @base_commit = @compare.base_commit - @diffs = @compare.diff_file_collection(diff_options: diff_options) + @diffs = @compare.diffs(diff_options: diff_options) @diff_notes_disabled = true @grouped_diff_discussions = {} diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 20afc6afcb2..2cf6a2dd1b3 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -86,7 +86,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController respond_to do |format| format.html { define_discussion_vars } format.json do - @diffs = @merge_request.diff_file_collection(diff_options) + @diffs = @merge_request.diffs(diff_options) render json: { html: view_to_html_string("projects/merge_requests/show/_diffs") } end @@ -108,7 +108,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController define_commit_vars - render_diff_for_path(@merge_request.diff_file_collection(diff_options)) + render_diff_for_path(@merge_request.diffs(diff_options)) end def commits @@ -156,9 +156,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @commits = @merge_request.compare_commits.reverse @commit = @merge_request.diff_head_commit @base_commit = @merge_request.diff_base_commit - if @merge_request.compare - @diffs = @merge_request.diff_file_collection(diff_options) - end + @diffs = @merge_request.diffs(diff_options) if @merge_request.compare @diff_notes_disabled = true @pipeline = @merge_request.pipeline diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 2abe24b78bf..cc7121b1163 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -23,10 +23,7 @@ module DiffHelper end def diff_options - options = Gitlab::Diff::FileCollection.default_options.merge( - ignore_whitespace_change: hide_whitespace?, - no_collapse: expand_all_diffs? - ) + options = { ignore_whitespace_change: hide_whitespace?, no_collapse: expand_all_diffs? } if action_name == 'diff_for_path' options[:no_collapse] = true diff --git a/app/models/commit.rb b/app/models/commit.rb index a339d47f5f3..d58c2fb8106 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -104,7 +104,7 @@ class Commit end def diff_line_count - @diff_line_count ||= Commit::diff_line_count(self.diffs) + @diff_line_count ||= Commit::diff_line_count(raw_diffs) @diff_line_count end @@ -317,7 +317,11 @@ class Commit nil end - def diff_file_collection(diff_options = nil) + def raw_diffs(*args) + raw.diffs(*args) + end + + def diffs(diff_options = nil) Gitlab::Diff::FileCollection::Commit.new(self, diff_options: diff_options) end @@ -330,7 +334,7 @@ class Commit def repo_changes changes = { added: [], modified: [], removed: [] } - diffs.each do |diff| + raw_diffs.each do |diff| if diff.deleted_file changes[:removed] << diff.old_path elsif diff.renamed_file || diff.new_file diff --git a/app/models/compare.rb b/app/models/compare.rb index 05c8fbbcc36..98c042f3809 100644 --- a/app/models/compare.rb +++ b/app/models/compare.rb @@ -1,6 +1,8 @@ class Compare delegate :same, :head, :base, to: :@compare + attr_reader :project + def self.decorate(compare, project) if compare.is_a?(Compare) compare @@ -15,49 +17,47 @@ class Compare end def commits - @commits ||= Commit.decorate(@compare.commits, @project) + @commits ||= Commit.decorate(@compare.commits, project) end def start_commit return @start_commit if defined?(@start_commit) commit = @compare.base - @start_commit = commit ? ::Commit.new(commit, @project) : nil + @start_commit = commit ? ::Commit.new(commit, project) : nil end - def commit - return @commit if defined?(@commit) + def head_commit + return @head_commit if defined?(@head_commit) commit = @compare.head - @commit = commit ? ::Commit.new(commit, @project) : nil - end - alias_method :head_commit, :commit - - # Used only on emails_on_push_worker.rb - def base_commit=(commit) - @base_commit = commit + @head_commit = commit ? ::Commit.new(commit, project) : nil end + alias_method :commit, :head_commit def base_commit return @base_commit if defined?(@base_commit) - @base_commit = if start_commit && commit - @project.merge_base_commit(start_commit.id, commit.id) + @base_commit = if start_commit && head_commit + project.merge_base_commit(start_commit.id, head_commit.id) else nil end end - # keyword args until we get ride of diff_refs as argument - def diff_file_collection(diff_options:, diff_refs: self.diff_refs) - Gitlab::Diff::FileCollection::Compare.new(@compare, - project: @project, + def raw_diffs(*args) + @compare.diffs(*args) + end + + def diffs(diff_options:) + Gitlab::Diff::FileCollection::Compare.new(self, + project: project, diff_options: diff_options, diff_refs: diff_refs) end def diff_refs - @diff_refs ||= Gitlab::Diff::DiffRefs.new( + Gitlab::Diff::DiffRefs.new( base_sha: base_commit.try(:sha), start_sha: start_commit.try(:sha), head_sha: commit.try(:sha) diff --git a/app/models/legacy_diff_note.rb b/app/models/legacy_diff_note.rb index 865712268a0..6ed66001513 100644 --- a/app/models/legacy_diff_note.rb +++ b/app/models/legacy_diff_note.rb @@ -85,7 +85,7 @@ class LegacyDiffNote < Note return nil unless noteable return @diff if defined?(@diff) - @diff = noteable.diffs(Commit.max_diff_options).find do |d| + @diff = noteable.raw_diffs(Commit.max_diff_options).find do |d| d.new_path && Digest::SHA1.hexdigest(d.new_path) == diff_file_hash end end @@ -116,7 +116,7 @@ class LegacyDiffNote < Note # Find the diff on noteable that matches our own def find_noteable_diff - diffs = noteable.diffs(Commit.max_diff_options) + diffs = noteable.raw_diffs(Commit.max_diff_options) diffs.find { |d| d.new_path == self.diff.new_path } end end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 62e5573dfdc..009262d6b48 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -164,13 +164,13 @@ class MergeRequest < ActiveRecord::Base merge_request_diff ? merge_request_diff.first_commit : compare_commits.first end - def diffs(*args) - merge_request_diff ? merge_request_diff.diffs(*args) : compare.diffs(*args) + def raw_diffs(*args) + merge_request_diff ? merge_request_diff.diffs(*args) : compare.raw_diffs(*args) end - def diff_file_collection(diff_options = nil) + def diffs(diff_options = nil) if self.compare - self.compare.diff_file_collection(diff_options: diff_options, diff_refs: diff_refs) + self.compare.diffs(diff_options: diff_options) else Gitlab::Diff::FileCollection::MergeRequest.new(self, diff_options: diff_options) end diff --git a/app/models/repository.rb b/app/models/repository.rb index bac37483c47..3d95344a68f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -372,7 +372,7 @@ class Repository # We don't want to flush the cache if the commit didn't actually make any # changes to any of the possible avatar files. if revision && commit = self.commit(revision) - return unless commit.diffs. + return unless commit.raw_diffs. any? { |diff| AVATAR_FILES.include?(diff.new_path) } end diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index bb3aff72b47..6d6075628af 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -26,7 +26,6 @@ class CompareService source_sha ) - # REVIEW be sure if it's target_project or source_project Compare.new(raw_compare, target_project) end end diff --git a/app/services/merge_requests/merge_request_diff_cache_service.rb b/app/services/merge_requests/merge_request_diff_cache_service.rb index 8151c24d1b0..2945a7fd4e4 100644 --- a/app/services/merge_requests/merge_request_diff_cache_service.rb +++ b/app/services/merge_requests/merge_request_diff_cache_service.rb @@ -2,7 +2,7 @@ module MergeRequests class MergeRequestDiffCacheService def execute(merge_request) # Executing the iteration we cache all the highlighted diff information - merge_request.diff_file_collection.diff_files.to_a + merge_request.diffs.diff_files.to_a end end end diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 35fdf7d5278..20dc280c3b2 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -9,11 +9,11 @@ = link_to 'Expand all', url_for(params.merge(expand_all_diffs: 1, format: nil)), class: 'btn btn-default' - if show_whitespace_toggle - if current_controller?(:commit) - = commit_diff_whitespace_link(@project, @commit, class: 'hidden-xs') + = commit_diff_whitespace_link(diffs.project, @commit, class: 'hidden-xs') - elsif current_controller?(:merge_requests) - = diff_merge_request_whitespace_link(@project, @merge_request, class: 'hidden-xs') + = diff_merge_request_whitespace_link(diffs.project, @merge_request, class: 'hidden-xs') - elsif current_controller?(:compare) - = diff_compare_whitespace_link(@project, params[:from], params[:to], class: 'hidden-xs') + = diff_compare_whitespace_link(diffs.project, params[:from], params[:to], class: 'hidden-xs') .btn-group = inline_diff_btn = parallel_diff_btn @@ -22,12 +22,12 @@ - if diff_files.overflow? = render 'projects/diffs/warning', diff_files: diff_files -.files{data: {can_create_note: (!@diff_notes_disabled && can?(current_user, :create_note, @project))}} +.files{data: {can_create_note: (!@diff_notes_disabled && can?(current_user, :create_note, diffs.project))}} - diff_files.each_with_index do |diff_file, index| - diff_commit = commit_for_diff(diff_file) - blob = diff_file.blob(diff_commit) - next unless blob - - blob.load_all_data!(@project.repository) unless blob.only_display_raw? + - blob.load_all_data!(diffs.project.repository) unless blob.only_display_raw? - = render 'projects/diffs/file', i: index, project: @project, - diff_file: diff_file, diff_commit: diff_commit, blob: blob, diff_refs: diffs.diff_refs + = render 'projects/diffs/file', i: index, project: diffs.project, + diff_file: diff_file, diff_commit: diff_commit, blob: blob diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index c6d2567af35..013b05628fa 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -1,6 +1,5 @@ - if @merge_request_diff.collected? - = render "projects/diffs/diffs", diffs: @diffs, - diff_refs: @diffs.diff_refs, project: @diffs.project + = render "projects/diffs/diffs", diffs: @diffs - elsif @merge_request_diff.empty? .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} - else diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index 0b63913cfd1..c6a5af2809a 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -33,18 +33,13 @@ class EmailsOnPushWorker reverse_compare = false if action == :push - base_commit = project.merge_base_commit(before_sha, after_sha) - compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) - compare = Compare.decorate(compare, project) - compare.base_commit = base_commit + compare = CompareService.new.execute(project, before_sha, project, after_sha) diff_refs = compare.diff_refs return false if compare.same if compare.commits.empty? - compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha) - compare = Compare.decorate(compare, project) - compare.base_commit = base_commit + compare = CompareService.new.execute(project, after_sha, project, before_sha) diff_refs = compare.diff_refs reverse_compare = true diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb index a3c34e02baa..07cc7c1cbd7 100644 --- a/app/workers/irker_worker.rb +++ b/app/workers/irker_worker.rb @@ -141,8 +141,10 @@ class IrkerWorker end def files_count(commit) - files = "#{commit.diffs.real_size} file" - files += 's' if commit.diffs.size > 1 + diffs = commit.raw_diffs + + files = "#{diffs.real_size} file" + files += 's' if diffs.size > 1 files end diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 4a11c8e3620..b4eaf1813d4 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -54,7 +54,7 @@ module API sha = params[:sha] commit = user_project.commit(sha) not_found! "Commit" unless commit - commit.diffs.to_a + commit.raw_diffs.to_a end # Get a commit's comments @@ -96,7 +96,7 @@ module API } if params[:path] && params[:line] && params[:line_type] - commit.diffs(all_diffs: true).each do |diff| + commit.raw_diffs(all_diffs: true).each do |diff| next unless diff.new_path == params[:path] lines = Gitlab::Diff::Parser.new.parse(diff.diff.each_line) diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 3e21b7a0b8a..e5b00dc45a5 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -224,7 +224,7 @@ module API class MergeRequestChanges < MergeRequest expose :diffs, as: :changes, using: Entities::RepoDiff do |compare, _| - compare.diffs(all_diffs: true).to_a + compare.raw_diffs(all_diffs: true).to_a end end diff --git a/lib/gitlab/diff/file_collection.rb b/lib/gitlab/diff/file_collection.rb deleted file mode 100644 index ce6717c7205..00000000000 --- a/lib/gitlab/diff/file_collection.rb +++ /dev/null @@ -1,9 +0,0 @@ -module Gitlab - module Diff - module FileCollection - def self.default_options - ::Commit.max_diff_options.merge(ignore_whitespace_change: false, no_collapse: false) - end - end - end -end diff --git a/lib/gitlab/diff/file_collection/base.rb b/lib/gitlab/diff/file_collection/base.rb index a0c88265c45..2b9fc65b985 100644 --- a/lib/gitlab/diff/file_collection/base.rb +++ b/lib/gitlab/diff/file_collection/base.rb @@ -1,28 +1,33 @@ module Gitlab module Diff module FileCollection - class Base attr_reader :project, :diff_options, :diff_view, :diff_refs delegate :count, :size, :real_size, to: :diff_files - def initialize(diffs, project:, diff_options: nil, diff_refs: nil) - @diffs = diffs + def self.default_options + ::Commit.max_diff_options.merge(ignore_whitespace_change: false, no_collapse: false) + end + + def initialize(diffable, project:, diff_options: nil, diff_refs: nil) + diff_options = self.class.default_options.merge(diff_options || {}) + + @diffable = diffable + @diffs = diffable.raw_diffs(diff_options) @project = project @diff_options = diff_options @diff_refs = diff_refs end def diff_files - @diffs.decorate! { |diff| decorate_diff!(diff) } + @diff_files ||= @diffs.decorate! { |diff| decorate_diff!(diff) } end private def decorate_diff!(diff) - return diff if diff.is_a?(Gitlab::Diff::File) - Gitlab::Diff::File.new(diff, diff_refs: @diff_refs, repository: @project.repository) + Gitlab::Diff::File.new(diff, repository: project.repository, diff_refs: diff_refs) end end end diff --git a/lib/gitlab/diff/file_collection/commit.rb b/lib/gitlab/diff/file_collection/commit.rb index 19def300b74..4dc297ec036 100644 --- a/lib/gitlab/diff/file_collection/commit.rb +++ b/lib/gitlab/diff/file_collection/commit.rb @@ -3,10 +3,7 @@ module Gitlab module FileCollection class Commit < Base def initialize(commit, diff_options:) - # Not merge just set defaults - diff_options = diff_options || Gitlab::Diff::FileCollection.default_options - - super(commit.diffs(diff_options), + super(commit, project: commit.project, diff_options: diff_options, diff_refs: commit.diff_refs) diff --git a/lib/gitlab/diff/file_collection/compare.rb b/lib/gitlab/diff/file_collection/compare.rb index aba5a28b51f..20d8f891cc3 100644 --- a/lib/gitlab/diff/file_collection/compare.rb +++ b/lib/gitlab/diff/file_collection/compare.rb @@ -3,10 +3,7 @@ module Gitlab module FileCollection class Compare < Base def initialize(compare, project:, diff_options:, diff_refs: nil) - # Not merge just set defaults - diff_options = diff_options || Gitlab::Diff::FileCollection.default_options - - super(compare.diffs(diff_options), + super(compare, project: project, diff_options: diff_options, diff_refs: diff_refs) diff --git a/lib/gitlab/diff/file_collection/merge_request.rb b/lib/gitlab/diff/file_collection/merge_request.rb index 9fde0bba183..4f946908e2f 100644 --- a/lib/gitlab/diff/file_collection/merge_request.rb +++ b/lib/gitlab/diff/file_collection/merge_request.rb @@ -4,10 +4,8 @@ module Gitlab class MergeRequest < Base def initialize(merge_request, diff_options:) @merge_request = merge_request - # Not merge just set defaults - diff_options = diff_options || Gitlab::Diff::FileCollection.default_options - super(merge_request.diffs(diff_options), + super(merge_request, project: merge_request.project, diff_options: diff_options, diff_refs: merge_request.diff_refs) @@ -19,18 +17,11 @@ module Gitlab private - # Extracted method to highlight in the same iteration to the diff_collection. Iteration in the DiffCollections - # seems particularly slow on big diffs (event when already populated). + # Extracted method to highlight in the same iteration to the diff_collection. def decorate_diff!(diff) - highlight! super - end - - def highlight!(diff_file) - if cacheable? - cache_highlight!(diff_file) - else - diff_file # Don't need to eager load highlighted diff lines - end + diff_file = super + cache_highlight!(diff_file) if cacheable? + diff_file end def highlight_diff_file_from_cache!(diff_file, cache_diff_lines) @@ -55,15 +46,13 @@ module Gitlab else highlight_cache[file_path] = diff_file.highlighted_diff_lines.map(&:to_hash) end - - diff_file end def highlight_cache return @highlight_cache if defined?(@highlight_cache) @highlight_cache = Rails.cache.read(cache_key) || {} - @highlight_cache_was_empty = highlight_cache.empty? + @highlight_cache_was_empty = @highlight_cache.empty? @highlight_cache end diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb index 16491ede71b..62d29387d60 100644 --- a/lib/gitlab/email/message/repository_push.rb +++ b/lib/gitlab/email/message/repository_push.rb @@ -41,7 +41,8 @@ module Gitlab def diffs return unless compare - @diffs ||= compare.diff_file_collection(diff_options: { max_files: 30 }).diff_files + # This diff is more moderated in number of files and lines + @diffs ||= compare.diffs(diff_options: { max_files: 30, max_lines: 5000, no_collapse: true }).diff_files end def diffs_count diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index c2fd2c8a533..4949280d641 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -6,7 +6,7 @@ describe DiffHelper do let(:project) { create(:project) } let(:repository) { project.repository } let(:commit) { project.commit(sample_commit.id) } - let(:diffs) { commit.diffs } + let(:diffs) { commit.raw_diffs } let(:diff) { diffs.first } let(:diff_refs) { [commit.parent, commit] } let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) } @@ -32,16 +32,6 @@ describe DiffHelper do end describe 'diff_options' do - it 'should return hard limit for a diff if force diff is true' do - allow(controller).to receive(:params) { { force_show_diff: true } } - expect(diff_options).to include(Commit.max_diff_options) - end - - it 'should return hard limit for a diff if expand_all_diffs is true' do - allow(controller).to receive(:params) { { expand_all_diffs: true } } - expect(diff_options).to include(Commit.max_diff_options) - end - it 'should return no collapse false' do expect(diff_options).to include(no_collapse: false) end @@ -55,6 +45,18 @@ describe DiffHelper do allow(controller).to receive(:action_name) { 'diff_for_path' } expect(diff_options).to include(no_collapse: true) end + + it 'should return paths if action name diff_for_path and param old path' do + allow(controller).to receive(:params) { { old_path: 'lib/wadus.rb' } } + allow(controller).to receive(:action_name) { 'diff_for_path' } + expect(diff_options[:paths]).to include('lib/wadus.rb') + end + + it 'should return paths if action name diff_for_path and param new path' do + allow(controller).to receive(:params) { { new_path: 'lib/wadus.rb' } } + allow(controller).to receive(:action_name) { 'diff_for_path' } + expect(diff_options[:paths]).to include('lib/wadus.rb') + end end describe 'unfold_bottom_class' do diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index e883a6eb9c2..0650cb291e5 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Diff::File, lib: true do let(:project) { create(:project) } let(:commit) { project.commit(sample_commit.id) } - let(:diff) { commit.diffs.first } + let(:diff) { commit.raw_diffs.first } let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) } describe '#diff_lines' do diff --git a/spec/lib/gitlab/diff/highlight_spec.rb b/spec/lib/gitlab/diff/highlight_spec.rb index 88e4115c453..1c2ddeed692 100644 --- a/spec/lib/gitlab/diff/highlight_spec.rb +++ b/spec/lib/gitlab/diff/highlight_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Diff::Highlight, lib: true do let(:project) { create(:project) } let(:commit) { project.commit(sample_commit.id) } - let(:diff) { commit.diffs.first } + let(:diff) { commit.raw_diffs.first } let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: project.repository) } describe '#highlight' do diff --git a/spec/lib/gitlab/diff/line_mapper_spec.rb b/spec/lib/gitlab/diff/line_mapper_spec.rb index 4e50e03bb7e..4b943fa382d 100644 --- a/spec/lib/gitlab/diff/line_mapper_spec.rb +++ b/spec/lib/gitlab/diff/line_mapper_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Diff::LineMapper, lib: true do let(:project) { create(:project) } let(:repository) { project.repository } let(:commit) { project.commit(sample_commit.id) } - let(:diffs) { commit.diffs } + let(:diffs) { commit.raw_diffs } let(:diff) { diffs.first } let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) } subject { described_class.new(diff_file) } diff --git a/spec/lib/gitlab/diff/parallel_diff_spec.rb b/spec/lib/gitlab/diff/parallel_diff_spec.rb index 2aa5ae44f54..af18d3c25a6 100644 --- a/spec/lib/gitlab/diff/parallel_diff_spec.rb +++ b/spec/lib/gitlab/diff/parallel_diff_spec.rb @@ -6,7 +6,7 @@ describe Gitlab::Diff::ParallelDiff, lib: true do let(:project) { create(:project) } let(:repository) { project.repository } let(:commit) { project.commit(sample_commit.id) } - let(:diffs) { commit.diffs } + let(:diffs) { commit.raw_diffs } let(:diff) { diffs.first } let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: commit.diff_refs, repository: repository) } subject { described_class.new(diff_file) } diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb index c3359627652..b983d73f8be 100644 --- a/spec/lib/gitlab/diff/parser_spec.rb +++ b/spec/lib/gitlab/diff/parser_spec.rb @@ -5,7 +5,7 @@ describe Gitlab::Diff::Parser, lib: true do let(:project) { create(:project) } let(:commit) { project.commit(sample_commit.id) } - let(:diff) { commit.diffs.first } + let(:diff) { commit.raw_diffs.first } let(:parser) { Gitlab::Diff::Parser.new } describe '#parse' do diff --git a/spec/models/compare_spec.rb b/spec/models/compare_spec.rb new file mode 100644 index 00000000000..49ab3c4b6e9 --- /dev/null +++ b/spec/models/compare_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +describe Compare, models: true do + include RepoHelpers + + let(:project) { create(:project, :public) } + let(:commit) { project.commit } + + let(:start_commit) { sample_image_commit } + let(:head_commit) { sample_commit } + + let(:raw_compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, start_commit.id, head_commit.id) } + + subject { described_class.new(raw_compare, project) } + + describe '#start_commit' do + it 'returns raw compare base commit' do + expect(subject.start_commit.id).to eq(start_commit.id) + end + + it 'returns nil if compare base commit is nil' do + expect(raw_compare).to receive(:base).and_return(nil) + + expect(subject.start_commit).to eq(nil) + end + end + + describe '#commit' do + it 'returns raw compare head commit' do + expect(subject.commit.id).to eq(head_commit.id) + end + + it 'returns nil if compare head commit is nil' do + expect(raw_compare).to receive(:head).and_return(nil) + + expect(subject.commit).to eq(nil) + end + end + + describe '#base_commit' do + let(:base_commit) { Commit.new(another_sample_commit, project) } + + it 'returns project merge base commit' do + expect(project).to receive(:merge_base_commit).with(start_commit.id, head_commit.id).and_return(base_commit) + + expect(subject.base_commit).to eq(base_commit) + end + + it 'returns nil if there is no start_commit' do + expect(subject).to receive(:start_commit).and_return(nil) + + expect(subject.base_commit).to eq(nil) + end + + it 'returns nil if there is no head commit' do + expect(subject).to receive(:head_commit).and_return(nil) + + expect(subject.base_commit).to eq(nil) + end + end + + describe '#diff_refs' do + it 'uses base_commit sha as base_sha' do + expect(subject).to receive(:base_commit).at_least(:once).and_call_original + + expect(subject.diff_refs.base_sha).to eq(subject.base_commit.id) + end + + it 'uses start_commit sha as start_sha' do + expect(subject.diff_refs.start_sha).to eq(start_commit.id) + end + + it 'uses commit sha as head sha' do + expect(subject.diff_refs.head_sha).to eq(head_commit.id) + end + end +end diff --git a/spec/models/legacy_diff_note_spec.rb b/spec/models/legacy_diff_note_spec.rb index d23fc06c3ad..c8ee656fe3b 100644 --- a/spec/models/legacy_diff_note_spec.rb +++ b/spec/models/legacy_diff_note_spec.rb @@ -58,7 +58,7 @@ describe LegacyDiffNote, models: true do # Generate a real line_code value so we know it will match. We use a # random line from a random diff just for funsies. - diff = merge.diffs.to_a.sample + diff = merge.raw_diffs.to_a.sample line = Gitlab::Diff::Parser.new.parse(diff.diff.each_line).to_a.sample code = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index fa1f7edae8e..152e0cce5ad 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -128,7 +128,7 @@ describe MergeRequest, models: true do end end - describe '#diffs' do + describe '#raw_diffs' do let(:merge_request) { build(:merge_request) } let(:options) { { paths: ['a/b', 'b/a', 'c/*'] } } @@ -138,6 +138,31 @@ describe MergeRequest, models: true do expect(merge_request.merge_request_diff).to receive(:diffs).with(options) + merge_request.raw_diffs(options) + end + end + + context 'when there are no MR diffs' do + it 'delegates to the compare object' do + merge_request.compare = double(:compare) + + expect(merge_request.compare).to receive(:raw_diffs).with(options) + + merge_request.raw_diffs(options) + end + end + end + + describe '#diffs' do + let(:merge_request) { build(:merge_request) } + let(:options) { { paths: ['a/b', 'b/a', 'c/*'] } } + + context 'when there are MR diffs' do + it 'delegates to the MR diffs' do + merge_request.merge_request_diff = MergeRequestDiff.new + + expect(merge_request.merge_request_diff).to receive(:diffs).with(hash_including(options)) + merge_request.diffs(options) end end @@ -146,7 +171,7 @@ describe MergeRequest, models: true do it 'delegates to the compare object' do merge_request.compare = double(:compare) - expect(merge_request.compare).to receive(:diffs).with(options) + expect(merge_request.compare).to receive(:diffs).with(diff_options: options) merge_request.diffs(options) end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index cce15538b93..2a053b1804f 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -1154,7 +1154,7 @@ describe Repository, models: true do it 'does not flush the cache if the commit does not change any logos' do diff = double(:diff, new_path: 'test.txt') - expect(commit).to receive(:diffs).and_return([diff]) + expect(commit).to receive(:raw_diffs).and_return([diff]) expect(cache).not_to receive(:expire) repository.expire_avatar_cache(repository.root_ref, '123') @@ -1163,7 +1163,7 @@ describe Repository, models: true do it 'flushes the cache if the commit changes any of the logos' do diff = double(:diff, new_path: Repository::AVATAR_FILES[0]) - expect(commit).to receive(:diffs).and_return([diff]) + expect(commit).to receive(:raw_diffs).and_return([diff]) expect(cache).to receive(:expire).with(:avatar) repository.expire_avatar_cache(repository.root_ref, '123') diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index e4ea8506598..51ee2167d47 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -173,10 +173,10 @@ describe API::API, api: true do end it 'should return the inline comment' do - post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.diffs.first.new_path, line: 7, line_type: 'new' + post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.raw_diffs.first.new_path, line: 7, line_type: 'new' expect(response).to have_http_status(201) expect(json_response['note']).to eq('My comment') - expect(json_response['path']).to eq(project.repository.commit.diffs.first.new_path) + expect(json_response['path']).to eq(project.repository.commit.raw_diffs.first.new_path) expect(json_response['line']).to eq(7) expect(json_response['line_type']).to eq('new') end diff --git a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb index c6cceed31ad..8f71d71b0f0 100644 --- a/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb +++ b/spec/services/merge_requests/merge_request_diff_cache_service_spec.rb @@ -5,9 +5,9 @@ describe MergeRequests::MergeRequestDiffCacheService do let(:subject) { MergeRequests::MergeRequestDiffCacheService.new } describe '#execute' do - it 'retrieve the diff files to cache the highlighted result' do + it 'retrieves the diff files to cache the highlighted result' do merge_request = create(:merge_request) - cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection.default_options] + cache_key = [merge_request.merge_request_diff, 'highlighted-diff-files', Gitlab::Diff::FileCollection::MergeRequest.default_options] expect(Rails.cache).to receive(:read).with(cache_key).and_return({}) expect(Rails.cache).to receive(:write).with(cache_key, anything) -- cgit v1.2.1 From 1a9431b914b7d138a37557b5d4a81ecd2423692c Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 3 Aug 2016 08:54:01 +0300 Subject: Move markdown doc to the right location [ci skip] --- doc/README.md | 2 +- doc/markdown/markdown.md | 1 + doc/user/img/markdown_logo.png | Bin 0 -> 9509 bytes doc/user/img/markdown_video.mp4 | Bin 0 -> 383631 bytes doc/user/markdown.md | 786 ++++++++++++++++++++++++++++++++ doc/user/project/img/markdown_logo.png | Bin 9509 -> 0 bytes doc/user/project/img/markdown_video.mp4 | Bin 383631 -> 0 bytes doc/user/project/markdown.md | 786 -------------------------------- 8 files changed, 788 insertions(+), 787 deletions(-) create mode 100644 doc/markdown/markdown.md create mode 100644 doc/user/img/markdown_logo.png create mode 100644 doc/user/img/markdown_video.mp4 create mode 100644 doc/user/markdown.md delete mode 100644 doc/user/project/img/markdown_logo.png delete mode 100644 doc/user/project/img/markdown_video.mp4 delete mode 100644 doc/user/project/markdown.md diff --git a/doc/README.md b/doc/README.md index 751e685b19b..d28ad499d3a 100644 --- a/doc/README.md +++ b/doc/README.md @@ -9,7 +9,7 @@ - [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. - [Importing to GitLab](workflow/importing/README.md). - [Importing and exporting projects between instances](user/project/settings/import_export.md). -- [Markdown](user/project/markdown.md) GitLab's advanced formatting system. +- [Markdown](user/markdown.md) GitLab's advanced formatting system. - [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab. - [Permissions](user/permissions.md) Learn what each role in a project (external/guest/reporter/developer/master/owner) can do. - [Profile Settings](profile/README.md) diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md new file mode 100644 index 00000000000..4ac81ab3ee7 --- /dev/null +++ b/doc/markdown/markdown.md @@ -0,0 +1 @@ +This document was moved to [user/markdown.md](../user/markdown.md). diff --git a/doc/user/img/markdown_logo.png b/doc/user/img/markdown_logo.png new file mode 100644 index 00000000000..05c8b0d0ccf Binary files /dev/null and b/doc/user/img/markdown_logo.png differ diff --git a/doc/user/img/markdown_video.mp4 b/doc/user/img/markdown_video.mp4 new file mode 100644 index 00000000000..1fc478842f5 Binary files /dev/null and b/doc/user/img/markdown_video.mp4 differ diff --git a/doc/user/markdown.md b/doc/user/markdown.md new file mode 100644 index 00000000000..7fe96e67dbb --- /dev/null +++ b/doc/user/markdown.md @@ -0,0 +1,786 @@ +# Markdown + +## Table of Contents + +**[GitLab Flavored Markdown](#gitlab-flavored-markdown-gfm)** + +* [Newlines](#newlines) +* [Multiple underscores in words](#multiple-underscores-in-words) +* [URL auto-linking](#url-auto-linking) +* [Multiline Blockquote](#multiline-blockquote) +* [Code and Syntax Highlighting](#code-and-syntax-highlighting) +* [Inline Diff](#inline-diff) +* [Emoji](#emoji) +* [Special GitLab references](#special-gitlab-references) +* [Task Lists](#task-lists) +* [Videos](#videos) + +**[Standard Markdown](#standard-markdown)** + +* [Headers](#headers) +* [Emphasis](#emphasis) +* [Lists](#lists) +* [Links](#links) +* [Images](#images) +* [Blockquotes](#blockquotes) +* [Inline HTML](#inline-html) +* [Horizontal Rule](#horizontal-rule) +* [Line Breaks](#line-breaks) +* [Tables](#tables) + +**[Wiki-Specific Markdown](#wiki-specific-markdown)** + +* [Wiki - Direct page link](#wiki-direct-page-link) +* [Wiki - Direct file link](#wiki-direct-file-link) +* [Wiki - Hierarchical link](#wiki-hierarchical-link) +* [Wiki - Root link](#wiki-root-link) + +**[References](#references)** + +## GitLab Flavored Markdown (GFM) + +> **Note:** +Not all of the GitLab-specific extensions to Markdown that are described in +this document currently work on our documentation website. +> +For the best result, we encourage you to check this document out as rendered +by GitLab: [markdown.md] + +_GitLab uses the [Redcarpet Ruby library][redcarpet] for Markdown processing._ + +GitLab uses "GitLab Flavored Markdown" (GFM). It extends the standard Markdown in a few significant ways to add some useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). + +You can use GFM in the following areas: + +- comments +- issues +- merge requests +- milestones +- snippets (the snippet must be named with a `.md` extension) +- wiki pages +- markdown documents inside the repository + +You can also use other rich text files in GitLab. You might have to install a +dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information. + +## Newlines + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#newlines + +GFM honors the markdown specification in how [paragraphs and line breaks are handled](https://daringfireball.net/projects/markdown/syntax#p). + +A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. +Line-breaks, or softreturns, are rendered if you end a line with two or more spaces: + + Roses are red [followed by two or more spaces] + Violets are blue + + Sugar is sweet + +Roses are red +Violets are blue + +Sugar is sweet + +## Multiple underscores in words + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiple-underscores-in-words + +It is not reasonable to italicize just _part_ of a word, especially when you're dealing with code and names that often appear with multiple underscores. Therefore, GFM ignores multiple underscores in words: + + perform_complicated_task + + do_this_and_do_that_and_another_thing + +perform_complicated_task + +do_this_and_do_that_and_another_thing + +## URL auto-linking + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#url-auto-linking + +GFM will autolink almost any URL you copy and paste into your text: + + * https://www.google.com + * https://google.com/ + * ftp://ftp.us.debian.org/debian/ + * smb://foo/bar/baz + * irc://irc.freenode.net/gitlab + * http://localhost:3000 + +* https://www.google.com +* https://google.com/ +* ftp://ftp.us.debian.org/debian/ +* smb://foo/bar/baz +* irc://irc.freenode.net/gitlab +* http://localhost:3000 + +## Multiline Blockquote + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiline-blockquote + +On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines, +GFM supports multiline blockquotes fenced by >>>: + +```no-highlight +>>> +If you paste a message from somewhere else + +that + +spans + +multiple lines, + +you can quote that without having to manually prepend `>` to every line! +>>> +``` + +>>> +If you paste a message from somewhere else + +that + +spans + +multiple lines, + +you can quote that without having to manually prepend `>` to every line! +>>> + +## Code and Syntax Highlighting + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#code-and-syntax-highlighting + +_GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a +list of supported languages visit the Rouge website._ + +Blocks of code are either fenced by lines with three back-ticks ```, +or are indented with four spaces. Only the fenced code blocks support syntax +highlighting: + +```no-highlight +Inline `code` has `back-ticks around` it. +``` + +Inline `code` has `back-ticks around` it. + +Example: + + ```javascript + var s = "JavaScript syntax highlighting"; + alert(s); + ``` + + ```python + def function(): + #indenting works just fine in the fenced code block + s = "Python syntax highlighting" + print s + ``` + + ```ruby + require 'redcarpet' + markdown = Redcarpet.new("Hello World!") + puts markdown.to_html + ``` + + ``` + No language indicated, so no syntax highlighting. + s = "There is no highlighting for this." + But let's throw in a tag. + ``` + +becomes: + +```javascript +var s = "JavaScript syntax highlighting"; +alert(s); +``` + +```python +def function(): + #indenting works just fine in the fenced code block + s = "Python syntax highlighting" + print s +``` + +```ruby +require 'redcarpet' +markdown = Redcarpet.new("Hello World!") +puts markdown.to_html +``` + +``` +No language indicated, so no syntax highlighting. +s = "There is no highlighting for this." +But let's throw in a tag. +``` + +## Inline Diff + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#inline-diff + +With inline diffs tags you can display {+ additions +} or [- deletions -]. + +The wrapping tags can be either curly braces or square brackets [+ additions +] or {- deletions -}. + +However the wrapping tags cannot be mixed as such: + +- {+ additions +] +- [+ additions +} +- {- deletions -] +- [- deletions -} + +## Emoji + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#emoji + + Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: + + :zap: You can use emoji anywhere GFM is supported. :v: + + You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. + + If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. + + Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup: + +Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: + +:zap: You can use emoji anywhere GFM is supported. :v: + +You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. + +If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. + +Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup: + +## Special GitLab References + +GFM recognizes special references. + +You can easily reference e.g. an issue, a commit, a team member or even the whole team within a project. + +GFM will turn that reference into a link so you can navigate between them easily. + +GFM will recognize the following: + +| input | references | +|:-----------------------|:--------------------------- | +| `@user_name` | specific user | +| `@group_name` | specific group | +| `@all` | entire team | +| `#123` | issue | +| `!123` | merge request | +| `$123` | snippet | +| `~123` | label by ID | +| `~bug` | one-word label by name | +| `~"feature request"` | multi-word label by name | +| `%123` | milestone by ID | +| `%v1.23` | one-word milestone by name | +| `%"release candidate"` | multi-word milestone by name | +| `9ba12248` | specific commit | +| `9ba12248...b19a04f5` | commit range comparison | +| `[README](doc/README)` | repository file references | + +GFM also recognizes certain cross-project references: + +| input | references | +|:----------------------------------------|:------------------------| +| `namespace/project#123` | issue | +| `namespace/project!123` | merge request | +| `namespace/project%123` | milestone | +| `namespace/project$123` | snippet | +| `namespace/project@9ba12248` | specific commit | +| `namespace/project@9ba12248...b19a04f5` | commit range comparison | +| `namespace/project~"Some label"` | issues with given label | + +## Task Lists + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#task-lists + +You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so: + +```no-highlight +- [x] Completed task +- [ ] Incomplete task + - [ ] Sub-task 1 + - [x] Sub-task 2 + - [ ] Sub-task 3 +``` + +- [x] Completed task +- [ ] Incomplete task + - [ ] Sub-task 1 + - [x] Sub-task 2 + - [ ] Sub-task 3 + +Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes. + +## Videos + +> If this is not rendered correctly, see +https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#videos + +Image tags with a video extension are automatically converted to a video player. + +The valid video extensions are `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`. + + Here's a sample video: + + ![Sample Video](img/markdown_video.mp4) + +Here's a sample video: + +![Sample Video](img/markdown_video.mp4) + +# Standard Markdown + +## Headers + +```no-highlight +# H1 +## H2 +### H3 +#### H4 +##### H5 +###### H6 + +Alternatively, for H1 and H2, an underline-ish style: + +Alt-H1 +====== + +Alt-H2 +------ +``` + +# H1 +## H2 +### H3 +#### H4 +##### H5 +###### H6 + +Alternatively, for H1 and H2, an underline-ish style: + +Alt-H1 +====== + +Alt-H2 +------ + +### Header IDs and links + +All Markdown-rendered headers automatically get IDs, except in comments. + +On hover a link to those IDs becomes visible to make it easier to copy the link to the header to give it to someone else. + +The IDs are generated from the content of the header according to the following rules: + +1. All text is converted to lowercase +1. All non-word text (e.g., punctuation, HTML) is removed +1. All spaces are converted to hyphens +1. Two or more hyphens in a row are converted to one +1. If a header with the same ID has already been generated, a unique + incrementing number is appended, starting at 1. + +For example: + +``` +# This header has spaces in it +## This header has a :thumbsup: in it +# This header has Unicode in it: 한글 +## This header has spaces in it +### This header has spaces in it +``` + +Would generate the following link IDs: + +1. `this-header-has-spaces-in-it` +1. `this-header-has-a-in-it` +1. `this-header-has-unicode-in-it-한글` +1. `this-header-has-spaces-in-it` +1. `this-header-has-spaces-in-it-1` + +Note that the Emoji processing happens before the header IDs are generated, so the Emoji is converted to an image which then gets removed from the ID. + +## Emphasis + +```no-highlight +Emphasis, aka italics, with *asterisks* or _underscores_. + +Strong emphasis, aka bold, with **asterisks** or __underscores__. + +Combined emphasis with **asterisks and _underscores_**. + +Strikethrough uses two tildes. ~~Scratch this.~~ +``` + +Emphasis, aka italics, with *asterisks* or _underscores_. + +Strong emphasis, aka bold, with **asterisks** or __underscores__. + +Combined emphasis with **asterisks and _underscores_**. + +Strikethrough uses two tildes. ~~Scratch this.~~ + +## Lists + +```no-highlight +1. First ordered list item +2. Another item + * Unordered sub-list. +1. Actual numbers don't matter, just that it's a number + 1. Ordered sub-list +4. And another item. + +* Unordered list can use asterisks +- Or minuses ++ Or pluses +``` + +1. First ordered list item +2. Another item + * Unordered sub-list. +1. Actual numbers don't matter, just that it's a number + 1. Ordered sub-list +4. And another item. + +* Unordered list can use asterisks +- Or minuses ++ Or pluses + +If a list item contains multiple paragraphs, +each subsequent paragraph should be indented with four spaces. + +```no-highlight +1. First ordered list item + + Second paragraph of first item. +2. Another item +``` + +1. First ordered list item + + Second paragraph of first item. +2. Another item + +If the second paragraph isn't indented with four spaces, +the second list item will be incorrectly labeled as `1`. + +```no-highlight +1. First ordered list item + + Second paragraph of first item. +2. Another item +``` + +1. First ordered list item + + Second paragraph of first item. +2. Another item + +## Links + +There are two ways to create links, inline-style and reference-style. + + [I'm an inline-style link](https://www.google.com) + + [I'm a reference-style link][Arbitrary case-insensitive reference text] + + [I'm a relative reference to a repository file](LICENSE) + + [You can use numbers for reference-style link definitions][1] + + Or leave it empty and use the [link text itself][] + + Some text to show that the reference links can follow later. + + [arbitrary case-insensitive reference text]: https://www.mozilla.org + [1]: http://slashdot.org + [link text itself]: https://www.reddit.com + +[I'm an inline-style link](https://www.google.com) + +[I'm a reference-style link][Arbitrary case-insensitive reference text] + +[I'm a relative reference to a repository file](LICENSE)[^1] + +[You can use numbers for reference-style link definitions][1] + +Or leave it empty and use the [link text itself][] + +Some text to show that the reference links can follow later. + +[arbitrary case-insensitive reference text]: https://www.mozilla.org +[1]: http://slashdot.org +[link text itself]: https://www.reddit.com + +**Note** + +Relative links do not allow referencing project files in a wiki page or wiki page in a project file. The reason for this is that, in GitLab, wiki is always a separate git repository. For example: + +`[I'm a reference-style link](style)` + +will point the link to `wikis/style` when the link is inside of a wiki markdown file. + +## Images + + Here's our logo (hover to see the title text): + + Inline-style: + ![alt text](img/markdown_logo.png) + + Reference-style: + ![alt text1][logo] + + [logo]: img/markdown_logo.png + +Here's our logo: + +Inline-style: + +![alt text](img/markdown_logo.png) + +Reference-style: + +![alt text][logo] + +[logo]: img/markdown_logo.png + +## Blockquotes + +```no-highlight +> Blockquotes are very handy in email to emulate reply text. +> This line is part of the same quote. + +Quote break. + +> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. +``` + +> Blockquotes are very handy in email to emulate reply text. +> This line is part of the same quote. + +Quote break. + +> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. + +## Inline HTML + +You can also use raw HTML in your Markdown, and it'll mostly work pretty well. + +See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes. In addition to the default `SanitizationFilter` whitelist, GitLab allows `span` elements. + +```no-highlight +
+
Definition list
+
Is something people use sometimes.
+ +
Markdown in HTML
+
Does *not* work **very** well. Use HTML tags.
+
+``` + +
+
Definition list
+
Is something people use sometimes.
+ +
Markdown in HTML
+
Does *not* work **very** well. Use HTML tags.
+
+ +## Horizontal Rule + +``` +Three or more... + +--- + +Hyphens + +*** + +Asterisks + +___ + +Underscores +``` + +Three or more... + +--- + +Hyphens + +*** + +Asterisks + +___ + +Underscores + +## Line Breaks + +My basic recommendation for learning how line breaks work is to experiment and discover -- hit <Enter> once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. "Markdown Toggle" is your friend. + +Here are some things to try out: + +``` +Here's a line for us to start with. + +This line is separated from the one above by two newlines, so it will be a *separate paragraph*. + +This line is also a separate paragraph, but... +This line is only separated by a single newline, so it's a separate line in the *same paragraph*. + +This line is also a separate paragraph, and... +This line is on its own line, because the previous line ends with two +spaces. +``` + +Here's a line for us to start with. + +This line is separated from the one above by two newlines, so it will be a *separate paragraph*. + +This line is also begins a separate paragraph, but... +This line is only separated by a single newline, so it's a separate line in the *same paragraph*. + +This line is also a separate paragraph, and... +This line is on its own line, because the previous line ends with two +spaces. + +## Tables + +Tables aren't part of the core Markdown spec, but they are part of GFM and Markdown Here supports them. + +``` +| header 1 | header 2 | +| -------- | -------- | +| cell 1 | cell 2 | +| cell 3 | cell 4 | +``` + +Code above produces next output: + +| header 1 | header 2 | +| -------- | -------- | +| cell 1 | cell 2 | +| cell 3 | cell 4 | + +**Note** + +The row of dashes between the table header and body must have at least three dashes in each column. + +By including colons in the header row, you can align the text within that column: + +``` +| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned | +| :----------- | :------: | ------------: | :----------- | :------: | ------------: | +| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | +| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | +``` + +| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned | +| :----------- | :------: | ------------: | :----------- | :------: | ------------: | +| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | +| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | + + +## Wiki-specific Markdown + +The following examples show how links inside wikis behave. + +### Wiki - Direct page link + +A link which just includes the slug for a page will point to that page, +_at the base level of the wiki_. + +This snippet would link to a `documentation` page at the root of your wiki: + +```markdown +[Link to Documentation](documentation) +``` + +### Wiki - Direct file link + +Links with a file extension point to that file, _relative to the current page_. + +If this snippet was placed on a page at `/documentation/related`, +it would link to `/documentation/file.md`: + +```markdown +[Link to File](file.md) +``` + +### Wiki - Hierarchical link + +A link can be constructed relative to the current wiki page using `./`, +`../`, etc. + +- If this snippet was placed on a page at `/documentation/main`, + it would link to `/documentation/related`: + + ```markdown + [Link to Related Page](./related) + ``` + +- If this snippet was placed on a page at `/documentation/related/content`, + it would link to `/documentation/main`: + + ```markdown + [Link to Related Page](../main) + ``` + +- If this snippet was placed on a page at `/documentation/main`, + it would link to `/documentation/related.md`: + + ```markdown + [Link to Related Page](./related.md) + ``` + +- If this snippet was placed on a page at `/documentation/related/content`, + it would link to `/documentation/main.md`: + + ```markdown + [Link to Related Page](../main.md) + ``` + +### Wiki - Root link + +A link starting with a `/` is relative to the wiki root. + +- This snippet links to `/documentation`: + + ```markdown + [Link to Related Page](/documentation) + ``` + +- This snippet links to `/miscellaneous.md`: + + ```markdown + [Link to Related Page](/miscellaneous.md) + ``` +## References + +- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). +- The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown. +- [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown. + +[markdown.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md +[rouge]: http://rouge.jneen.net/ "Rouge website" +[redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website" +[^1]: This link will be broken if you see this document from the Help page or docs.gitlab.com diff --git a/doc/user/project/img/markdown_logo.png b/doc/user/project/img/markdown_logo.png deleted file mode 100644 index 05c8b0d0ccf..00000000000 Binary files a/doc/user/project/img/markdown_logo.png and /dev/null differ diff --git a/doc/user/project/img/markdown_video.mp4 b/doc/user/project/img/markdown_video.mp4 deleted file mode 100644 index 1fc478842f5..00000000000 Binary files a/doc/user/project/img/markdown_video.mp4 and /dev/null differ diff --git a/doc/user/project/markdown.md b/doc/user/project/markdown.md deleted file mode 100644 index 7fe96e67dbb..00000000000 --- a/doc/user/project/markdown.md +++ /dev/null @@ -1,786 +0,0 @@ -# Markdown - -## Table of Contents - -**[GitLab Flavored Markdown](#gitlab-flavored-markdown-gfm)** - -* [Newlines](#newlines) -* [Multiple underscores in words](#multiple-underscores-in-words) -* [URL auto-linking](#url-auto-linking) -* [Multiline Blockquote](#multiline-blockquote) -* [Code and Syntax Highlighting](#code-and-syntax-highlighting) -* [Inline Diff](#inline-diff) -* [Emoji](#emoji) -* [Special GitLab references](#special-gitlab-references) -* [Task Lists](#task-lists) -* [Videos](#videos) - -**[Standard Markdown](#standard-markdown)** - -* [Headers](#headers) -* [Emphasis](#emphasis) -* [Lists](#lists) -* [Links](#links) -* [Images](#images) -* [Blockquotes](#blockquotes) -* [Inline HTML](#inline-html) -* [Horizontal Rule](#horizontal-rule) -* [Line Breaks](#line-breaks) -* [Tables](#tables) - -**[Wiki-Specific Markdown](#wiki-specific-markdown)** - -* [Wiki - Direct page link](#wiki-direct-page-link) -* [Wiki - Direct file link](#wiki-direct-file-link) -* [Wiki - Hierarchical link](#wiki-hierarchical-link) -* [Wiki - Root link](#wiki-root-link) - -**[References](#references)** - -## GitLab Flavored Markdown (GFM) - -> **Note:** -Not all of the GitLab-specific extensions to Markdown that are described in -this document currently work on our documentation website. -> -For the best result, we encourage you to check this document out as rendered -by GitLab: [markdown.md] - -_GitLab uses the [Redcarpet Ruby library][redcarpet] for Markdown processing._ - -GitLab uses "GitLab Flavored Markdown" (GFM). It extends the standard Markdown in a few significant ways to add some useful functionality. It was inspired by [GitHub Flavored Markdown](https://help.github.com/articles/basic-writing-and-formatting-syntax/). - -You can use GFM in the following areas: - -- comments -- issues -- merge requests -- milestones -- snippets (the snippet must be named with a `.md` extension) -- wiki pages -- markdown documents inside the repository - -You can also use other rich text files in GitLab. You might have to install a -dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information. - -## Newlines - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#newlines - -GFM honors the markdown specification in how [paragraphs and line breaks are handled](https://daringfireball.net/projects/markdown/syntax#p). - -A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. -Line-breaks, or softreturns, are rendered if you end a line with two or more spaces: - - Roses are red [followed by two or more spaces] - Violets are blue - - Sugar is sweet - -Roses are red -Violets are blue - -Sugar is sweet - -## Multiple underscores in words - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiple-underscores-in-words - -It is not reasonable to italicize just _part_ of a word, especially when you're dealing with code and names that often appear with multiple underscores. Therefore, GFM ignores multiple underscores in words: - - perform_complicated_task - - do_this_and_do_that_and_another_thing - -perform_complicated_task - -do_this_and_do_that_and_another_thing - -## URL auto-linking - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#url-auto-linking - -GFM will autolink almost any URL you copy and paste into your text: - - * https://www.google.com - * https://google.com/ - * ftp://ftp.us.debian.org/debian/ - * smb://foo/bar/baz - * irc://irc.freenode.net/gitlab - * http://localhost:3000 - -* https://www.google.com -* https://google.com/ -* ftp://ftp.us.debian.org/debian/ -* smb://foo/bar/baz -* irc://irc.freenode.net/gitlab -* http://localhost:3000 - -## Multiline Blockquote - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#multiline-blockquote - -On top of standard Markdown [blockquotes](#blockquotes), which require prepending `>` to quoted lines, -GFM supports multiline blockquotes fenced by >>>: - -```no-highlight ->>> -If you paste a message from somewhere else - -that - -spans - -multiple lines, - -you can quote that without having to manually prepend `>` to every line! ->>> -``` - ->>> -If you paste a message from somewhere else - -that - -spans - -multiple lines, - -you can quote that without having to manually prepend `>` to every line! ->>> - -## Code and Syntax Highlighting - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#code-and-syntax-highlighting - -_GitLab uses the [Rouge Ruby library][rouge] for syntax highlighting. For a -list of supported languages visit the Rouge website._ - -Blocks of code are either fenced by lines with three back-ticks ```, -or are indented with four spaces. Only the fenced code blocks support syntax -highlighting: - -```no-highlight -Inline `code` has `back-ticks around` it. -``` - -Inline `code` has `back-ticks around` it. - -Example: - - ```javascript - var s = "JavaScript syntax highlighting"; - alert(s); - ``` - - ```python - def function(): - #indenting works just fine in the fenced code block - s = "Python syntax highlighting" - print s - ``` - - ```ruby - require 'redcarpet' - markdown = Redcarpet.new("Hello World!") - puts markdown.to_html - ``` - - ``` - No language indicated, so no syntax highlighting. - s = "There is no highlighting for this." - But let's throw in a tag. - ``` - -becomes: - -```javascript -var s = "JavaScript syntax highlighting"; -alert(s); -``` - -```python -def function(): - #indenting works just fine in the fenced code block - s = "Python syntax highlighting" - print s -``` - -```ruby -require 'redcarpet' -markdown = Redcarpet.new("Hello World!") -puts markdown.to_html -``` - -``` -No language indicated, so no syntax highlighting. -s = "There is no highlighting for this." -But let's throw in a tag. -``` - -## Inline Diff - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#inline-diff - -With inline diffs tags you can display {+ additions +} or [- deletions -]. - -The wrapping tags can be either curly braces or square brackets [+ additions +] or {- deletions -}. - -However the wrapping tags cannot be mixed as such: - -- {+ additions +] -- [+ additions +} -- {- deletions -] -- [- deletions -} - -## Emoji - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#emoji - - Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: - - :zap: You can use emoji anywhere GFM is supported. :v: - - You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. - - If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. - - Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup: - -Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: - -:zap: You can use emoji anywhere GFM is supported. :v: - -You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. - -If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. - -Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup: - -## Special GitLab References - -GFM recognizes special references. - -You can easily reference e.g. an issue, a commit, a team member or even the whole team within a project. - -GFM will turn that reference into a link so you can navigate between them easily. - -GFM will recognize the following: - -| input | references | -|:-----------------------|:--------------------------- | -| `@user_name` | specific user | -| `@group_name` | specific group | -| `@all` | entire team | -| `#123` | issue | -| `!123` | merge request | -| `$123` | snippet | -| `~123` | label by ID | -| `~bug` | one-word label by name | -| `~"feature request"` | multi-word label by name | -| `%123` | milestone by ID | -| `%v1.23` | one-word milestone by name | -| `%"release candidate"` | multi-word milestone by name | -| `9ba12248` | specific commit | -| `9ba12248...b19a04f5` | commit range comparison | -| `[README](doc/README)` | repository file references | - -GFM also recognizes certain cross-project references: - -| input | references | -|:----------------------------------------|:------------------------| -| `namespace/project#123` | issue | -| `namespace/project!123` | merge request | -| `namespace/project%123` | milestone | -| `namespace/project$123` | snippet | -| `namespace/project@9ba12248` | specific commit | -| `namespace/project@9ba12248...b19a04f5` | commit range comparison | -| `namespace/project~"Some label"` | issues with given label | - -## Task Lists - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#task-lists - -You can add task lists to issues, merge requests and comments. To create a task list, add a specially-formatted Markdown list, like so: - -```no-highlight -- [x] Completed task -- [ ] Incomplete task - - [ ] Sub-task 1 - - [x] Sub-task 2 - - [ ] Sub-task 3 -``` - -- [x] Completed task -- [ ] Incomplete task - - [ ] Sub-task 1 - - [x] Sub-task 2 - - [ ] Sub-task 3 - -Task lists can only be created in descriptions, not in titles. Task item state can be managed by editing the description's Markdown or by toggling the rendered check boxes. - -## Videos - -> If this is not rendered correctly, see -https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md#videos - -Image tags with a video extension are automatically converted to a video player. - -The valid video extensions are `.mp4`, `.m4v`, `.mov`, `.webm`, and `.ogv`. - - Here's a sample video: - - ![Sample Video](img/markdown_video.mp4) - -Here's a sample video: - -![Sample Video](img/markdown_video.mp4) - -# Standard Markdown - -## Headers - -```no-highlight -# H1 -## H2 -### H3 -#### H4 -##### H5 -###### H6 - -Alternatively, for H1 and H2, an underline-ish style: - -Alt-H1 -====== - -Alt-H2 ------- -``` - -# H1 -## H2 -### H3 -#### H4 -##### H5 -###### H6 - -Alternatively, for H1 and H2, an underline-ish style: - -Alt-H1 -====== - -Alt-H2 ------- - -### Header IDs and links - -All Markdown-rendered headers automatically get IDs, except in comments. - -On hover a link to those IDs becomes visible to make it easier to copy the link to the header to give it to someone else. - -The IDs are generated from the content of the header according to the following rules: - -1. All text is converted to lowercase -1. All non-word text (e.g., punctuation, HTML) is removed -1. All spaces are converted to hyphens -1. Two or more hyphens in a row are converted to one -1. If a header with the same ID has already been generated, a unique - incrementing number is appended, starting at 1. - -For example: - -``` -# This header has spaces in it -## This header has a :thumbsup: in it -# This header has Unicode in it: 한글 -## This header has spaces in it -### This header has spaces in it -``` - -Would generate the following link IDs: - -1. `this-header-has-spaces-in-it` -1. `this-header-has-a-in-it` -1. `this-header-has-unicode-in-it-한글` -1. `this-header-has-spaces-in-it` -1. `this-header-has-spaces-in-it-1` - -Note that the Emoji processing happens before the header IDs are generated, so the Emoji is converted to an image which then gets removed from the ID. - -## Emphasis - -```no-highlight -Emphasis, aka italics, with *asterisks* or _underscores_. - -Strong emphasis, aka bold, with **asterisks** or __underscores__. - -Combined emphasis with **asterisks and _underscores_**. - -Strikethrough uses two tildes. ~~Scratch this.~~ -``` - -Emphasis, aka italics, with *asterisks* or _underscores_. - -Strong emphasis, aka bold, with **asterisks** or __underscores__. - -Combined emphasis with **asterisks and _underscores_**. - -Strikethrough uses two tildes. ~~Scratch this.~~ - -## Lists - -```no-highlight -1. First ordered list item -2. Another item - * Unordered sub-list. -1. Actual numbers don't matter, just that it's a number - 1. Ordered sub-list -4. And another item. - -* Unordered list can use asterisks -- Or minuses -+ Or pluses -``` - -1. First ordered list item -2. Another item - * Unordered sub-list. -1. Actual numbers don't matter, just that it's a number - 1. Ordered sub-list -4. And another item. - -* Unordered list can use asterisks -- Or minuses -+ Or pluses - -If a list item contains multiple paragraphs, -each subsequent paragraph should be indented with four spaces. - -```no-highlight -1. First ordered list item - - Second paragraph of first item. -2. Another item -``` - -1. First ordered list item - - Second paragraph of first item. -2. Another item - -If the second paragraph isn't indented with four spaces, -the second list item will be incorrectly labeled as `1`. - -```no-highlight -1. First ordered list item - - Second paragraph of first item. -2. Another item -``` - -1. First ordered list item - - Second paragraph of first item. -2. Another item - -## Links - -There are two ways to create links, inline-style and reference-style. - - [I'm an inline-style link](https://www.google.com) - - [I'm a reference-style link][Arbitrary case-insensitive reference text] - - [I'm a relative reference to a repository file](LICENSE) - - [You can use numbers for reference-style link definitions][1] - - Or leave it empty and use the [link text itself][] - - Some text to show that the reference links can follow later. - - [arbitrary case-insensitive reference text]: https://www.mozilla.org - [1]: http://slashdot.org - [link text itself]: https://www.reddit.com - -[I'm an inline-style link](https://www.google.com) - -[I'm a reference-style link][Arbitrary case-insensitive reference text] - -[I'm a relative reference to a repository file](LICENSE)[^1] - -[You can use numbers for reference-style link definitions][1] - -Or leave it empty and use the [link text itself][] - -Some text to show that the reference links can follow later. - -[arbitrary case-insensitive reference text]: https://www.mozilla.org -[1]: http://slashdot.org -[link text itself]: https://www.reddit.com - -**Note** - -Relative links do not allow referencing project files in a wiki page or wiki page in a project file. The reason for this is that, in GitLab, wiki is always a separate git repository. For example: - -`[I'm a reference-style link](style)` - -will point the link to `wikis/style` when the link is inside of a wiki markdown file. - -## Images - - Here's our logo (hover to see the title text): - - Inline-style: - ![alt text](img/markdown_logo.png) - - Reference-style: - ![alt text1][logo] - - [logo]: img/markdown_logo.png - -Here's our logo: - -Inline-style: - -![alt text](img/markdown_logo.png) - -Reference-style: - -![alt text][logo] - -[logo]: img/markdown_logo.png - -## Blockquotes - -```no-highlight -> Blockquotes are very handy in email to emulate reply text. -> This line is part of the same quote. - -Quote break. - -> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. -``` - -> Blockquotes are very handy in email to emulate reply text. -> This line is part of the same quote. - -Quote break. - -> This is a very long line that will still be quoted properly when it wraps. Oh boy let's keep writing to make sure this is long enough to actually wrap for everyone. Oh, you can *put* **Markdown** into a blockquote. - -## Inline HTML - -You can also use raw HTML in your Markdown, and it'll mostly work pretty well. - -See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes. In addition to the default `SanitizationFilter` whitelist, GitLab allows `span` elements. - -```no-highlight -
-
Definition list
-
Is something people use sometimes.
- -
Markdown in HTML
-
Does *not* work **very** well. Use HTML tags.
-
-``` - -
-
Definition list
-
Is something people use sometimes.
- -
Markdown in HTML
-
Does *not* work **very** well. Use HTML tags.
-
- -## Horizontal Rule - -``` -Three or more... - ---- - -Hyphens - -*** - -Asterisks - -___ - -Underscores -``` - -Three or more... - ---- - -Hyphens - -*** - -Asterisks - -___ - -Underscores - -## Line Breaks - -My basic recommendation for learning how line breaks work is to experiment and discover -- hit <Enter> once (i.e., insert one newline), then hit it twice (i.e., insert two newlines), see what happens. You'll soon learn to get what you want. "Markdown Toggle" is your friend. - -Here are some things to try out: - -``` -Here's a line for us to start with. - -This line is separated from the one above by two newlines, so it will be a *separate paragraph*. - -This line is also a separate paragraph, but... -This line is only separated by a single newline, so it's a separate line in the *same paragraph*. - -This line is also a separate paragraph, and... -This line is on its own line, because the previous line ends with two -spaces. -``` - -Here's a line for us to start with. - -This line is separated from the one above by two newlines, so it will be a *separate paragraph*. - -This line is also begins a separate paragraph, but... -This line is only separated by a single newline, so it's a separate line in the *same paragraph*. - -This line is also a separate paragraph, and... -This line is on its own line, because the previous line ends with two -spaces. - -## Tables - -Tables aren't part of the core Markdown spec, but they are part of GFM and Markdown Here supports them. - -``` -| header 1 | header 2 | -| -------- | -------- | -| cell 1 | cell 2 | -| cell 3 | cell 4 | -``` - -Code above produces next output: - -| header 1 | header 2 | -| -------- | -------- | -| cell 1 | cell 2 | -| cell 3 | cell 4 | - -**Note** - -The row of dashes between the table header and body must have at least three dashes in each column. - -By including colons in the header row, you can align the text within that column: - -``` -| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned | -| :----------- | :------: | ------------: | :----------- | :------: | ------------: | -| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | -| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | -``` - -| Left Aligned | Centered | Right Aligned | Left Aligned | Centered | Right Aligned | -| :----------- | :------: | ------------: | :----------- | :------: | ------------: | -| Cell 1 | Cell 2 | Cell 3 | Cell 4 | Cell 5 | Cell 6 | -| Cell 7 | Cell 8 | Cell 9 | Cell 10 | Cell 11 | Cell 12 | - - -## Wiki-specific Markdown - -The following examples show how links inside wikis behave. - -### Wiki - Direct page link - -A link which just includes the slug for a page will point to that page, -_at the base level of the wiki_. - -This snippet would link to a `documentation` page at the root of your wiki: - -```markdown -[Link to Documentation](documentation) -``` - -### Wiki - Direct file link - -Links with a file extension point to that file, _relative to the current page_. - -If this snippet was placed on a page at `/documentation/related`, -it would link to `/documentation/file.md`: - -```markdown -[Link to File](file.md) -``` - -### Wiki - Hierarchical link - -A link can be constructed relative to the current wiki page using `./`, -`../`, etc. - -- If this snippet was placed on a page at `/documentation/main`, - it would link to `/documentation/related`: - - ```markdown - [Link to Related Page](./related) - ``` - -- If this snippet was placed on a page at `/documentation/related/content`, - it would link to `/documentation/main`: - - ```markdown - [Link to Related Page](../main) - ``` - -- If this snippet was placed on a page at `/documentation/main`, - it would link to `/documentation/related.md`: - - ```markdown - [Link to Related Page](./related.md) - ``` - -- If this snippet was placed on a page at `/documentation/related/content`, - it would link to `/documentation/main.md`: - - ```markdown - [Link to Related Page](../main.md) - ``` - -### Wiki - Root link - -A link starting with a `/` is relative to the wiki root. - -- This snippet links to `/documentation`: - - ```markdown - [Link to Related Page](/documentation) - ``` - -- This snippet links to `/miscellaneous.md`: - - ```markdown - [Link to Related Page](/miscellaneous.md) - ``` -## References - -- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). -- The [Markdown Syntax Guide](https://daringfireball.net/projects/markdown/syntax) at Daring Fireball is an excellent resource for a detailed explanation of standard markdown. -- [Dillinger.io](http://dillinger.io) is a handy tool for testing standard markdown. - -[markdown.md]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/markdown/markdown.md -[rouge]: http://rouge.jneen.net/ "Rouge website" -[redcarpet]: https://github.com/vmg/redcarpet "Redcarpet website" -[^1]: This link will be broken if you see this document from the Help page or docs.gitlab.com -- cgit v1.2.1 From b1b32bfcf8c28e038a7450238a4109288e1146ed Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 3 Aug 2016 09:31:36 +0100 Subject: Fixed issue with `this` not being defined --- app/assets/javascripts/gl_dropdown.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index a9a7c802f87..356810c85cc 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -60,8 +60,8 @@ } return this.options.query(this.input.val(), function(data) { return this.options.callback(data); - }); - }, 250); + }.bind(this)); + }.bind(this), 250); } else { return this.filter(this.input.val()); } -- cgit v1.2.1 From dd35c3ddf6dce7a69cc116fe6165dad68b8e9251 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 2 Aug 2016 17:51:17 +0200 Subject: Improve AutolinkFilter#text_parse performance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By using clever XPath queries we can quite significantly improve the performance of this method. The actual improvement depends a bit on the amount of links used but in my tests the new implementation is usually around 8 times faster than the old one. This was measured using the following benchmark: require 'benchmark/ips' text = '

' + Note.select("string_agg(note, '') AS note").limit(50).take[:note] + '

' document = Nokogiri::HTML.fragment(text) filter = Banzai::Filter::AutolinkFilter.new(document, autolink: true) puts "Input size: #{(text.bytesize.to_f / 1024 / 1024).round(2)} MB" filter.rinku_parse Benchmark.ips(time: 15) do |bench| bench.report 'text_parse' do filter.text_parse end bench.report 'text_parse_fast' do filter.text_parse_fast end bench.compare! end Here the "text_parse_fast" method is the new implementation and "text_parse" the old one. The input size was around 180 MB. Running this benchmark outputs the following: Input size: 181.16 MB Calculating ------------------------------------- text_parse 1.000 i/100ms text_parse_fast 9.000 i/100ms ------------------------------------------------- text_parse 13.021 (±15.4%) i/s - 188.000 text_parse_fast 112.741 (± 3.5%) i/s - 1.692k Comparison: text_parse_fast: 112.7 i/s text_parse: 13.0 i/s - 8.66x slower Again the production timings may (and most likely will) vary depending on the input being processed. --- CHANGELOG | 1 + lib/banzai/filter/autolink_filter.rb | 15 +++++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c099c63ce86..47e959dea68 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ v 8.11.0 (unreleased) - Add support for using RequestStore within Sidekiq tasks via SIDEKIQ_REQUEST_STORE env variable - Optimize maximum user access level lookup in loading of notes - Add "No one can push" as an option for protected branches. !5081 + - Improve performance of AutolinkFilter#text_parse by using XPath - Environments have an url to link to - Limit git rev-list output count to one in forced push check - Clean up unused routes (Josef Strzibny) diff --git a/lib/banzai/filter/autolink_filter.rb b/lib/banzai/filter/autolink_filter.rb index 9ed45707515..799b83b1069 100644 --- a/lib/banzai/filter/autolink_filter.rb +++ b/lib/banzai/filter/autolink_filter.rb @@ -31,6 +31,14 @@ module Banzai # Text matching LINK_PATTERN inside these elements will not be linked IGNORE_PARENTS = %w(a code kbd pre script style).to_set + # The XPath query to use for finding text nodes to parse. + TEXT_QUERY = %Q(descendant-or-self::text()[ + not(#{IGNORE_PARENTS.map { |p| "ancestor::#{p}" }.join(' or ')}) + and contains(., '://') + and not(starts-with(., 'http')) + and not(starts-with(., 'ftp')) + ]) + def call return doc if context[:autolink] == false @@ -66,16 +74,11 @@ module Banzai # Autolinks any text matching LINK_PATTERN that Rinku didn't already # replace def text_parse - search_text_nodes(doc).each do |node| + doc.xpath(TEXT_QUERY).each do |node| content = node.to_html - next if has_ancestor?(node, IGNORE_PARENTS) next unless content.match(LINK_PATTERN) - # If Rinku didn't link this, there's probably a good reason, so we'll - # skip it too - next if content.start_with?(*%w(http https ftp)) - html = autolink_filter(content) next if html == content -- cgit v1.2.1 From f87eb2502023fb0c17d29aef398058c62736c9ca Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 3 Aug 2016 13:00:34 +0200 Subject: Fix Import/Export error checking versions --- CHANGELOG | 3 +++ lib/gitlab/import_export/version_checker.rb | 2 +- .../gitlab/import_export/version_checker_spec.rb | 30 ++++++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 spec/lib/gitlab/import_export/version_checker_spec.rb diff --git a/CHANGELOG b/CHANGELOG index db2617dcbd7..b299337aef9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -56,6 +56,9 @@ v 8.11.0 (unreleased) - Fix RequestProfiler::Middleware error when code is reloaded in development - Catch what warden might throw when profiling requests to re-throw it +v 8.10.4 (unreleased) + - Fix Import/Export error checking versions + v 8.10.3 - Fix Import/Export issue importing milestones and labels not associated properly. !5426 - Fix timing problems running imports on production. !5523 diff --git a/lib/gitlab/import_export/version_checker.rb b/lib/gitlab/import_export/version_checker.rb index abfc694b879..de3fe6d822e 100644 --- a/lib/gitlab/import_export/version_checker.rb +++ b/lib/gitlab/import_export/version_checker.rb @@ -25,7 +25,7 @@ module Gitlab def verify_version!(version) if Gem::Version.new(version) > Gem::Version.new(Gitlab::ImportExport.version) - raise Gitlab::ImportExport::Error("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}") + raise Gitlab::ImportExport::Error.new("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}") else true end diff --git a/spec/lib/gitlab/import_export/version_checker_spec.rb b/spec/lib/gitlab/import_export/version_checker_spec.rb new file mode 100644 index 00000000000..90c6d1c67f6 --- /dev/null +++ b/spec/lib/gitlab/import_export/version_checker_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe Gitlab::ImportExport::VersionChecker, services: true do + describe 'bundle a project Git repo' do + let(:shared) { Gitlab::ImportExport::Shared.new(relative_path: '') } + let(:version) { Gitlab::ImportExport.version } + + before do + allow(File).to receive(:open).and_return(version) + end + + it 'returns true if Import/Export have the same version' do + expect(described_class.check!(shared: shared)).to be true + end + + context 'newer version' do + let(:version) { '900.0'} + + it 'returns false if export version is newer' do + expect(described_class.check!(shared: shared)).to be false + end + + it 'shows the correct error message' do + described_class.check!(shared: shared) + + expect(shared.errors.first).to eq("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}") + end + end + end +end -- cgit v1.2.1 From ff4407996baac8e766aa8912b0fd882427aa2183 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 3 Aug 2016 16:05:17 +0300 Subject: Add doc guidelines on documents naming and location [ci skip] --- doc/development/doc_styleguide.md | 65 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 60 insertions(+), 5 deletions(-) diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 3a3597bccaa..da6b7686427 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -3,12 +3,64 @@ This styleguide recommends best practices to improve documentation and to keep it organized and easy to find. -## Naming +## Location and naming of documents -- When creating a new document and it has more than one word in its name, - make sure to use underscores instead of spaces or dashes (`-`). For example, - a proper naming would be `import_projects_from_github.md`. The same rule - applies to images. +>**Note:** +These guidelines derive from the discussion taken place in issue [#3349](ce-3349). + +The documentation hierarchy can be vastly improved by providing a better layout +and organization of directories. + +Having a structured document layout, we will be able to have meaningful URLs +like `docs.gitlab.com/user/project/merge_requests.html`. With this pattern, +you can immediately tell that you are navigating a user related documentation +and is about the project and its merge requests. + +The table below shows what kind of documentation goes where. + +| Directory | What belongs here | +| --------- | -------------- | +| `doc/user/` | User related documentation. Anything that can be done within the GitLab UI goes here including `/admin`. | +| `doc/administration/` | Documentation that requires the user to have access to the server where GitLab is installed. The admin settings that can be accessed via GitLab's interface go under `doc/user/admin_area/`. | +| `doc/api/` | API related documentation. | +| `doc/development/` | Documentation related to the development of GitLab. Any styleguides should go here. | +| `doc/legal/` | Legal documents about contributing to GitLab. | +| `doc/install/`| Probably the most visited directory, since `installation.md` is there. Ideally this should go under `doc/administration/`, but it's best to leave it as-is in order to avoid confusion (still debated though). | +| `doc/update/` | Same with `doc/install/`. Should be under `administration/`, but this is a well known location, better leave as-is, at least for now. | + +--- + +**General rules:** + +1. The correct naming and location of a new document, is a combination + of the relative URL of the document in question and the GitLab Map design + that is used for UX purposes ([source][graffle], [image][gitlab-map]). +1. When creating a new document and it has more than one word in its name, + make sure to use underscores instead of spaces or dashes (`-`). For example, + a proper naming would be `import_projects_from_github.md`. The same rule + applies to images. +1. There are four main directories, `user`, `administration`, `api` and `development`. +1. The `doc/user/` directory has five main subdirectories: `project/`, `group/`, + `profile/`, `dashboard/` and `admin_area/`. + 1. `doc/user/project/` should contain all project related documentation. + 1. `doc/user/group/` should contain all group related documentation. + 1. `doc/user/profile/` should contain all profile related documentation. + Every page you would navigate under `/profile` should have its own document, + i.e. `account.md`, `applications.md`, `emails.md`, etc. + 1. `doc/user/dashboard/` should contain all dashboard related documentation. + 1. `doc/user/admin_area/` should contain all admin related documentation + describing what can be achieved by accessing GitLab's admin interface + (_not to be confused with `doc/administration` where server access is + required_). + 1. Every category under `/admin/application_settings` should have its + own document located at `doc/user/admin_area/settings/`. For example, + the **Visibility and Access Controls** category should have a document + located at `doc/user/admin_area/settings/visibility_and_access_controls.md`. + +--- + +If you are unsure where a document should live, you can ping `@axil` in your +merge request. ## Text @@ -372,3 +424,6 @@ curl -X PUT -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -d "domain_whitelist[]=*.ex [single spaces]: http://www.slate.com/articles/technology/technology/2011/01/space_invaders.html [gfm]: http://docs.gitlab.com/ce/markdown/markdown.html#newlines "GitLab flavored markdown documentation" [doc-restart]: ../administration/restart_gitlab.md "GitLab restart documentation" +[ce-3349]: https://gitlab.com/gitlab-org/gitlab-ce/issues/3349 "Documentation restructure" +[graffle]: https://gitlab.com/gitlab-org/gitlab-design/blob/d8d39f4a87b90fb9ae89ca12dc565347b4900d5e/production/resources/gitlab-map.graffle +[gitlab-map]: https://gitlab.com/gitlab-org/gitlab-design/raw/master/production/resources/gitlab-map.png -- cgit v1.2.1 From c91168c04a71301d8423b881c1219cfd510d5784 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Wed, 3 Aug 2016 17:33:37 +0300 Subject: Small refactor on Registry troubleshooting [ci skip] --- doc/container_registry/troubleshooting.md | 49 +++++++++++++++---------------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/doc/container_registry/troubleshooting.md b/doc/container_registry/troubleshooting.md index 8008bf29935..14c4a7d9a63 100644 --- a/doc/container_registry/troubleshooting.md +++ b/doc/container_registry/troubleshooting.md @@ -5,27 +5,27 @@ 1. Check to make sure that the system clock on your Docker client and GitLab server have been synchronized (e.g. via NTP). -2. If you are using an S3-backed registry, double check that the IAM +2. If you are using an S3-backed Registry, double check that the IAM permissions and the S3 credentials (including region) are correct. See [the sample IAM policy](https://docs.docker.com/registry/storage-drivers/s3/) for more details. -3. Check the registry logs (e.g. `/var/log/gitlab/registry/current`) and the GitLab production logs +3. Check the Registry logs (e.g. `/var/log/gitlab/registry/current`) and the GitLab production logs for errors (e.g. `/var/log/gitlab/gitlab-rails/production.log`). You may be able to find clues there. -# Advanced Troubleshooting +## Advanced Troubleshooting -NOTE: The following section is only recommended for experts. +>**NOTE:** The following section is only recommended for experts. Sometimes it's not obvious what is wrong, and you may need to dive deeper into -the communication between the Docker client and the registry to find out +the communication between the Docker client and the Registry to find out what's wrong. We will use a concrete example in the past to illustrate how to diagnose a problem with the S3 setup. -## Example: Unexpected 403 error during push +### Unexpected 403 error during push -A user attempted to enable an S3-backed registry. The `docker login` step went +A user attempted to enable an S3-backed Registry. The `docker login` step went fine. However, when pushing an image, the output showed: ``` @@ -44,11 +44,11 @@ error parsing HTTP 403 response body: unexpected end of JSON input: "" ``` This error is ambiguous, as it's not clear whether the 403 is coming from the -GitLab Rails application, the Docker registry, or something else. In this +GitLab Rails application, the Docker Registry, or something else. In this case, since we know that since the login succeeded, we probably need to look -at the communication between the client and the registry. +at the communication between the client and the Registry. -The REST API between the Docker client and registry is [described +The REST API between the Docker client and Registry is [described here](https://docs.docker.com/registry/spec/api/). Normally, one would just use Wireshark or tcpdump to capture the traffic and see where things went wrong. However, since all communication between Docker clients and servers @@ -56,12 +56,12 @@ are done over HTTPS, it's a bit difficult to decrypt the traffic quickly even if you know the private key. What can we do instead? One way would be to disable HTTPS by setting up an [insecure -registry](https://docs.docker.com/registry/insecure/). This could introduce a +Registry](https://docs.docker.com/registry/insecure/). This could introduce a security hole and is only recommended for local testing. If you have a production system and can't or don't want to do this, there is another way: use mitmproxy, which stands for Man-in-the-Middle Proxy. -## mitmproxy +### mitmproxy [mitmproxy](https://mitmproxy.org/) allows you to place a proxy between your client and server to inspect all traffic. One wrinkle is that your system @@ -70,10 +70,9 @@ needs to trust the mitmproxy SSL certificates for this to work. The following installation instructions assume you are running Ubuntu: 1. Install mitmproxy (see http://docs.mitmproxy.org/en/stable/install.html) - -2. Run `mitmproxy --port 9000` to generate its certificates. Enter CTRL-C to quit. - -3. Install the certificate from ~/.mitmproxy to your system: +1. Run `mitmproxy --port 9000` to generate its certificates. + Enter CTRL-C to quit. +1. Install the certificate from `~/.mitmproxy` to your system: ```sh sudo cp ~/.mitmproxy/mitmproxy-ca-cert.pem /usr/local/share/ca-certificates/mitmproxy-ca-cert.crt @@ -87,24 +86,22 @@ Updating certificates in /etc/ssl/certs... 1 added, 0 removed; done. Running hooks in /etc/ca-certificates/update.d....done. ``` -## Verifying mitmproxy certifiactes - -To verify that the certificates are properly install, run: +To verify that the certificates are properly installed, run: ```sh mitmproxy --port 9000 ``` -This will run mitmproxy on port 9000. In another window, run: +This will run mitmproxy on port `9000`. In another window, run: ```sh curl --proxy http://localhost:9000 https://httpbin.org/status/200 ``` -If everything is setup correctly, then you will see information on the mitmproxy window and +If everything is setup correctly, you will see information on the mitmproxy window and no errors from the curl commands. -## Running the Docker daemon with a proxy +### Running the Docker daemon with a proxy For Docker to connect through a proxy, you must start the Docker daemon with the proper environment variables. The easiest way is to shutdown Docker (e.g. `sudo initctl stop docker`) @@ -118,10 +115,10 @@ docker daemon --debug This will launch the Docker daemon and proxy all connections through mitmproxy. -## Running the Docker client +### Running the Docker client -Now that we have mitmproxy and Docker running, we can now attempt to login and push a container -image. You may need to run as root to do this. For example: +Now that we have mitmproxy and Docker running, we can attempt to login and push +a container image. You may need to run as root to do this. For example: ```sh docker login s3-testing.myregistry.com:4567 @@ -141,4 +138,4 @@ The above image shows: What does this mean? This strongly suggests that the S3 user does not have the right [permissions to perform a HEAD request](http://docs.aws.amazon.com/AmazonS3/latest/API/RESTObjectHEAD.html). The solution: check the [IAM permissions again](https://docs.docker.com/registry/storage-drivers/s3/). -Once the right permissions were set, the error went away. +Once the right permissions were set, the error will go away. -- cgit v1.2.1 From 038d6febedc03280bd686d929057c02d05f2afd6 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 3 Aug 2016 16:52:08 +0200 Subject: Improve performance of SyntaxHighlightFilter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit By using Rouge::Lexer.find instead of find_fancy() and memoizing the HTML formatter we can speed up the highlighting process by between 1.7 and 1.8 times (at least when measured using synthetic benchmarks). To measure this I used the following benchmark: require 'benchmark/ips' input = '' Dir['./app/controllers/**/*.rb'].each do |controller| input << <<-EOF
#{File.read(controller).strip}
EOF end document = Nokogiri::HTML.fragment(input) filter = Banzai::Filter::SyntaxHighlightFilter.new(document) puts "Input size: #{(input.bytesize.to_f / 1024).round(2)} KB" Benchmark.ips do |bench| bench.report 'call' do filter.call end end This benchmark produces 250 KB of input. Before these changes the timing output would be as follows: Calculating ------------------------------------- call 1.000 i/100ms ------------------------------------------------- call 22.439 (±35.7%) i/s - 93.000 After these changes the output instead is as follows: Calculating ------------------------------------- call 1.000 i/100ms ------------------------------------------------- call 41.283 (±38.8%) i/s - 148.000 Note that due to the fairly high standard deviation and this being a synthetic benchmark it's entirely possible the real-world improvements are smaller. --- CHANGELOG | 1 + lib/banzai/filter/syntax_highlight_filter.rb | 26 ++++++++++++++++++-------- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index c099c63ce86..a07b85ca27f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 8.11.0 (unreleased) - Limit git rev-list output count to one in forced push check - Clean up unused routes (Josef Strzibny) - Add green outline to New Branch button. !5447 (winniehell) + - Improve performance of syntax highlighting Markdown code blocks - Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects - Retrieve rendered HTML from cache in one request - Fix renaming repository when name contains invalid chararacters under project settings diff --git a/lib/banzai/filter/syntax_highlight_filter.rb b/lib/banzai/filter/syntax_highlight_filter.rb index 91f0159f9a1..fcdb496aed2 100644 --- a/lib/banzai/filter/syntax_highlight_filter.rb +++ b/lib/banzai/filter/syntax_highlight_filter.rb @@ -17,15 +17,12 @@ module Banzai def highlight_node(node) language = node.attr('class') - code = node.text - + code = node.text css_classes = "code highlight" - - lexer = Rouge::Lexer.find_fancy(language) || Rouge::Lexers::PlainText - formatter = Rouge::Formatters::HTML.new + lexer = lexer_for(language) begin - code = formatter.format(lexer.lex(code)) + code = format(lex(lexer, code)) css_classes << " js-syntax-highlight #{lexer.tag}" rescue @@ -41,14 +38,27 @@ module Banzai private + # Separate method so it can be instrumented. + def lex(lexer, code) + lexer.lex(code) + end + + def format(tokens) + rouge_formatter.format(tokens) + end + + def lexer_for(language) + (Rouge::Lexer.find(language) || Rouge::Lexers::PlainText).new + end + def replace_parent_pre_element(node, highlighted) # Replace the parent `pre` element with the entire highlighted block node.parent.replace(highlighted) end # Override Rouge::Plugins::Redcarpet#rouge_formatter - def rouge_formatter(lexer) - Rouge::Formatters::HTML.new + def rouge_formatter(lexer = nil) + @rouge_formatter ||= Rouge::Formatters::HTML.new end end end -- cgit v1.2.1 From 9a934e25011baa7b68239ce6fdbe010ab88cf86f Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 3 Aug 2016 17:08:41 +0200 Subject: Instrument Gitlab::Highlight This class does quite a few interesting things so let's instrument it so we can see how much time is being spent in this class. --- CHANGELOG | 1 + config/initializers/metrics.rb | 3 +++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 472faa05b75..d5029178fd8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.11.0 (unreleased) - Include old revision in merge request update hooks (Ben Boeckel) - Add build event color in HipChat messages (David Eisner) - Make fork counter always clickable. !5463 (winniehell) + - Gitlab::Highlight is now instrumented - All created issues, API or WebUI, can be submitted to Akismet for spam check !5333 - The overhead of instrumented method calls has been reduced - Remove `search_id` of labels dropdown filter to fix 'Missleading URI for labels in Merge Requests and Issues view'. !5368 (Scott Le) diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb index b68a09ce730..cc8208db3c1 100644 --- a/config/initializers/metrics.rb +++ b/config/initializers/metrics.rb @@ -145,6 +145,9 @@ if Gitlab::Metrics.enabled? config.instrument_methods(Rinku) config.instrument_instance_methods(Repository) + + config.instrument_methods(Gitlab::Highlight) + config.instrument_instance_methods(Gitlab::Highlight) end GC::Profiler.enable -- cgit v1.2.1 From c008a1a9674f7c01b4504e22ed414b07eff05385 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 3 Aug 2016 09:32:01 -0700 Subject: Make Compare#diffs diff_options a regular argument --- app/controllers/projects/compare_controller.rb | 4 ++-- app/models/compare.rb | 2 +- app/models/merge_request.rb | 2 +- lib/gitlab/email/message/repository_push.rb | 2 +- spec/models/merge_request_spec.rb | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 4a42a7d091b..bee3d56076c 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -21,7 +21,7 @@ class Projects::CompareController < Projects::ApplicationController def diff_for_path return render_404 unless @compare - render_diff_for_path(@compare.diffs(diff_options: diff_options)) + render_diff_for_path(@compare.diffs(diff_options)) end def create @@ -45,7 +45,7 @@ class Projects::CompareController < Projects::ApplicationController @commit = @compare.commit @base_commit = @compare.base_commit - @diffs = @compare.diffs(diff_options: diff_options) + @diffs = @compare.diffs(diff_options) @diff_notes_disabled = true @grouped_diff_discussions = {} diff --git a/app/models/compare.rb b/app/models/compare.rb index 98c042f3809..4856510f526 100644 --- a/app/models/compare.rb +++ b/app/models/compare.rb @@ -49,7 +49,7 @@ class Compare @compare.diffs(*args) end - def diffs(diff_options:) + def diffs(diff_options = nil) Gitlab::Diff::FileCollection::Compare.new(self, project: project, diff_options: diff_options, diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 009262d6b48..c4761fac2fb 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -170,7 +170,7 @@ class MergeRequest < ActiveRecord::Base def diffs(diff_options = nil) if self.compare - self.compare.diffs(diff_options: diff_options) + self.compare.diffs(diff_options) else Gitlab::Diff::FileCollection::MergeRequest.new(self, diff_options: diff_options) end diff --git a/lib/gitlab/email/message/repository_push.rb b/lib/gitlab/email/message/repository_push.rb index 62d29387d60..0e3b65fceb4 100644 --- a/lib/gitlab/email/message/repository_push.rb +++ b/lib/gitlab/email/message/repository_push.rb @@ -42,7 +42,7 @@ module Gitlab return unless compare # This diff is more moderated in number of files and lines - @diffs ||= compare.diffs(diff_options: { max_files: 30, max_lines: 5000, no_collapse: true }).diff_files + @diffs ||= compare.diffs(max_files: 30, max_lines: 5000, no_collapse: true).diff_files end def diffs_count diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 152e0cce5ad..e43008c1a4d 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -171,7 +171,7 @@ describe MergeRequest, models: true do it 'delegates to the compare object' do merge_request.compare = double(:compare) - expect(merge_request.compare).to receive(:diffs).with(diff_options: options) + expect(merge_request.compare).to receive(:diffs).with(options) merge_request.diffs(options) end -- cgit v1.2.1 From a16c26c957ae893f6957fd0ad66c189d0b8ca079 Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Fri, 29 Jul 2016 19:31:37 +0200 Subject: Speed up Commit#repo_changes --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 4 ++-- app/models/commit.rb | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 38e91fc3e98..8d0e377e31a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -57,6 +57,7 @@ v 8.11.0 (unreleased) - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) - Fix RequestProfiler::Middleware error when code is reloaded in development - Catch what warden might throw when profiling requests to re-throw it + - Speed up Commit#repo_changes v 8.10.3 - Fix Import/Export issue importing milestones and labels not associated properly. !5426 diff --git a/Gemfile b/Gemfile index 5f247abd2fc..16f24553ed1 100644 --- a/Gemfile +++ b/Gemfile @@ -53,7 +53,7 @@ gem 'browser', '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem 'gitlab_git', '~> 10.4.2' +gem 'gitlab_git', '~> 10.4.3' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes diff --git a/Gemfile.lock b/Gemfile.lock index 7b4175ea824..866f5014847 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -278,7 +278,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) - gitlab_git (10.4.2) + gitlab_git (10.4.3) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -870,7 +870,7 @@ DEPENDENCIES github-linguist (~> 4.7.0) github-markup (~> 1.4) gitlab-flowdock-git-hook (~> 1.0.1) - gitlab_git (~> 10.4.2) + gitlab_git (~> 10.4.3) gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) diff --git a/app/models/commit.rb b/app/models/commit.rb index d58c2fb8106..cc413448ce8 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -334,7 +334,7 @@ class Commit def repo_changes changes = { added: [], modified: [], removed: [] } - raw_diffs.each do |diff| + raw_diffs(deltas_only: true).each do |diff| if diff.deleted_file changes[:removed] << diff.old_path elsif diff.renamed_file || diff.new_file -- cgit v1.2.1 From 6eba7188f1cd1fc0bfcb8b1cf46f40338dc892b5 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 29 Jul 2016 13:46:39 -0700 Subject: Use only deltas in diffs when scanning the last commit for changes in the avatar to save memory --- CHANGELOG | 2 +- app/models/repository.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8d0e377e31a..864b8afaf7d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -57,7 +57,7 @@ v 8.11.0 (unreleased) - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) - Fix RequestProfiler::Middleware error when code is reloaded in development - Catch what warden might throw when profiling requests to re-throw it - - Speed up Commit#repo_changes + - Speed up and reduce memory usage of Commit#repo_changes and Repository#expire_avatar_cache v 8.10.3 - Fix Import/Export issue importing milestones and labels not associated properly. !5426 diff --git a/app/models/repository.rb b/app/models/repository.rb index 3d95344a68f..c1170c470ea 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -372,7 +372,7 @@ class Repository # We don't want to flush the cache if the commit didn't actually make any # changes to any of the possible avatar files. if revision && commit = self.commit(revision) - return unless commit.raw_diffs. + return unless commit.raw_diffs(deltas_only: true). any? { |diff| AVATAR_FILES.include?(diff.new_path) } end -- cgit v1.2.1 From 08c1dd348273df67bf14172e9082308e12f94784 Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Mon, 1 Aug 2016 13:14:41 +0200 Subject: Use commit deltas when counting files in IrkerWorker --- CHANGELOG | 2 +- app/workers/irker_worker.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 864b8afaf7d..25911e02ec6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -57,7 +57,7 @@ v 8.11.0 (unreleased) - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) - Fix RequestProfiler::Middleware error when code is reloaded in development - Catch what warden might throw when profiling requests to re-throw it - - Speed up and reduce memory usage of Commit#repo_changes and Repository#expire_avatar_cache + - Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker v 8.10.3 - Fix Import/Export issue importing milestones and labels not associated properly. !5426 diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb index 07cc7c1cbd7..19f38358eb5 100644 --- a/app/workers/irker_worker.rb +++ b/app/workers/irker_worker.rb @@ -141,7 +141,7 @@ class IrkerWorker end def files_count(commit) - diffs = commit.raw_diffs + diffs = commit.raw_diffs(deltas_only: true) files = "#{diffs.real_size} file" files += 's' if diffs.size > 1 -- cgit v1.2.1 From e8c6f119cd7cf519db3ad1622786aad3156324d4 Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Mon, 18 Jul 2016 15:14:30 -0600 Subject: Add an oauth provider path helper. The helper constructs the path for a given oauth provider since Devise 4.0 deprecated passing the provider to the omniauth authentication path. Fixes #18110. --- app/controllers/sessions_controller.rb | 2 +- app/helpers/auth_helper.rb | 6 ++++++ app/views/devise/sessions/_new_crowd.html.haml | 2 +- app/views/devise/shared/_omniauth_box.html.haml | 2 +- app/views/profiles/accounts/show.html.haml | 2 +- 5 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 17aed816cbd..25598e065b8 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -101,7 +101,7 @@ class SessionsController < Devise::SessionsController # Prevent alert from popping up on the first page shown after authentication. flash[:alert] = nil - redirect_to user_omniauth_authorize_path(provider.to_sym) + redirect_to provider_path(provider) end def valid_otp_attempt?(user) diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index cd4d778e508..dcc156311a1 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -60,6 +60,12 @@ module AuthHelper end end + # Constructs the OAuth provider path. + # For example: user_google_omniauth_authorize_path + def provider_path(provider) + send("user_#{provider.underscore}_omniauth_authorize_path") + end + def auth_active?(provider) current_user.identities.exists?(provider: provider.to_s) end diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml index 8e81671b7e7..6c7c89700cd 100644 --- a/app/views/devise/sessions/_new_crowd.html.haml +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -1,4 +1,4 @@ -= form_tag(user_omniauth_authorize_path("crowd"), id: 'new_crowd_user' ) do += form_tag(provider_path("crowd"), id: 'new_crowd_user' ) do = text_field_tag :username, nil, {class: "form-control top", placeholder: "Username", autofocus: "autofocus"} = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} - if devise_mapping.rememberable? diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index de18bc2d844..07d4caf6b94 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -5,4 +5,4 @@ - providers.each do |provider| %span.light - has_icon = provider_has_icon?(provider) - = link_to provider_image_tag(provider), user_omniauth_authorize_path(provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true" + = link_to provider_image_tag(provider), provider_path(provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true" diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 57d16d29158..7fadd7bf9f1 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -70,7 +70,7 @@ = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do Disconnect - else - = link_to user_omniauth_authorize_path(provider), method: :post, class: 'provider-btn not-active', "data-no-turbolink" => "true" do + = link_to provider_path(provider), method: :post, class: 'provider-btn not-active', "data-no-turbolink" => "true" do Connect %hr - if current_user.can_change_username? -- cgit v1.2.1 From 602fe111912bac119e752b0dfa3b4b3cd81585ff Mon Sep 17 00:00:00 2001 From: Connor Shea Date: Mon, 25 Jul 2016 11:40:40 -0600 Subject: Remove provider path, replace with dynamic path. --- CHANGELOG | 1 + app/controllers/sessions_controller.rb | 2 +- app/helpers/auth_helper.rb | 6 ------ app/views/devise/sessions/_new_crowd.html.haml | 2 +- app/views/devise/shared/_omniauth_box.html.haml | 2 +- app/views/profiles/accounts/show.html.haml | 2 +- spec/features/login_spec.rb | 2 +- spec/views/devise/shared/_signin_box.html.haml_spec.rb | 2 +- 8 files changed, 7 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 2bcbe501fb1..03dc62bfd1d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -20,6 +20,7 @@ v 8.11.0 (unreleased) - Update to gitlab_git 10.4.1 and take advantage of preserved Ref objects - Retrieve rendered HTML from cache in one request - Fix renaming repository when name contains invalid chararacters under project settings + - Fix devise deprecation warnings. - Optimize checking if a user has read access to a list of issues !5370 - Nokogiri's various parsing methods are now instrumented - Add a way to send an email and create an issue based on private personal token. Find the email address from issues page. !3363 diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 25598e065b8..5d7ecfeacf4 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -101,7 +101,7 @@ class SessionsController < Devise::SessionsController # Prevent alert from popping up on the first page shown after authentication. flash[:alert] = nil - redirect_to provider_path(provider) + redirect_to omniauth_authorize_path(:user, provider) end def valid_otp_attempt?(user) diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb index dcc156311a1..cd4d778e508 100644 --- a/app/helpers/auth_helper.rb +++ b/app/helpers/auth_helper.rb @@ -60,12 +60,6 @@ module AuthHelper end end - # Constructs the OAuth provider path. - # For example: user_google_omniauth_authorize_path - def provider_path(provider) - send("user_#{provider.underscore}_omniauth_authorize_path") - end - def auth_active?(provider) current_user.identities.exists?(provider: provider.to_s) end diff --git a/app/views/devise/sessions/_new_crowd.html.haml b/app/views/devise/sessions/_new_crowd.html.haml index 6c7c89700cd..b7d3acac2b1 100644 --- a/app/views/devise/sessions/_new_crowd.html.haml +++ b/app/views/devise/sessions/_new_crowd.html.haml @@ -1,4 +1,4 @@ -= form_tag(provider_path("crowd"), id: 'new_crowd_user' ) do += form_tag(omniauth_authorize_path(:user, :crowd), id: 'new_crowd_user' ) do = text_field_tag :username, nil, {class: "form-control top", placeholder: "Username", autofocus: "autofocus"} = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} - if devise_mapping.rememberable? diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml index 07d4caf6b94..2e7da2747d0 100644 --- a/app/views/devise/shared/_omniauth_box.html.haml +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -5,4 +5,4 @@ - providers.each do |provider| %span.light - has_icon = provider_has_icon?(provider) - = link_to provider_image_tag(provider), provider_path(provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true" + = link_to provider_image_tag(provider), omniauth_authorize_path(:user, provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true" diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 7fadd7bf9f1..c80f22457b4 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -70,7 +70,7 @@ = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'provider-btn' do Disconnect - else - = link_to provider_path(provider), method: :post, class: 'provider-btn not-active', "data-no-turbolink" => "true" do + = link_to omniauth_authorize_path(:user, provider), method: :post, class: 'provider-btn not-active', "data-no-turbolink" => "true" do Connect %hr - if current_user.can_change_username? diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb index 58753ff21f6..c4e8b1da531 100644 --- a/spec/features/login_spec.rb +++ b/spec/features/login_spec.rb @@ -128,7 +128,7 @@ feature 'Login', feature: true do end allow(Gitlab::OAuth::Provider).to receive_messages(providers: [:saml], config_for: saml_config) allow(Gitlab.config.omniauth).to receive_messages(messages) - allow_any_instance_of(Object).to receive(:user_omniauth_authorize_path).with('saml').and_return('/users/auth/saml') + expect_any_instance_of(Object).to receive(:omniauth_authorize_path).with(:user, "saml").and_return('/users/auth/saml') end it 'should show 2FA prompt after OAuth login' do diff --git a/spec/views/devise/shared/_signin_box.html.haml_spec.rb b/spec/views/devise/shared/_signin_box.html.haml_spec.rb index 05a76ee4bdb..ee362e6fcb3 100644 --- a/spec/views/devise/shared/_signin_box.html.haml_spec.rb +++ b/spec/views/devise/shared/_signin_box.html.haml_spec.rb @@ -31,7 +31,7 @@ describe 'devise/shared/_signin_box' do def enable_crowd allow(view).to receive(:form_based_providers).and_return([:crowd]) allow(view).to receive(:crowd_enabled?).and_return(true) - allow(view).to receive(:user_omniauth_authorize_path).with('crowd'). + allow(view).to receive(:omniauth_authorize_path).with(:user, :crowd). and_return('/crowd') end end -- cgit v1.2.1 From c887a131c018d02d3d71bea22d02934ca96d4acb Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Mon, 1 Aug 2016 14:16:53 -0500 Subject: Remove unused images --- CHANGELOG | 1 + app/assets/images/bg-header.png | Bin 90 -> 0 bytes app/assets/images/bg_fallback.png | Bin 167 -> 0 bytes app/assets/images/chosen-sprite.png | Bin 367 -> 0 bytes app/assets/images/diff_note_add.png | Bin 418 -> 0 bytes app/assets/images/icon-search.png | Bin 222 -> 0 bytes app/assets/images/icon_sprite.png | Bin 2636 -> 0 bytes app/assets/images/images.png | Bin 5806 -> 0 bytes app/assets/images/move.png | Bin 197 -> 0 bytes app/assets/images/progress_bar.gif | Bin 494 -> 0 bytes app/assets/images/slider_handles.png | Bin 1341 -> 0 bytes 11 files changed, 1 insertion(+) delete mode 100644 app/assets/images/bg-header.png delete mode 100644 app/assets/images/bg_fallback.png delete mode 100644 app/assets/images/chosen-sprite.png delete mode 100644 app/assets/images/diff_note_add.png delete mode 100644 app/assets/images/icon-search.png delete mode 100644 app/assets/images/icon_sprite.png delete mode 100644 app/assets/images/images.png delete mode 100644 app/assets/images/move.png delete mode 100644 app/assets/images/progress_bar.gif delete mode 100644 app/assets/images/slider_handles.png diff --git a/CHANGELOG b/CHANGELOG index 38e91fc3e98..271632bd81b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -16,6 +16,7 @@ v 8.11.0 (unreleased) - Optimize maximum user access level lookup in loading of notes - Add "No one can push" as an option for protected branches. !5081 - Environments have an url to link to + - Remove unused images (ClemMakesApps) - Limit git rev-list output count to one in forced push check - Clean up unused routes (Josef Strzibny) - Add green outline to New Branch button. !5447 (winniehell) diff --git a/app/assets/images/bg-header.png b/app/assets/images/bg-header.png deleted file mode 100644 index 639271c6faf..00000000000 Binary files a/app/assets/images/bg-header.png and /dev/null differ diff --git a/app/assets/images/bg_fallback.png b/app/assets/images/bg_fallback.png deleted file mode 100644 index 5c55bc79dec..00000000000 Binary files a/app/assets/images/bg_fallback.png and /dev/null differ diff --git a/app/assets/images/chosen-sprite.png b/app/assets/images/chosen-sprite.png deleted file mode 100644 index 3d936b07d44..00000000000 Binary files a/app/assets/images/chosen-sprite.png and /dev/null differ diff --git a/app/assets/images/diff_note_add.png b/app/assets/images/diff_note_add.png deleted file mode 100644 index 0084422e330..00000000000 Binary files a/app/assets/images/diff_note_add.png and /dev/null differ diff --git a/app/assets/images/icon-search.png b/app/assets/images/icon-search.png deleted file mode 100644 index 3c1c146541d..00000000000 Binary files a/app/assets/images/icon-search.png and /dev/null differ diff --git a/app/assets/images/icon_sprite.png b/app/assets/images/icon_sprite.png deleted file mode 100644 index 2e7a5023398..00000000000 Binary files a/app/assets/images/icon_sprite.png and /dev/null differ diff --git a/app/assets/images/images.png b/app/assets/images/images.png deleted file mode 100644 index bd60de994c4..00000000000 Binary files a/app/assets/images/images.png and /dev/null differ diff --git a/app/assets/images/move.png b/app/assets/images/move.png deleted file mode 100644 index 6a0567f8f25..00000000000 Binary files a/app/assets/images/move.png and /dev/null differ diff --git a/app/assets/images/progress_bar.gif b/app/assets/images/progress_bar.gif deleted file mode 100644 index c3d43fa40b2..00000000000 Binary files a/app/assets/images/progress_bar.gif and /dev/null differ diff --git a/app/assets/images/slider_handles.png b/app/assets/images/slider_handles.png deleted file mode 100644 index 52ad11ab7a1..00000000000 Binary files a/app/assets/images/slider_handles.png and /dev/null differ -- cgit v1.2.1 From 8a62f4e7d46908c56bedf155792323dc4218be94 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 3 Aug 2016 11:22:40 -0700 Subject: Update the gitlab-shell version in the tmp/tests directory to the right version Previously the gitlab-shell version would never be updated if the directory existed via the `gitlab:shell:install` Rake task. This could lead to incompatibility issues or random errors. --- lib/tasks/gitlab/shell.rake | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index c85ebdf8619..ba93945bd03 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -5,7 +5,8 @@ namespace :gitlab do warn_user_is_not_gitlab default_version = Gitlab::Shell.version_required - args.with_defaults(tag: 'v' + default_version, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git") + default_version_tag = 'v' + default_version + args.with_defaults(tag: default_version_tag, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git") user = Gitlab.config.gitlab.user home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home @@ -15,7 +16,12 @@ namespace :gitlab do target_dir = Gitlab.config.gitlab_shell.path # Clone if needed - unless File.directory?(target_dir) + if File.directory?(target_dir) + Dir.chdir(target_dir) do + system(*%W(Gitlab.config.git.bin_path} fetch --tags --quiet)) + system(*%W(Gitlab.config.git.bin_path} checkout --quiet #{default_version_tag})) + end + else system(*%W(#{Gitlab.config.git.bin_path} clone -- #{args.repo} #{target_dir})) end -- cgit v1.2.1 From 54f6266fadcadb6e423a3275858a0b0e8189b900 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 3 Aug 2016 17:26:36 +0200 Subject: Improve CI fixtures --- db/fixtures/development/14_builds.rb | 45 +++++++++++++++++------------------- 1 file changed, 21 insertions(+), 24 deletions(-) diff --git a/db/fixtures/development/14_builds.rb b/db/fixtures/development/14_builds.rb index 124704cb451..e65abe4ef77 100644 --- a/db/fixtures/development/14_builds.rb +++ b/db/fixtures/development/14_builds.rb @@ -1,6 +1,6 @@ class Gitlab::Seeder::Builds STAGES = %w[build notify_build test notify_test deploy notify_deploy] - + def initialize(project) @project = project end @@ -8,26 +8,26 @@ class Gitlab::Seeder::Builds def seed! pipelines.each do |pipeline| begin - build_create!(pipeline, name: 'build:linux', stage: 'build') - build_create!(pipeline, name: 'build:osx', stage: 'build') + build_create!(pipeline, name: 'build:linux', stage: 'build', status_event: :success) + build_create!(pipeline, name: 'build:osx', stage: 'build', status_event: :success) - build_create!(pipeline, name: 'slack post build', stage: 'notify_build') + build_create!(pipeline, name: 'slack post build', stage: 'notify_build', status_event: :success) - build_create!(pipeline, name: 'rspec:linux', stage: 'test') - build_create!(pipeline, name: 'rspec:windows', stage: 'test') - build_create!(pipeline, name: 'rspec:windows', stage: 'test') - build_create!(pipeline, name: 'rspec:osx', stage: 'test') - build_create!(pipeline, name: 'spinach:linux', stage: 'test') - build_create!(pipeline, name: 'spinach:osx', stage: 'test') - build_create!(pipeline, name: 'cucumber:linux', stage: 'test') - build_create!(pipeline, name: 'cucumber:osx', stage: 'test') + build_create!(pipeline, name: 'rspec:linux', stage: 'test', status_event: :success) + build_create!(pipeline, name: 'rspec:windows', stage: 'test', status_event: :success) + build_create!(pipeline, name: 'rspec:windows', stage: 'test', status_event: :success) + build_create!(pipeline, name: 'rspec:osx', stage: 'test', status_event: :success) + build_create!(pipeline, name: 'spinach:linux', stage: 'test', status: :pending) + build_create!(pipeline, name: 'spinach:osx', stage: 'test', status_event: :cancel) + build_create!(pipeline, name: 'cucumber:linux', stage: 'test', status_event: :run) + build_create!(pipeline, name: 'cucumber:osx', stage: 'test', status_event: :drop) - build_create!(pipeline, name: 'slack post test', stage: 'notify_test') + build_create!(pipeline, name: 'slack post test', stage: 'notify_test', status_event: :success) - build_create!(pipeline, name: 'staging', stage: 'deploy', environment: 'staging') - build_create!(pipeline, name: 'production', stage: 'deploy', environment: 'production', when: 'manual') + build_create!(pipeline, name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success) + build_create!(pipeline, name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :success) - commit_status_create!(pipeline, name: 'jenkins') + commit_status_create!(pipeline, name: 'jenkins', status: :success) print '.' rescue ActiveRecord::RecordInvalid @@ -48,7 +48,7 @@ class Gitlab::Seeder::Builds def build_create!(pipeline, opts = {}) attributes = build_attributes_for(pipeline, opts) - build = Ci::Build.new(attributes) + build = Ci::Build.create!(attributes) if opts[:name].start_with?('build') artifacts_cache_file(artifacts_archive_path) do |file| @@ -60,23 +60,20 @@ class Gitlab::Seeder::Builds end end - build.save! - build.update(status: build_status) - if %w(running success failed).include?(build.status) # We need to set build trace after saving a build (id required) build.trace = FFaker::Lorem.paragraphs(6).join("\n\n") end end - + def commit_status_create!(pipeline, opts = {}) attributes = commit_status_attributes_for(pipeline, opts) - GenericCommitStatus.create(attributes) + GenericCommitStatus.create!(attributes) end - + def commit_status_attributes_for(pipeline, opts) { name: 'test build', stage: 'test', stage_idx: stage_index(opts[:stage]), - ref: 'master', user: build_user, project: @project, pipeline: pipeline, + ref: 'master', tag: false, user: build_user, project: @project, pipeline: pipeline, created_at: Time.now, updated_at: Time.now }.merge(opts) end -- cgit v1.2.1 From efef0be2352a7a2ca769c1db0560a76fbe36b9a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Tue, 2 Aug 2016 19:35:48 -0400 Subject: Remove unnecessary index_projects_on_builds_enabled index from the projects table --- CHANGELOG | 1 + .../20160802010328_remove_builds_enable_index_on_projects.rb | 9 +++++++++ db/schema.rb | 3 +-- 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20160802010328_remove_builds_enable_index_on_projects.rb diff --git a/CHANGELOG b/CHANGELOG index 25911e02ec6..5abcc17dda1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -39,6 +39,7 @@ v 8.11.0 (unreleased) - Make branches sortable without push permission !5462 (winniehell) - Check for Ci::Build artifacts at database level on pipeline partial - Convert image diff background image to CSS (ClemMakesApps) + - Remove unnecessary index_projects_on_builds_enabled index from the projects table - Make "New issue" button in Issue page less obtrusive !5457 (winniehell) - Gitlab::Metrics.current_transaction needs to be public for RailsQueueDuration - Fix search for notes which belongs to deleted objects diff --git a/db/migrate/20160802010328_remove_builds_enable_index_on_projects.rb b/db/migrate/20160802010328_remove_builds_enable_index_on_projects.rb new file mode 100644 index 00000000000..5fd51cb65f1 --- /dev/null +++ b/db/migrate/20160802010328_remove_builds_enable_index_on_projects.rb @@ -0,0 +1,9 @@ +class RemoveBuildsEnableIndexOnProjects < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + remove_index :projects, column: :builds_enabled if index_exists?(:projects, :builds_enabled) + end +end diff --git a/db/schema.rb b/db/schema.rb index 5b35a528e71..dc28842758a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160726093600) do +ActiveRecord::Schema.define(version: 20160802010328) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -852,7 +852,6 @@ ActiveRecord::Schema.define(version: 20160726093600) do end add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree - add_index "projects", ["builds_enabled"], name: "index_projects_on_builds_enabled", using: :btree add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree -- cgit v1.2.1 From 631f59d4e7f449c59735cd7eab25cf407e06d5d8 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Wed, 3 Aug 2016 23:32:12 +0200 Subject: change the API on the merge_request_diff model from diffs -> raw_diffs --- app/models/merge_request.rb | 2 +- app/models/merge_request_diff.rb | 10 +++++----- spec/models/merge_request_diff_spec.rb | 12 ++++++------ spec/models/merge_request_spec.rb | 4 ++-- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index c4761fac2fb..b1fb3ce5d69 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -165,7 +165,7 @@ class MergeRequest < ActiveRecord::Base end def raw_diffs(*args) - merge_request_diff ? merge_request_diff.diffs(*args) : compare.raw_diffs(*args) + merge_request_diff ? merge_request_diff.raw_diffs(*args) : compare.raw_diffs(*args) end def diffs(diff_options = nil) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 119266f2d2c..fa0efe2d596 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -33,12 +33,12 @@ class MergeRequestDiff < ActiveRecord::Base end def size - real_size.presence || diffs.size + real_size.presence || raw_diffs.size end - def diffs(options={}) + def raw_diffs(options={}) if options[:ignore_whitespace_change] - @diffs_no_whitespace ||= begin + @raw_diffs_no_whitespace ||= begin compare = Gitlab::Git::Compare.new( repository.raw_repository, self.start_commit_sha || self.target_branch_sha, @@ -47,8 +47,8 @@ class MergeRequestDiff < ActiveRecord::Base compare.diffs(options) end else - @diffs ||= {} - @diffs[options] ||= load_diffs(st_diffs, options) + @raw_diffs ||= {} + @raw_diffs[options] ||= load_diffs(st_diffs, options) end end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 9a637c94fbe..29f7396f862 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -10,7 +10,7 @@ describe MergeRequestDiff, models: true do expect(mr_diff).not_to receive(:load_diffs) expect(Gitlab::Git::Compare).to receive(:new).and_call_original - mr_diff.diffs(ignore_whitespace_change: true) + mr_diff.raw_diffs(ignore_whitespace_change: true) end end @@ -18,19 +18,19 @@ describe MergeRequestDiff, models: true do before { mr_diff.update_attributes(st_diffs: '') } it 'returns an empty DiffCollection' do - expect(mr_diff.diffs).to be_a(Gitlab::Git::DiffCollection) - expect(mr_diff.diffs).to be_empty + expect(mr_diff.raw_diffs).to be_a(Gitlab::Git::DiffCollection) + expect(mr_diff.raw_diffs).to be_empty end end context 'when the raw diffs exist' do it 'returns the diffs' do - expect(mr_diff.diffs).to be_a(Gitlab::Git::DiffCollection) - expect(mr_diff.diffs).not_to be_empty + expect(mr_diff.raw_diffs).to be_a(Gitlab::Git::DiffCollection) + expect(mr_diff.raw_diffs).not_to be_empty end context 'when the :paths option is set' do - let(:diffs) { mr_diff.diffs(paths: ['files/ruby/popen.rb', 'files/ruby/popen.rb']) } + let(:diffs) { mr_diff.raw_diffs(paths: ['files/ruby/popen.rb', 'files/ruby/popen.rb']) } it 'only returns diffs that match the (old path, new path) given' do expect(diffs.map(&:new_path)).to contain_exactly('files/ruby/popen.rb') diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index e43008c1a4d..d793cfd0bde 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -136,7 +136,7 @@ describe MergeRequest, models: true do it 'delegates to the MR diffs' do merge_request.merge_request_diff = MergeRequestDiff.new - expect(merge_request.merge_request_diff).to receive(:diffs).with(options) + expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(options) merge_request.raw_diffs(options) end @@ -161,7 +161,7 @@ describe MergeRequest, models: true do it 'delegates to the MR diffs' do merge_request.merge_request_diff = MergeRequestDiff.new - expect(merge_request.merge_request_diff).to receive(:diffs).with(hash_including(options)) + expect(merge_request.merge_request_diff).to receive(:raw_diffs).with(hash_including(options)) merge_request.diffs(options) end -- cgit v1.2.1 From 443ae8c4e6682cd66eab0a2a7ec6ef913c0d684c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 3 Aug 2016 16:45:06 -0700 Subject: Fix skip_repo parameter being ignored when destroying a namespace When destroying a namespace, the `skip_repo` parameter is supposed to prevent the repository directory from being destroyed and allow the namespace after_destroy hook to run. If the namespace fails to be deleted for some reason, we could be left with repositories that are deleted with existing projects. --- CHANGELOG | 1 + app/workers/project_destroy_worker.rb | 2 +- spec/workers/project_destroy_worker_spec.rb | 24 ++++++++++++++++++++++++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 spec/workers/project_destroy_worker_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 25911e02ec6..73812e16d6f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -52,6 +52,7 @@ v 8.11.0 (unreleased) - Add commit stats in commit api. !5517 (dixpac) - Add CI configuration button on project page - Make error pages responsive (Takuya Noguchi) + - Fix skip_repo parameter being ignored when destroying a namespace - Change requests_profiles resource constraint to catch virtually any file - Reduce number of queries made for merge_requests/:id/diffs - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) diff --git a/app/workers/project_destroy_worker.rb b/app/workers/project_destroy_worker.rb index b51c6a266c9..3062301a9b1 100644 --- a/app/workers/project_destroy_worker.rb +++ b/app/workers/project_destroy_worker.rb @@ -12,6 +12,6 @@ class ProjectDestroyWorker user = User.find(user_id) - ::Projects::DestroyService.new(project, user, params).execute + ::Projects::DestroyService.new(project, user, params.symbolize_keys).execute end end diff --git a/spec/workers/project_destroy_worker_spec.rb b/spec/workers/project_destroy_worker_spec.rb new file mode 100644 index 00000000000..1b910d9b91e --- /dev/null +++ b/spec/workers/project_destroy_worker_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe ProjectDestroyWorker do + let(:project) { create(:project) } + let(:path) { project.repository.path_to_repo } + + subject { ProjectDestroyWorker.new } + + describe "#perform" do + it "deletes the project" do + subject.perform(project.id, project.owner, {}) + + expect(Project.all).not_to include(project) + expect(Dir.exist?(path)).to be_falsey + end + + it "deletes the project but skips repo deletion" do + subject.perform(project.id, project.owner, { "skip_repo" => true }) + + expect(Project.all).not_to include(project) + expect(Dir.exist?(path)).to be_truthy + end + end +end -- cgit v1.2.1 From bb193801f892490d64cfe34563a77a7ae90ff5d8 Mon Sep 17 00:00:00 2001 From: Herminio Torres Date: Thu, 4 Aug 2016 01:04:01 -0300 Subject: Fix Mystery Guest Magic Variable - Change the name of the variable to assign the local variable for partial, rather than `i` use the `index`. Don't pass a local variable called `i` to a partial. --- CHANGELOG | 1 + app/views/projects/diffs/_diffs.html.haml | 2 +- app/views/projects/diffs/_file.html.haml | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 73812e16d6f..8362131a090 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.11.0 (unreleased) + - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) - Fix the title of the toggle dropdown button. !5515 (herminiotorres) - Improve diff performance by eliminating redundant checks for text blobs - Convert switch icon into icon font (ClemMakesApps) diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 20dc280c3b2..ebaf939f930 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -29,5 +29,5 @@ - next unless blob - blob.load_all_data!(diffs.project.repository) unless blob.only_display_raw? - = render 'projects/diffs/file', i: index, project: diffs.project, + = render 'projects/diffs/file', index: index, project: diffs.project, diff_file: diff_file, diff_commit: diff_commit, blob: blob diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index f914e13a1ec..f0a86fd6d40 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -1,6 +1,6 @@ -.diff-file.file-holder{id: "diff-#{i}", data: diff_file_html_data(project, diff_file)} +.diff-file.file-holder{id: "diff-#{index}", data: diff_file_html_data(project, diff_file)} .file-title{id: "file-path-#{hexdigest(diff_file.file_path)}"} - = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "#diff-#{i}" + = render "projects/diffs/file_header", diff_file: diff_file, blob: blob, diff_commit: diff_commit, project: project, url: "#diff-#{index}" - unless diff_file.submodule? .file-actions.hidden-xs -- cgit v1.2.1 From 8e13162ae112c4c72ee269de0c7f58bfd675fa52 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Thu, 4 Aug 2016 09:55:02 +0530 Subject: Migrate protected branch access levels to match constants in `Gitlab::Access` - In the final round of review (!5081), we moved the protected branch access levels from Rails enums to constants from Gitlab::Access. - The migrations that moved us from the old data model (a single protected_branches table with developers_can_push and developers_can_merge flags) to the new one (separate tables for push_access_levels and merge_access_levels) was not updated. - These migrations still used 0 to mean "Masters" and 1 to mean "Developers" (matching the previous Rails enum), while Gitlab::Access uses 40 and 30 for these, respectively. - Once the migrations run, our data gets into a broken state. - We fix this by migrating all `0`s to `40` and all `1`s to `30`. - https://gitlab.com/gitlab-org/gitlab-ce/issues/20606#note_13561628 = Caveats = - In Gitlab::Access, 0 represents NO_ACCESS. When we run this migration, all protected branches with "No one" as an access level will be changed to "Masters" --- ...ve_from_developers_can_merge_to_protected_branches_merge_access.rb | 4 ++-- ...move_from_developers_can_push_to_protected_branches_push_access.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb b/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb index fa93936ced7..1db0df92bec 100644 --- a/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb +++ b/db/migrate/20160705055254_move_from_developers_can_merge_to_protected_branches_merge_access.rb @@ -14,7 +14,7 @@ class MoveFromDevelopersCanMergeToProtectedBranchesMergeAccess < ActiveRecord::M def up execute <<-HEREDOC INSERT into protected_branch_merge_access_levels (protected_branch_id, access_level, created_at, updated_at) - SELECT id, (CASE WHEN developers_can_merge THEN 1 ELSE 0 END), now(), now() + SELECT id, (CASE WHEN developers_can_merge THEN 30 ELSE 40 END), now(), now() FROM protected_branches HEREDOC end @@ -23,7 +23,7 @@ class MoveFromDevelopersCanMergeToProtectedBranchesMergeAccess < ActiveRecord::M execute <<-HEREDOC UPDATE protected_branches SET developers_can_merge = TRUE WHERE id IN (SELECT protected_branch_id FROM protected_branch_merge_access_levels - WHERE access_level = 1); + WHERE access_level = 30); HEREDOC end end diff --git a/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb b/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb index 56f6159d1d8..5c3e189bb5b 100644 --- a/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb +++ b/db/migrate/20160705055308_move_from_developers_can_push_to_protected_branches_push_access.rb @@ -14,7 +14,7 @@ class MoveFromDevelopersCanPushToProtectedBranchesPushAccess < ActiveRecord::Mig def up execute <<-HEREDOC INSERT into protected_branch_push_access_levels (protected_branch_id, access_level, created_at, updated_at) - SELECT id, (CASE WHEN developers_can_push THEN 1 ELSE 0 END), now(), now() + SELECT id, (CASE WHEN developers_can_push THEN 30 ELSE 40 END), now(), now() FROM protected_branches HEREDOC end @@ -23,7 +23,7 @@ class MoveFromDevelopersCanPushToProtectedBranchesPushAccess < ActiveRecord::Mig execute <<-HEREDOC UPDATE protected_branches SET developers_can_push = TRUE WHERE id IN (SELECT protected_branch_id FROM protected_branch_push_access_levels - WHERE access_level = 1); + WHERE access_level = 30); HEREDOC end end -- cgit v1.2.1 From 4d0461e29983630a653203a1e5c1a28dfcfeada5 Mon Sep 17 00:00:00 2001 From: Timothy Andrew Date: Thu, 4 Aug 2016 10:42:34 +0530 Subject: Fix `#down` for two protected branches-related migrations. - The migrations called `add_column_with_default` with a `null` option, which the Rails `add_column` method accepts. This fails because `add_column_with_default` expects an `allow_null` option instead. - The migrations have been fixed to use `allow_null`. --- ...20160705055809_remove_developers_can_push_from_protected_branches.rb | 2 +- ...0160705055813_remove_developers_can_merge_from_protected_branches.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb b/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb index f563f660ddf..52a9819c628 100644 --- a/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb +++ b/db/migrate/20160705055809_remove_developers_can_push_from_protected_branches.rb @@ -14,6 +14,6 @@ class RemoveDevelopersCanPushFromProtectedBranches < ActiveRecord::Migration end def down - add_column_with_default(:protected_branches, :developers_can_push, :boolean, default: false, null: false) + add_column_with_default(:protected_branches, :developers_can_push, :boolean, default: false, allow_null: false) end end diff --git a/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb b/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb index aa71e06d36e..4a7bde7f9f3 100644 --- a/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb +++ b/db/migrate/20160705055813_remove_developers_can_merge_from_protected_branches.rb @@ -14,6 +14,6 @@ class RemoveDevelopersCanMergeFromProtectedBranches < ActiveRecord::Migration end def down - add_column_with_default(:protected_branches, :developers_can_merge, :boolean, default: false, null: false) + add_column_with_default(:protected_branches, :developers_can_merge, :boolean, default: false, allow_null: false) end end -- cgit v1.2.1 From a999153bcfcadc2d8a44eaceee1d63cc65957fcf Mon Sep 17 00:00:00 2001 From: Ruben Davila Date: Thu, 4 Aug 2016 00:22:49 -0500 Subject: Don't setup DB for slack notification build --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2eda2a6007d..8da9acf9066 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -252,6 +252,9 @@ coverage: notify:slack: stage: post-test + variables: + USE_DB: "false" + USE_BUNDLE_INSTALL: "false" script: - ./scripts/notify_slack.sh "#builds" "Build on \`$CI_BUILD_REF_NAME\` failed! Commit \`$(git log -1 --oneline)\` See " when: on_failure -- cgit v1.2.1 From b6d545df51d7c3b4e572b40cdafb7efeb78edaf4 Mon Sep 17 00:00:00 2001 From: Tim Masliuchenko Date: Thu, 21 Jul 2016 21:44:12 +0300 Subject: Add unfold links for Side-by-Side view --- CHANGELOG | 1 + app/assets/javascripts/diff.js | 29 +++----- app/controllers/projects/blob_controller.rb | 2 + app/helpers/diff_helper.rb | 39 +++++++---- app/views/projects/blob/diff.html.haml | 34 ++++++---- app/views/projects/diffs/_content.html.haml | 2 +- app/views/projects/diffs/_diffs.html.haml | 2 +- app/views/projects/diffs/_line.html.haml | 3 +- app/views/projects/diffs/_match_line.html.haml | 7 -- app/views/projects/diffs/_parallel_view.html.haml | 10 +-- app/views/projects/diffs/_text_file.html.haml | 5 +- app/views/projects/merge_requests/_show.html.haml | 2 +- features/project/merge_requests.feature | 9 +++ features/steps/project/merge_requests.rb | 3 + spec/helpers/diff_helper_spec.rb | 80 ++++++++++++++++------- 15 files changed, 139 insertions(+), 89 deletions(-) delete mode 100644 app/views/projects/diffs/_match_line.html.haml diff --git a/CHANGELOG b/CHANGELOG index 73812e16d6f..89336cf484c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -59,6 +59,7 @@ v 8.11.0 (unreleased) - Fix RequestProfiler::Middleware error when code is reloaded in development - Catch what warden might throw when profiling requests to re-throw it - Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker + - Add unfold links for Side-by-Side view. !5415 (Tim Masliuchenko) v 8.10.3 - Fix Import/Export issue importing milestones and labels not associated properly. !5426 diff --git a/app/assets/javascripts/diff.js b/app/assets/javascripts/diff.js index 298f3852085..3dd7ceba92f 100644 --- a/app/assets/javascripts/diff.js +++ b/app/assets/javascripts/diff.js @@ -10,7 +10,7 @@ $(document).off('click', '.js-unfold'); $(document).on('click', '.js-unfold', (function(_this) { return function(event) { - var line_number, link, offset, old_line, params, prev_new_line, prev_old_line, ref, ref1, since, target, to, unfold, unfoldBottom; + var line_number, link, file, offset, old_line, params, prev_new_line, prev_old_line, ref, ref1, since, target, to, unfold, unfoldBottom; target = $(event.target); unfoldBottom = target.hasClass('js-unfold-bottom'); unfold = true; @@ -31,14 +31,16 @@ unfold = false; } } - link = target.parents('.diff-file').attr('data-blob-diff-path'); + file = target.parents('.diff-file'); + link = file.data('blob-diff-path'); params = { since: since, to: to, bottom: unfoldBottom, offset: offset, unfold: unfold, - indent: 1 + indent: 1, + view: file.data('view') }; return $.get(link, params, function(response) { return target.parent().replaceWith(response); @@ -48,26 +50,13 @@ } Diff.prototype.lineNumbers = function(line) { - var i, l, len, line_number, line_numbers, lines, results; if (!line.children().length) { return [0, 0]; } - lines = line.children().slice(0, 2); - line_numbers = (function() { - var i, len, results; - results = []; - for (i = 0, len = lines.length; i < len; i++) { - l = lines[i]; - results.push($(l).attr('data-linenumber')); - } - return results; - })(); - results = []; - for (i = 0, len = line_numbers.length; i < len; i++) { - line_number = line_numbers[i]; - results.push(parseInt(line_number)); - } - return results; + + return line.find('.diff-line-num').map(function() { + return parseInt($(this).data('linenumber')); + }); }; return Diff; diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index eda3727a28d..19d051720e9 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -76,6 +76,8 @@ class Projects::BlobController < Projects::ApplicationController end def diff + apply_diff_view_cookie! + @form = UnfoldForm.new(params) @lines = Gitlab::Highlight.highlight_lines(repository, @ref, @path) @lines = @lines[@form.since - 1..@form.to - 1] diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index cc7121b1163..f3c9ea074b4 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -13,12 +13,11 @@ module DiffHelper end def diff_view - diff_views = %w(inline parallel) - - if diff_views.include?(cookies[:diff_view]) - cookies[:diff_view] - else - diff_views.first + @diff_view ||= begin + diff_views = %w(inline parallel) + diff_view = cookies[:diff_view] + diff_view = diff_views.first unless diff_views.include?(diff_view) + diff_view.to_sym end end @@ -33,12 +32,23 @@ module DiffHelper options end - def unfold_bottom_class(bottom) - bottom ? 'js-unfold js-unfold-bottom' : '' - end + def diff_match_line(old_pos, new_pos, text: '', view: :inline, bottom: false) + content = content_tag :td, text, class: "line_content match #{view == :inline ? '' : view}" + cls = ['diff-line-num', 'unfold', 'js-unfold'] + cls << 'js-unfold-bottom' if bottom + + html = '' + if old_pos + html << content_tag(:td, '...', class: cls + ['old_line'], data: { linenumber: old_pos }) + html << content unless view == :inline + end + + if new_pos + html << content_tag(:td, '...', class: cls + ['new_line'], data: { linenumber: new_pos }) + html << content + end - def unfold_class(unfold) - unfold ? 'unfold js-unfold' : '' + html.html_safe end def diff_line_content(line, line_type = nil) @@ -67,11 +77,11 @@ module DiffHelper end def inline_diff_btn - diff_btn('Inline', 'inline', diff_view == 'inline') + diff_btn('Inline', 'inline', diff_view == :inline) end def parallel_diff_btn - diff_btn('Side-by-side', 'parallel', diff_view == 'parallel') + diff_btn('Side-by-side', 'parallel', diff_view == :parallel) end def submodule_link(blob, ref, repository = @repository) @@ -103,7 +113,8 @@ module DiffHelper commit = commit_for_diff(diff_file) { blob_diff_path: namespace_project_blob_diff_path(project.namespace, project, - tree_join(commit.id, diff_file.file_path)) + tree_join(commit.id, diff_file.file_path)), + view: diff_view } end diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index 5926d181ba3..a79ae53c780 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -1,20 +1,30 @@ - if @lines.present? + - line_class = diff_view == :inline ? '' : diff_view - if @form.unfold? && @form.since != 1 && !@form.bottom? - %tr.line_holder - = render "projects/diffs/match_line", { line: @match_line, - line_old: @form.since, line_new: @form.since, bottom: false, new_file: false } + %tr.line_holder{ class: line_class } + = diff_match_line @form.since, @form.since, text: @match_line, view: diff_view - @lines.each_with_index do |line, index| - line_new = index + @form.since - line_old = line_new - @form.offset - %tr.line_holder{ id: line_old } - %td.old_line.diff-line-num{ data: { linenumber: line_old } } - = link_to raw(line_old), "##{line_old}" - %td.new_line.diff-line-num{ data: { linenumber: line_old } } - = link_to raw(line_new) , "##{line_old}" - %td.line_content.noteable_line==#{' ' * @form.indent}#{line} + - line_content = capture do + %td.line_content.noteable_line{ class: line_class }==#{' ' * @form.indent}#{line} + %tr.line_holder{ id: line_old, class: line_class } + - case diff_view + - when :inline + %td.old_line.diff-line-num{ data: { linenumber: line_old } } + %a{href: "##{line_old}", data: { linenumber: line_old }} + %td.new_line.diff-line-num{ data: { linenumber: line_new } } + %a{href: "##{line_new}", data: { linenumber: line_new }} + = line_content + - when :parallel + %td.old_line.diff-line-num{data: { linenumber: line_old }} + = link_to raw(line_old), "##{line_old}" + = line_content + %td.new_line.diff-line-num{data: { linenumber: line_new }} + = link_to raw(line_new), "##{line_new}" + = line_content - if @form.unfold? && @form.bottom? && @form.to < @blob.loc - %tr.line_holder{ id: @form.to } - = render "projects/diffs/match_line", { line: @match_line, - line_old: @form.to, line_new: @form.to, bottom: true, new_file: false } + %tr.line_holder{ id: @form.to, class: line_class } + = diff_match_line @form.to, @form.to, text: @match_line, view: diff_view, bottom: true diff --git a/app/views/projects/diffs/_content.html.haml b/app/views/projects/diffs/_content.html.haml index a1b071f130c..d37961c4e40 100644 --- a/app/views/projects/diffs/_content.html.haml +++ b/app/views/projects/diffs/_content.html.haml @@ -13,7 +13,7 @@ .nothing-here-block.diff-collapsed{data: { diff_for_path: url } } This diff is collapsed. Click to expand it. - elsif diff_file.diff_lines.length > 0 - - if diff_view == 'parallel' + - if diff_view == :parallel = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob - else = render "projects/diffs/text_file", diff_file: diff_file diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml index 20dc280c3b2..f707fa0ee5a 100644 --- a/app/views/projects/diffs/_diffs.html.haml +++ b/app/views/projects/diffs/_diffs.html.haml @@ -1,6 +1,6 @@ - show_whitespace_toggle = local_assigns.fetch(:show_whitespace_toggle, true) - diff_files = diffs.diff_files -- if diff_view == 'parallel' +- if diff_view == :parallel - fluid_layout true .content-block.oneline-block.files-changed diff --git a/app/views/projects/diffs/_line.html.haml b/app/views/projects/diffs/_line.html.haml index 4d3af905b58..2d6a370b848 100644 --- a/app/views/projects/diffs/_line.html.haml +++ b/app/views/projects/diffs/_line.html.haml @@ -4,8 +4,7 @@ %tr.line_holder{ plain ? { class: type} : { class: type, id: line_code } } - case type - when 'match' - = render "projects/diffs/match_line", { line: line.text, - line_old: line.old_pos, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file } + = diff_match_line line.old_pos, line.new_pos, text: line.text - when 'nonewline' %td.old_line.diff-line-num %td.new_line.diff-line-num diff --git a/app/views/projects/diffs/_match_line.html.haml b/app/views/projects/diffs/_match_line.html.haml deleted file mode 100644 index d6dddd97879..00000000000 --- a/app/views/projects/diffs/_match_line.html.haml +++ /dev/null @@ -1,7 +0,0 @@ -%td.old_line.diff-line-num{data: {linenumber: line_old}, - class: [unfold_bottom_class(bottom), unfold_class(!new_file)]} - \... -%td.new_line.diff-line-num{data: {linenumber: line_new}, - class: [unfold_bottom_class(bottom), unfold_class(!new_file)]} - \... -%td.line_content.match= line diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml index 7f30faa20d8..28aad3f4725 100644 --- a/app/views/projects/diffs/_parallel_view.html.haml +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -1,14 +1,15 @@ / Side-by-side diff view %div.text-file.diff-wrap-lines.code.file-content.js-syntax-highlight{ data: diff_view_data } %table + - last_line = 0 - diff_file.parallel_diff_lines.each do |line| - left = line[:left] - right = line[:right] + - last_line = right.new_pos if right %tr.line_holder.parallel - if left - if left.meta? - %td.old_line.diff-line-num.empty-cell - %td.line_content.parallel.match= left.text + = diff_match_line left.old_pos, nil, text: left.text, view: :parallel - else - left_line_code = diff_file.line_code(left) - left_position = diff_file.position(left) @@ -21,8 +22,7 @@ - if right - if right.meta? - %td.old_line.diff-line-num.empty-cell - %td.line_content.parallel.match= left.text + = diff_match_line nil, right.new_pos, text: left.text, view: :parallel - else - right_line_code = diff_file.line_code(right) - right_position = diff_file.position(right) @@ -37,3 +37,5 @@ - discussion_left, discussion_right = parallel_diff_discussions(left, right, diff_file) - if discussion_left || discussion_right = render "discussions/parallel_diff_discussion", discussion_left: discussion_left, discussion_right: discussion_right + - if !diff_file.new_file && last_line > 0 + = diff_match_line last_line, last_line, bottom: true, view: :parallel diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml index 5970b9abf2b..ab5463ba89d 100644 --- a/app/views/projects/diffs/_text_file.html.haml +++ b/app/views/projects/diffs/_text_file.html.haml @@ -15,6 +15,5 @@ - if discussion = render "discussions/diff_discussion", discussion: discussion - - if last_line > 0 - = render "projects/diffs/match_line", { line: "", - line_old: last_line, line_new: last_line, bottom: true, new_file: diff_file.new_file } + - if !diff_file.new_file && last_line > 0 + = diff_match_line last_line, last_line, bottom: true diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 873ed9b59ee..269198adf91 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -2,7 +2,7 @@ - page_description @merge_request.description - page_card_attributes @merge_request.card_attributes -- if diff_view == 'parallel' +- if diff_view == :parallel - fluid_layout true .merge-request{'data-url' => merge_request_path(@merge_request)} diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 21768c15c17..6bac6011467 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -236,6 +236,15 @@ Feature: Project Merge Requests And I unfold diff Then I should see additional file lines + @javascript + Scenario: I unfold diff in Side-by-Side view + Given project "Shop" have "Bug NS-05" open merge request with diffs inside + And I visit merge request page "Bug NS-05" + And I click on the Changes tab + And I click Side-by-side Diff tab + And I unfold diff + Then I should see additional file lines + @javascript Scenario: I show comments on a merge request side-by-side diff with comments in multiple files Given project "Shop" have "Bug NS-05" open merge request with diffs inside diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index da848afd48e..a02a54923a5 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -477,6 +477,9 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I click Side-by-side Diff tab' do find('a', text: 'Side-by-side').trigger('click') + + # Waits for load + expect(page).to have_css('.parallel') end step 'I should see comments on the side-by-side diff page' do diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index 4949280d641..b6554de1c64 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -15,22 +15,22 @@ describe DiffHelper do it 'returns a valid value when cookie is set' do helper.request.cookies[:diff_view] = 'parallel' - expect(helper.diff_view).to eq 'parallel' + expect(helper.diff_view).to eq :parallel end it 'returns a default value when cookie is invalid' do helper.request.cookies[:diff_view] = 'invalid' - expect(helper.diff_view).to eq 'inline' + expect(helper.diff_view).to eq :inline end it 'returns a default value when cookie is nil' do expect(helper.request.cookies).to be_empty - expect(helper.diff_view).to eq 'inline' + expect(helper.diff_view).to eq :inline end end - + describe 'diff_options' do it 'should return no collapse false' do expect(diff_options).to include(no_collapse: false) @@ -59,26 +59,6 @@ describe DiffHelper do end end - describe 'unfold_bottom_class' do - it 'should return empty string when bottom line shouldnt be unfolded' do - expect(unfold_bottom_class(false)).to eq('') - end - - it 'should return js class when bottom lines should be unfolded' do - expect(unfold_bottom_class(true)).to include('js-unfold-bottom') - end - end - - describe 'unfold_class' do - it 'returns empty on false' do - expect(unfold_class(false)).to eq('') - end - - it 'returns a class on true' do - expect(unfold_class(true)).to eq('unfold js-unfold') - end - end - describe '#diff_line_content' do it 'should return non breaking space when line is empty' do expect(diff_line_content(nil)).to eq('  ') @@ -105,4 +85,56 @@ describe DiffHelper do expect(marked_new_line).to be_html_safe end end + + describe "#diff_match_line" do + let(:old_pos) { 40 } + let(:new_pos) { 50 } + let(:text) { 'some_text' } + + it "should generate foldable top match line for inline view with empty text by default" do + output = diff_match_line old_pos, new_pos + + expect(output).to be_html_safe + expect(output).to have_css "td:nth-child(1):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.old_line[data-linenumber='#{old_pos}']", text: '...' + expect(output).to have_css "td:nth-child(2):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.new_line[data-linenumber='#{new_pos}']", text: '...' + expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: '' + end + + it "should allow to define text and bottom option" do + output = diff_match_line old_pos, new_pos, text: text, bottom: true + + expect(output).to be_html_safe + expect(output).to have_css "td:nth-child(1).diff-line-num.unfold.js-unfold.js-unfold-bottom.old_line[data-linenumber='#{old_pos}']", text: '...' + expect(output).to have_css "td:nth-child(2).diff-line-num.unfold.js-unfold.js-unfold-bottom.new_line[data-linenumber='#{new_pos}']", text: '...' + expect(output).to have_css 'td:nth-child(3):not(.parallel).line_content.match', text: text + end + + it "should generate match line for parallel view" do + output = diff_match_line old_pos, new_pos, text: text, view: :parallel + + expect(output).to be_html_safe + expect(output).to have_css "td:nth-child(1):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.old_line[data-linenumber='#{old_pos}']", text: '...' + expect(output).to have_css 'td:nth-child(2).line_content.match.parallel', text: text + expect(output).to have_css "td:nth-child(3):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.new_line[data-linenumber='#{new_pos}']", text: '...' + expect(output).to have_css 'td:nth-child(4).line_content.match.parallel', text: text + end + + it "should allow to generate only left match line for parallel view" do + output = diff_match_line old_pos, nil, text: text, view: :parallel + + expect(output).to be_html_safe + expect(output).to have_css "td:nth-child(1):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.old_line[data-linenumber='#{old_pos}']", text: '...' + expect(output).to have_css 'td:nth-child(2).line_content.match.parallel', text: text + expect(output).not_to have_css 'td:nth-child(3)' + end + + it "should allow to generate only right match line for parallel view" do + output = diff_match_line nil, new_pos, text: text, view: :parallel + + expect(output).to be_html_safe + expect(output).to have_css "td:nth-child(1):not(.js-unfold-bottom).diff-line-num.unfold.js-unfold.new_line[data-linenumber='#{new_pos}']", text: '...' + expect(output).to have_css 'td:nth-child(2).line_content.match.parallel', text: text + expect(output).not_to have_css 'td:nth-child(3)' + end + end end -- cgit v1.2.1 From c3ed78a4edf400e597803d91bb3afede4682afd7 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 4 Aug 2016 12:57:07 +0300 Subject: Refactor build artifacts documentation - Split user and admin documentation - Use new location paths - Add new Continuous Integration guide for the Admin area - Link to new guide from the Admin area --- .../admin/application_settings/_form.html.haml | 5 +- doc/administration/build_artifacts.md | 90 +++++++++++ doc/ci/README.md | 2 +- doc/ci/build_artifacts/README.md | 177 +-------------------- .../img/build_artifacts_browser.png | Bin 82102 -> 0 bytes .../img/build_artifacts_browser_button.png | Bin 7230 -> 0 bytes .../admin_area/settings/continuous_integration.md | 20 +++ .../img/admin_area_maximum_artifacts_size.png | Bin 0 -> 6227 bytes .../settings/img/admin_area_settings_button.png | Bin 0 -> 9184 bytes doc/user/project/builds/artifacts.md | 104 ++++++++++++ .../project/builds/img/build_artifacts_browser.png | Bin 0 -> 8365 bytes .../builds/img/build_artifacts_browser_button.png | Bin 0 -> 11041 bytes .../builds/img/build_artifacts_builds_page.png | Bin 0 -> 55625 bytes .../builds/img/build_artifacts_pipelines_page.png | Bin 0 -> 73038 bytes 14 files changed, 222 insertions(+), 176 deletions(-) create mode 100644 doc/administration/build_artifacts.md delete mode 100644 doc/ci/build_artifacts/img/build_artifacts_browser.png delete mode 100644 doc/ci/build_artifacts/img/build_artifacts_browser_button.png create mode 100644 doc/user/admin_area/settings/continuous_integration.md create mode 100644 doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.png create mode 100644 doc/user/admin_area/settings/img/admin_area_settings_button.png create mode 100644 doc/user/project/builds/artifacts.md create mode 100644 doc/user/project/builds/img/build_artifacts_browser.png create mode 100644 doc/user/project/builds/img/build_artifacts_browser_button.png create mode 100644 doc/user/project/builds/img/build_artifacts_builds_page.png create mode 100644 doc/user/project/builds/img/build_artifacts_pipelines_page.png diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 23b52d08df7..23f864df147 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -228,6 +228,9 @@ = f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2' .col-sm-10 = f.number_field :max_artifacts_size, class: 'form-control' + .help-block + Set the maximum file size each build's artifacts can have + = link_to "(?)", help_page_path("user/admin_area/settings/continuous_integration", anchor: "maximum-artifacts-size") - if Gitlab.config.registry.enabled %fieldset @@ -385,4 +388,4 @@ .form-actions - = f.submit 'Save', class: 'btn btn-save' \ No newline at end of file + = f.submit 'Save', class: 'btn btn-save' diff --git a/doc/administration/build_artifacts.md b/doc/administration/build_artifacts.md new file mode 100644 index 00000000000..64353f7282b --- /dev/null +++ b/doc/administration/build_artifacts.md @@ -0,0 +1,90 @@ +# Build artifacts administration + +>**Notes:** +>- Introduced in GitLab 8.2 and GitLab Runner 0.7.0. +>- Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format + changed to `ZIP`. +>- This is the administration documentation. For the user guide see + [user/project/builds/artifacts.md](../user/project/builds/artifacts.md). + +Artifacts is a list of files and directories which are attached to a build +after it completes successfully. This feature is enabled by default in all +GitLab installations. Keep reading if you want to know how to disable it. + +## Disabling build artifacts + +To disable artifacts site-wide, follow the steps below. + +--- + +**In Omnibus installations:** + +1. Edit `/etc/gitlab/gitlab.rb` and add the following line: + + ```ruby + gitlab_rails['artifacts_enabled'] = false + ``` + +1. Save the file and [reconfigure GitLab][] for the changes to take effect. + +--- + +**In installations from source:** + +1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines: + + ```yaml + artifacts: + enabled: false + ``` + +1. Save the file and [restart GitLab][] for the changes to take effect. + +## Storing build artifacts + +After a successful build, GitLab Runner uploads an archive containing the build +artifacts to GitLab. + +To change the location where the artifacts are stored, follow the steps below. + +--- + +**In Omnibus installations:** + +_The artifacts are stored by default in +`/var/opt/gitlab/gitlab-rails/shared/artifacts`._ + +1. To change the storage path for example to `/mnt/storage/artifacts`, edit + `/etc/gitlab/gitlab.rb` and add the following line: + + ```ruby + gitlab_rails['artifacts_path'] = "/mnt/storage/artifacts" + ``` + +1. Save the file and [reconfigure GitLab][] for the changes to take effect. + +--- + +**In installations from source:** + +_The artifacts are stored by default in +`/home/git/gitlab/shared/artifacts`._ + +1. To change the storage path for example to `/mnt/storage/artifacts`, edit + `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines: + + ```yaml + artifacts: + enabled: true + path: /mnt/storage/artifacts + ``` + +1. Save the file and [restart GitLab][] for the changes to take effect. + +## Set the maximum file size of the artifacts + +Provided the artifacts are enabled, you can change the maximum file size of the +artifacts through the [Admin area settings](../user/admin_area/settings/continuous_integration#maximum-artifacts-size). + +[reconfigure gitlab]: restart_gitlab.md "How to restart GitLab" +[restart gitlab]: restart_gitlab.md "How to restart GitLab" diff --git a/doc/ci/README.md b/doc/ci/README.md index 0833027f91d..10ce4ac8940 100644 --- a/doc/ci/README.md +++ b/doc/ci/README.md @@ -14,7 +14,7 @@ - [Use variables in your `.gitlab-ci.yml`](variables/README.md) - [Use SSH keys in your build environment](ssh_keys/README.md) - [Trigger builds through the API](triggers/README.md) -- [Build artifacts](build_artifacts/README.md) +- [Build artifacts](../user/project/builds/artifacts.md) - [User permissions](../user/permissions.md#gitlab-ci) - [API](../api/ci/README.md) - [CI services (linked docker containers)](services/README.md) diff --git a/doc/ci/build_artifacts/README.md b/doc/ci/build_artifacts/README.md index 9553bb11e9d..7bd7f3ae4f4 100644 --- a/doc/ci/build_artifacts/README.md +++ b/doc/ci/build_artifacts/README.md @@ -1,175 +1,4 @@ -# Introduction to build artifacts +This document was moved to: -Artifacts is a list of files and directories which are attached to a build -after it completes successfully. This feature is enabled by default in all GitLab installations. - -_If you are searching for ways to use artifacts, jump to -[Defining artifacts in `.gitlab-ci.yml`](#defining-artifacts-in-gitlab-ciyml)._ - -Since GitLab 8.2 and [GitLab Runner] 0.7.0, build artifacts that are created by -GitLab Runner are uploaded to GitLab and are downloadable as a single archive -(`tar.gz`) using the GitLab UI. - -Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format -changed to `ZIP`, and it is now possible to browse its contents, with the added -ability of downloading the files separately. - -**Note:** -The artifacts browser will be available only for new artifacts that are sent -to GitLab using GitLab Runner version 1.0 and up. It will not be possible to -browse old artifacts already uploaded to GitLab. - -## Disabling build artifacts - -To disable artifacts site-wide, follow the steps below. - ---- - -**In Omnibus installations:** - -1. Edit `/etc/gitlab/gitlab.rb` and add the following line: - - ```ruby - gitlab_rails['artifacts_enabled'] = false - ``` - -1. Save the file and [reconfigure GitLab][] for the changes to take effect. - ---- - -**In installations from source:** - -1. Edit `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines: - - ```yaml - artifacts: - enabled: false - ``` - -1. Save the file and [restart GitLab][] for the changes to take effect. - -## Defining artifacts in `.gitlab-ci.yml` - -A simple example of using the artifacts definition in `.gitlab-ci.yml` would be -the following: - -```yaml -pdf: - script: xelatex mycv.tex - artifacts: - paths: - - mycv.pdf -``` - -A job named `pdf` calls the `xelatex` command in order to build a pdf file from -the latex source file `mycv.tex`. We then define the `artifacts` paths which in -turn are defined with the `paths` keyword. All paths to files and directories -are relative to the repository that was cloned during the build. - -For more examples on artifacts, follow the -[separate artifacts yaml documentation](../yaml/README.md#artifacts). - -## Storing build artifacts - -After a successful build, GitLab Runner uploads an archive containing the build -artifacts to GitLab. - -To change the location where the artifacts are stored, follow the steps below. - ---- - -**In Omnibus installations:** - -_The artifacts are stored by default in -`/var/opt/gitlab/gitlab-rails/shared/artifacts`._ - -1. To change the storage path for example to `/mnt/storage/artifacts`, edit - `/etc/gitlab/gitlab.rb` and add the following line: - - ```ruby - gitlab_rails['artifacts_path'] = "/mnt/storage/artifacts" - ``` - -1. Save the file and [reconfigure GitLab][] for the changes to take effect. - ---- - -**In installations from source:** - -_The artifacts are stored by default in -`/home/git/gitlab/shared/artifacts`._ - -1. To change the storage path for example to `/mnt/storage/artifacts`, edit - `/home/git/gitlab/config/gitlab.yml` and add or amend the following lines: - - ```yaml - artifacts: - enabled: true - path: /mnt/storage/artifacts - ``` - -1. Save the file and [restart GitLab][] for the changes to take effect. - -## Browsing build artifacts - -When GitLab receives an artifacts archive, an archive metadata file is also -generated. This metadata file describes all the entries that are located in the -artifacts archive itself. The metadata file is in a binary format, with -additional GZIP compression. - -GitLab does not extract the artifacts archive in order to save space, memory -and disk I/O. It instead inspects the metadata file which contains all the -relevant information. This is especially important when there is a lot of -artifacts, or an archive is a very large file. - ---- - -After a successful build, if you visit the build's specific page, you can see -that there are two buttons. - -One is for downloading the artifacts archive and the other for browsing its -contents. - -![Build artifacts browser button](img/build_artifacts_browser_button.png) - ---- - -The archive browser shows the name and the actual file size of each file in the -archive. If your artifacts contained directories, then you are also able to -browse inside them. - -Below you can see an image of three different file formats, as well as two -directories. - -![Build artifacts browser](img/build_artifacts_browser.png) - ---- - -## Downloading build artifacts - -If you need to download the whole archive, there are buttons in various places -inside GitLab that make that possible. - -1. While on the builds page, you can see the download icon for each build's - artifacts archive in the right corner - -1. While inside a specific build, you are presented with a download button - along with the one that browses the archive - -1. And finally, when browsing an archive you can see the download button at - the top right corner - ---- - -Note that GitLab does not extract the entire artifacts archive to send just a -single file to the user. - -When clicking on a specific file, [GitLab Workhorse] extracts it from the -archive and the download begins. - -This implementation saves space, memory and disk I/O. - -[gitlab runner]: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner "GitLab Runner repository" -[reconfigure gitlab]: ../../administration/restart_gitlab.md "How to restart GitLab documentation" -[restart gitlab]: ../../administration/restart_gitlab.md "How to restart GitLab documentation" -[gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository" +- [user/project/builds/artifacts.md](../user/project/builds/artifacts.md) - user guide +- [administration/build_artifacts.md](../administration/build_artifacts.md) - administrator guide diff --git a/doc/ci/build_artifacts/img/build_artifacts_browser.png b/doc/ci/build_artifacts/img/build_artifacts_browser.png deleted file mode 100644 index 59cf2b8746b..00000000000 Binary files a/doc/ci/build_artifacts/img/build_artifacts_browser.png and /dev/null differ diff --git a/doc/ci/build_artifacts/img/build_artifacts_browser_button.png b/doc/ci/build_artifacts/img/build_artifacts_browser_button.png deleted file mode 100644 index 7801c2e6fa6..00000000000 Binary files a/doc/ci/build_artifacts/img/build_artifacts_browser_button.png and /dev/null differ diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md new file mode 100644 index 00000000000..34e2e557f89 --- /dev/null +++ b/doc/user/admin_area/settings/continuous_integration.md @@ -0,0 +1,20 @@ +# Continuous integration Admin settings + +## Maximum artifacts size + +The maximum size of the [build artifacts][art-yml] can be set in the Admin area +of your GitLab instance. The value is in MB and the default is 100MB. Note that +this setting is set for each build. + +1. Go to **Admin area > Settings** (`/admin/application_settings`). + + ![Admin area settings button](img/admin_area_settings_button.png) + +1. Change the value of the maximum artifacts size (in MB): + + ![Admin area maximum artifacts size](img/admin_area_maximum_artifacts_size.png) + +1. Hit **Save** for the changes to take effect. + + +[art-yml]: ../../../administration/build_artifacts.md diff --git a/doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.png b/doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.png new file mode 100644 index 00000000000..53f7e76033e Binary files /dev/null and b/doc/user/admin_area/settings/img/admin_area_maximum_artifacts_size.png differ diff --git a/doc/user/admin_area/settings/img/admin_area_settings_button.png b/doc/user/admin_area/settings/img/admin_area_settings_button.png new file mode 100644 index 00000000000..509708b627f Binary files /dev/null and b/doc/user/admin_area/settings/img/admin_area_settings_button.png differ diff --git a/doc/user/project/builds/artifacts.md b/doc/user/project/builds/artifacts.md new file mode 100644 index 00000000000..c93ae1c369c --- /dev/null +++ b/doc/user/project/builds/artifacts.md @@ -0,0 +1,104 @@ +# Introduction to build artifacts + +>**Notes:** +>- Since GitLab 8.2 and GitLab Runner 0.7.0, build artifacts that are created by + GitLab Runner are uploaded to GitLab and are downloadable as a single archive + (`tar.gz`) using the GitLab UI. +>- Starting from GitLab 8.4 and GitLab Runner 1.0, the artifacts archive format + changed to `ZIP`, and it is now possible to browse its contents, with the added + ability of downloading the files separately. +>- The artifacts browser will be available only for new artifacts that are sent + to GitLab using GitLab Runner version 1.0 and up. It will not be possible to + browse old artifacts already uploaded to GitLab. +>- This is the user documentation. For the administration guide see + [administration/build_artifacts.md](../../../administration/build_artifacts.md). + +Artifacts is a list of files and directories which are attached to a build +after it completes successfully. This feature is enabled by default in all GitLab installations. + +## Defining artifacts in `.gitlab-ci.yml` + +A simple example of using the artifacts definition in `.gitlab-ci.yml` would be +the following: + +```yaml +pdf: + script: xelatex mycv.tex + artifacts: + paths: + - mycv.pdf +``` + +A job named `pdf` calls the `xelatex` command in order to build a pdf file from +the latex source file `mycv.tex`. We then define the `artifacts` paths which in +turn are defined with the `paths` keyword. All paths to files and directories +are relative to the repository that was cloned during the build. + +For more examples on artifacts, follow the artifacts reference in +[`.gitlab-ci.yml` documentation](../../../ci/yaml/README.md#artifacts). + +## Browsing build artifacts + +When GitLab receives an artifacts archive, an archive metadata file is also +generated. This metadata file describes all the entries that are located in the +artifacts archive itself. The metadata file is in a binary format, with +additional GZIP compression. + +GitLab does not extract the artifacts archive in order to save space, memory +and disk I/O. It instead inspects the metadata file which contains all the +relevant information. This is especially important when there is a lot of +artifacts, or an archive is a very large file. + +--- + +After a build finishes, if you visit the build's specific page, you can see +that there are two buttons. One is for downloading the artifacts archive and +the other for browsing its contents. + +![Build artifacts browser button](img/build_artifacts_browser_button.png) + +--- + +The archive browser shows the name and the actual file size of each file in the +archive. If your artifacts contained directories, then you are also able to +browse inside them. + +Below you can see how browsing looks like. In this case we have browsed inside +the archive and at this point there is one directory and one HTML file. + +![Build artifacts browser](img/build_artifacts_browser.png) + +--- + +## Downloading build artifacts + +>**Note:** +GitLab does not extract the entire artifacts archive to send just a single file +to the user. When clicking on a specific file, [GitLab Workhorse] extracts it +from the archive and the download begins. This implementation saves space, +memory and disk I/O. + +If you need to download the whole archive, there are buttons in various places +inside GitLab that make that possible. + +1. While on the pipelines page, you can see the download icon for each build's + artifacts archive in the right corner: + + ![Build artifacts in Pipelines page](img/build_artifacts_pipelines_page.png) + +1. While on the builds page, you can see the download icon for each build's + artifacts archive in the right corner: + + ![Build artifacts in Builds page](img/build_artifacts_builds_page.png) + +1. While inside a specific build, you are presented with a download button + along with the one that browses the archive: + + ![Build artifacts browser button](img/build_artifacts_browser_button.png) + +1. And finally, when browsing an archive you can see the download button at + the top right corner: + + ![Build artifacts browser](img/build_artifacts_browser.png) + +[gitlab workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse "GitLab Workhorse repository" diff --git a/doc/user/project/builds/img/build_artifacts_browser.png b/doc/user/project/builds/img/build_artifacts_browser.png new file mode 100644 index 00000000000..d95e2800c0f Binary files /dev/null and b/doc/user/project/builds/img/build_artifacts_browser.png differ diff --git a/doc/user/project/builds/img/build_artifacts_browser_button.png b/doc/user/project/builds/img/build_artifacts_browser_button.png new file mode 100644 index 00000000000..463540634e3 Binary files /dev/null and b/doc/user/project/builds/img/build_artifacts_browser_button.png differ diff --git a/doc/user/project/builds/img/build_artifacts_builds_page.png b/doc/user/project/builds/img/build_artifacts_builds_page.png new file mode 100644 index 00000000000..db78386ba7b Binary files /dev/null and b/doc/user/project/builds/img/build_artifacts_builds_page.png differ diff --git a/doc/user/project/builds/img/build_artifacts_pipelines_page.png b/doc/user/project/builds/img/build_artifacts_pipelines_page.png new file mode 100644 index 00000000000..6c2d1a4bdc7 Binary files /dev/null and b/doc/user/project/builds/img/build_artifacts_pipelines_page.png differ -- cgit v1.2.1 From dfa8a6140daabf93be0a3f7703c541ddd573ef86 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 4 Aug 2016 13:23:13 +0300 Subject: Fix relative URL --- doc/ci/build_artifacts/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/ci/build_artifacts/README.md b/doc/ci/build_artifacts/README.md index 7bd7f3ae4f4..05605f10fb4 100644 --- a/doc/ci/build_artifacts/README.md +++ b/doc/ci/build_artifacts/README.md @@ -1,4 +1,4 @@ This document was moved to: -- [user/project/builds/artifacts.md](../user/project/builds/artifacts.md) - user guide -- [administration/build_artifacts.md](../administration/build_artifacts.md) - administrator guide +- [user/project/builds/artifacts.md](../../user/project/builds/artifacts.md) - user guide +- [administration/build_artifacts.md](../../administration/build_artifacts.md) - administrator guide -- cgit v1.2.1 From 8c8599a6056766313252e618e87b32456e26e10e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 4 Aug 2016 12:30:27 +0200 Subject: Remove CHANGELOG and fix typo MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 - doc/development/gotchas.md | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 36e99a3d3b1..fa272755033 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,7 +5,6 @@ v 8.11.0 (unreleased) - Limit git rev-list output count to one in forced push check - Retrieve rendered HTML from cache in one request - Load project invited groups and members eagerly in ProjectTeam#fetch_members - - Updated docs for CoffeeScript migration. v 8.10.0 - Fix profile activity heatmap to show correct day name (eanplatter) diff --git a/doc/development/gotchas.md b/doc/development/gotchas.md index fc52027ab94..159d5ce286d 100644 --- a/doc/development/gotchas.md +++ b/doc/development/gotchas.md @@ -41,10 +41,10 @@ Rubocop](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-4-stable/.rubocop.yml#L9 [Exception]: http://stackoverflow.com/q/10048173/223897 -## Don't use inline CoffeeScript/Javascript in views +## Don't use inline CoffeeScript/JavaScript in views Using the inline `:coffee` or `:coffeescript` Haml filters comes with a -performance overhead. Using inline Javascript is not a good way to structure your code and should be avoided. +performance overhead. Using inline JavaScript is not a good way to structure your code and should be avoided. _**Note:** We've [removed these two filters](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/config/initializers/hamlit.rb) in an initializer._ -- cgit v1.2.1 From fa54a8e984949227b2b56ecd20e37e0c6ba498cd Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Thu, 4 Aug 2016 11:56:58 +0200 Subject: =?UTF-8?q?Don=E2=80=99t=20close=20issues=20on=20original=20projec?= =?UTF-8?q?t=20from=20a=20fork?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Paco Guzman --- lib/gitlab/closing_issue_extractor.rb | 4 +++- spec/lib/gitlab/closing_issue_extractor_spec.rb | 11 +++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb index 9bef9037ad6..58f86abc5c4 100644 --- a/lib/gitlab/closing_issue_extractor.rb +++ b/lib/gitlab/closing_issue_extractor.rb @@ -22,7 +22,9 @@ module Gitlab @extractor.analyze(closing_statements.join(" ")) - @extractor.issues + @extractor.issues.reject do |issue| + @extractor.project.forked_from?(issue.project) # Don't extract issues on original project + end end end end diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index e9b8ce6b5bb..de3f64249a2 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -3,10 +3,12 @@ require 'spec_helper' describe Gitlab::ClosingIssueExtractor, lib: true do let(:project) { create(:project) } let(:project2) { create(:project) } + let(:forked_project) { Projects::ForkService.new(project, project.creator).execute } let(:issue) { create(:issue, project: project) } let(:issue2) { create(:issue, project: project2) } let(:reference) { issue.to_reference } let(:cross_reference) { issue2.to_reference(project) } + let(:fork_cross_reference) { issue.to_reference(forked_project) } subject { described_class.new(project, project.creator) } @@ -278,6 +280,15 @@ describe Gitlab::ClosingIssueExtractor, lib: true do end end + context "with a cross-project fork reference" do + subject { described_class.new(forked_project, forked_project.creator) } + + it do + message = "Closes #{fork_cross_reference}" + expect(subject.closed_by_message(message)).to be_empty + end + end + context "with an invalid URL" do it do message = "Closes https://google.com#{urls.namespace_project_issue_path(issue2.project.namespace, issue2.project, issue2)}" -- cgit v1.2.1 From 3dbec7f49a14802dbee1513a9d0015f935b3642f Mon Sep 17 00:00:00 2001 From: Sid Sijbrandij Date: Thu, 4 Aug 2016 10:39:09 +0000 Subject: One link to development guides. --- doc/README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/doc/README.md b/doc/README.md index d28ad499d3a..fc51ea911b9 100644 --- a/doc/README.md +++ b/doc/README.md @@ -54,7 +54,5 @@ ## Contributor documentation -- [Documentation styleguide](development/doc_styleguide.md) Use this styleguide if you are - contributing to documentation. -- [Development](development/README.md) Explains the architecture and the guidelines for shell commands. +- [Development](development/README.md) All styleguides and explanations how to contribute. - [Legal](legal/README.md) Contributor license agreements. -- cgit v1.2.1 From 1de019f5c8a76da10877b8a2b4f27823374c6da2 Mon Sep 17 00:00:00 2001 From: Sid Sijbrandij Date: Thu, 4 Aug 2016 10:45:59 +0000 Subject: Structure the development documentation --- doc/development/README.md | 33 ++++++++++++++++++++++++++------- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/doc/development/README.md b/doc/development/README.md index c5d5af43864..11aa50b89af 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -1,18 +1,37 @@ # Development +## Outside of docs + +- [CONTRIBUTING.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) main contributing guide +- [PROCESS.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/PROCESS.md) contributing process +- [GitLab Development Kit (GDK)](https://gitlab.com/gitlab-org/gitlab-development-kit) to install a development version + +## Styleguides + +- [Documentation styleguide](development/doc_styleguide.md) Use this styleguide if you are + contributing to documentation. +- [Migration Style Guide](migration_style_guide.md) for creating safe migrations +- [Testing standards and style guidelines](testing.md) +- [UI guide](ui_guide.md) for building GitLab with existing css styles and elements +- [SQL guidelines](sql.md) for SQL guidelines + + +## Process + +- [Code review guidelines](code_review.md) for reviewing code and having code reviewed. + +## Backend howtos + - [Architecture](architecture.md) of GitLab - [CI setup](ci_setup.md) for testing GitLab -- [Code review guidelines](code_review.md) for reviewing code and having code - reviewed. - [Gotchas](gotchas.md) to avoid - [How to dump production data to staging](db_dump.md) - [Instrumentation](instrumentation.md) -- [Licensing](licensing.md) for ensuring license compliance -- [Migration Style Guide](migration_style_guide.md) for creating safe migrations - [Performance guidelines](performance.md) - [Rake tasks](rake_tasks.md) for development - [Shell commands](shell_commands.md) in the GitLab codebase - [Sidekiq debugging](sidekiq_debugging.md) -- [SQL guidelines](sql.md) for SQL guidelines -- [Testing standards and style guidelines](testing.md) -- [UI guide](ui_guide.md) for building GitLab with existing css styles and elements + +## Compliance + +- [Licensing](licensing.md) for ensuring license compliance -- cgit v1.2.1 From 6a0bbb5aa58e37a0ad8c3817c4e809143adce1be Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 2 Aug 2016 11:32:28 +0200 Subject: using shared path for project import uploads and refactored gitlab remove export worker --- CHANGELOG | 3 + .../import/gitlab_projects_controller.rb | 7 ++- app/models/project.rb | 5 -- app/services/import_export_clean_up_service.rb | 24 ++++++++ .../repository_archive_clean_up_service.rb | 4 +- app/workers/gitlab_remove_project_export_worker.rb | 9 --- .../import_export_project_cleanup_worker.rb | 9 +++ config/initializers/1_settings.rb | 6 +- lib/gitlab/import_export.rb | 4 ++ .../import_export_clean_up_service_spec.rb | 64 ++++++++++++++++++++++ 10 files changed, 113 insertions(+), 22 deletions(-) create mode 100644 app/services/import_export_clean_up_service.rb delete mode 100644 app/workers/gitlab_remove_project_export_worker.rb create mode 100644 app/workers/import_export_project_cleanup_worker.rb create mode 100644 spec/services/import_export_clean_up_service_spec.rb diff --git a/CHANGELOG b/CHANGELOG index c099c63ce86..dd3ee006593 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -47,6 +47,9 @@ v 8.11.0 (unreleased) - Fix RequestProfiler::Middleware error when code is reloaded in development - Catch what warden might throw when profiling requests to re-throw it +v 8.10.4 (unreleased) + - Fix Import/Export project import not working in HA mode + v 8.10.3 - Fix Import/Export issue importing milestones and labels not associated properly. !5426 - Fix timing problems running imports on production. !5523 diff --git a/app/controllers/import/gitlab_projects_controller.rb b/app/controllers/import/gitlab_projects_controller.rb index 30df1fb2fec..3ec173abcdb 100644 --- a/app/controllers/import/gitlab_projects_controller.rb +++ b/app/controllers/import/gitlab_projects_controller.rb @@ -12,13 +12,14 @@ class Import::GitlabProjectsController < Import::BaseController return redirect_back_or_default(options: { alert: "You need to upload a GitLab project export archive." }) end - imported_file = project_params[:file].path + "-import" + import_upload_path = Gitlab::ImportExport.import_upload_path(filename: project_params[:file].original_filename) - FileUtils.copy_entry(project_params[:file].path, imported_file) + FileUtils.mkdir_p(File.dirname(import_upload_path)) + FileUtils.copy_entry(project_params[:file].path, import_upload_path) @project = Gitlab::ImportExport::ProjectCreator.new(project_params[:namespace_id], current_user, - File.expand_path(imported_file), + import_upload_path, project_params[:path]).execute if @project.saved? diff --git a/app/models/project.rb b/app/models/project.rb index 83b848ded8b..a18aef09acd 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -378,11 +378,6 @@ class Project < ActiveRecord::Base joins(join_body).reorder('join_note_counts.amount DESC') end - - # Deletes gitlab project export files older than 24 hours - def remove_gitlab_exports! - Gitlab::Popen.popen(%W(find #{Gitlab::ImportExport.storage_path} -not -path #{Gitlab::ImportExport.storage_path} -mmin +1440 -delete)) - end end def repository_storage_path diff --git a/app/services/import_export_clean_up_service.rb b/app/services/import_export_clean_up_service.rb new file mode 100644 index 00000000000..6442406d77e --- /dev/null +++ b/app/services/import_export_clean_up_service.rb @@ -0,0 +1,24 @@ +class ImportExportCleanUpService + LAST_MODIFIED_TIME_IN_MINUTES = 1440 + + attr_reader :mmin, :path + + def initialize(mmin = LAST_MODIFIED_TIME_IN_MINUTES) + @mmin = mmin + @path = Gitlab::ImportExport.storage_path + end + + def execute + Gitlab::Metrics.measure(:import_export_clean_up) do + return unless File.directory?(path) + + clean_up_export_files + end + end + + private + + def clean_up_export_files + Gitlab::Popen.popen(%W(find #{path} -not -path #{path} -mmin +#{mmin} -delete)) + end +end diff --git a/app/services/repository_archive_clean_up_service.rb b/app/services/repository_archive_clean_up_service.rb index 0b56b09738d..aa84d36a206 100644 --- a/app/services/repository_archive_clean_up_service.rb +++ b/app/services/repository_archive_clean_up_service.rb @@ -1,6 +1,8 @@ class RepositoryArchiveCleanUpService LAST_MODIFIED_TIME_IN_MINUTES = 120 + attr_reader :mmin, :path + def initialize(mmin = LAST_MODIFIED_TIME_IN_MINUTES) @mmin = mmin @path = Gitlab.config.gitlab.repository_downloads_path @@ -17,8 +19,6 @@ class RepositoryArchiveCleanUpService private - attr_reader :mmin, :path - def clean_up_old_archives run(%W(find #{path} -not -path #{path} -type f \( -name \*.tar -o -name \*.bz2 -o -name \*.tar.gz -o -name \*.zip \) -maxdepth 2 -mmin +#{mmin} -delete)) end diff --git a/app/workers/gitlab_remove_project_export_worker.rb b/app/workers/gitlab_remove_project_export_worker.rb deleted file mode 100644 index 1d91897d520..00000000000 --- a/app/workers/gitlab_remove_project_export_worker.rb +++ /dev/null @@ -1,9 +0,0 @@ -class GitlabRemoveProjectExportWorker - include Sidekiq::Worker - - sidekiq_options queue: :default - - def perform - Project.remove_gitlab_exports! - end -end diff --git a/app/workers/import_export_project_cleanup_worker.rb b/app/workers/import_export_project_cleanup_worker.rb new file mode 100644 index 00000000000..72e3a9ae734 --- /dev/null +++ b/app/workers/import_export_project_cleanup_worker.rb @@ -0,0 +1,9 @@ +class ImportExportProjectCleanupWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform + ImportExportCleanUpService.new.execute + end +end diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 49130f37b31..deac3b0f0f9 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -287,9 +287,9 @@ Settings.cron_jobs['admin_email_worker']['job_class'] = 'AdminEmailWorker' Settings.cron_jobs['repository_archive_cache_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['repository_archive_cache_worker']['cron'] ||= '0 * * * *' Settings.cron_jobs['repository_archive_cache_worker']['job_class'] = 'RepositoryArchiveCacheWorker' -Settings.cron_jobs['gitlab_remove_project_export_worker'] ||= Settingslogic.new({}) -Settings.cron_jobs['gitlab_remove_project_export_worker']['cron'] ||= '0 * * * *' -Settings.cron_jobs['gitlab_remove_project_export_worker']['job_class'] = 'GitlabRemoveProjectExportWorker' +Settings.cron_jobs['import_export_project_cleanup_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['import_export_project_cleanup_worker']['cron'] ||= '0 * * * *' +Settings.cron_jobs['import_export_project_cleanup_worker']['job_class'] = 'ImportExportProjectCleanupWorker' Settings.cron_jobs['requests_profiles_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['requests_profiles_worker']['cron'] ||= '0 0 * * *' Settings.cron_jobs['requests_profiles_worker']['job_class'] = 'RequestsProfilesWorker' diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index 48b2c43ac21..bb562bdcd2c 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -13,6 +13,10 @@ module Gitlab File.join(Settings.shared['path'], 'tmp/project_exports') end + def import_upload_path(filename:) + File.join(storage_path, 'uploads', filename) + end + def project_filename "project.json" end diff --git a/spec/services/import_export_clean_up_service_spec.rb b/spec/services/import_export_clean_up_service_spec.rb new file mode 100644 index 00000000000..130b9fc4b82 --- /dev/null +++ b/spec/services/import_export_clean_up_service_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +describe ImportExportCleanUpService, services: true do + describe '#execute' do + let(:service) { described_class.new } + + let(:tmp_import_export_folder) { 'tmp/project_exports' } + + context 'when the import/export directory does not exist' do + it 'does not remove any archives' do + path = '/invalid/path/' + stub_repository_downloads_path(path) + + expect(File).to receive(:directory?).with(path + tmp_import_export_folder).and_return(false).at_least(:once) + expect(service).not_to receive(:clean_up_export_files) + + service.execute + end + end + + context 'when the import/export directory exists' do + it 'removes old files' do + in_directory_with_files(mtime: 2.days.ago) do |dir, files| + service.execute + + files.each { |file| expect(File.exist?(file)).to eq false } + expect(File.directory?(dir)).to eq false + end + end + + it 'does not remove new files' do + in_directory_with_files(mtime: 2.hours.ago) do |dir, files| + service.execute + + files.each { |file| expect(File.exist?(file)).to eq true } + expect(File.directory?(dir)).to eq true + end + end + end + + def in_directory_with_files(mtime:) + Dir.mktmpdir do |tmpdir| + stub_repository_downloads_path(tmpdir) + dir = File.join(tmpdir, tmp_import_export_folder, 'subfolder') + FileUtils.mkdir_p(dir) + + files = FileUtils.touch(file_list(dir) + [dir], mtime: mtime) + + yield(dir, files) + end + end + + def stub_repository_downloads_path(path) + new_shared_settings = Settings.shared.merge('path' => path) + allow(Settings).to receive(:shared).and_return(new_shared_settings) + end + + def file_list(dir) + Array.new(5) do |num| + File.join(dir, "random-#{num}.tar.gz") + end + end + end +end -- cgit v1.2.1 From 4d716b765c66d51aa8026ce8622d704c2d52844a Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Thu, 4 Aug 2016 12:07:46 +0100 Subject: Underscore variable to camelCase --- app/assets/javascripts/gl_dropdown.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 356810c85cc..cc7e422fd89 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -53,9 +53,8 @@ if (this.options.remote) { clearTimeout(timeout); return timeout = setTimeout(function() { - var blur_field; - blur_field = this.shouldBlur(keyCode); - if (blur_field && this.filterInputBlur) { + var blurField = this.shouldBlur(keyCode); + if (blurField && this.filterInputBlur) { this.input.blur(); } return this.options.query(this.input.val(), function(data) { -- cgit v1.2.1 From 9772cd893eba9493037da572df39a3bfb992af59 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 4 Aug 2016 13:39:38 +0200 Subject: fix spec --- spec/services/import_export_clean_up_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/import_export_clean_up_service_spec.rb b/spec/services/import_export_clean_up_service_spec.rb index 130b9fc4b82..81b1d327696 100644 --- a/spec/services/import_export_clean_up_service_spec.rb +++ b/spec/services/import_export_clean_up_service_spec.rb @@ -44,7 +44,7 @@ describe ImportExportCleanUpService, services: true do dir = File.join(tmpdir, tmp_import_export_folder, 'subfolder') FileUtils.mkdir_p(dir) - files = FileUtils.touch(file_list(dir) + [dir], mtime: mtime) + files = FileUtils.touch(file_list(dir) + [dir], mtime: mtime.to_time) yield(dir, files) end -- cgit v1.2.1 From f15ed5f0a5c298a2f0eb5aaa6d848364133532a5 Mon Sep 17 00:00:00 2001 From: Herminio Torres Date: Thu, 4 Aug 2016 01:35:17 -0300 Subject: Fix Rename `add_users_into_project` and `projects_ids` We never add things `into` projects, we just add them `to` projects. So how about we rename this to `add_users_to_project`. Rename `projects_ids` to `project_ids` by following the convention of rails. --- CHANGELOG | 1 + app/models/members/project_member.rb | 6 +++--- app/models/project_team.rb | 2 +- lib/tasks/gitlab/bulk_add_permission.rake | 12 ++++++------ lib/tasks/gitlab/web_hook.rake | 4 ++-- spec/models/members/project_member_spec.rb | 4 ++-- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 886b7865e5b..339426f990f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,6 +2,7 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.11.0 (unreleased) - Fix don't pass a local variable called `i` to a partial. !20510 (herminiotorres) + - Fix rename `add_users_into_project` and `projects_ids`. !20512 (herminiotorres) - Fix the title of the toggle dropdown button. !5515 (herminiotorres) - Improve diff performance by eliminating redundant checks for text blobs - Convert switch icon into icon font (ClemMakesApps) diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb index f39afc61ce9..f176feddbad 100644 --- a/app/models/members/project_member.rb +++ b/app/models/members/project_member.rb @@ -21,19 +21,19 @@ class ProjectMember < Member # or symbol like :master representing role # # Ex. - # add_users_into_projects( + # add_users_to_projects( # project_ids, # user_ids, # ProjectMember::MASTER # ) # - # add_users_into_projects( + # add_users_to_projects( # project_ids, # user_ids, # :master # ) # - def add_users_into_projects(project_ids, user_ids, access, current_user = nil) + def add_users_to_projects(project_ids, user_ids, access, current_user = nil) access_level = if roles_hash.has_key?(access) roles_hash[access] elsif roles_hash.values.include?(access.to_i) diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 19fd082534c..d0a714cd6fc 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -34,7 +34,7 @@ class ProjectTeam end def add_users(users, access, current_user = nil) - ProjectMember.add_users_into_projects( + ProjectMember.add_users_to_projects( [project.id], users, access, diff --git a/lib/tasks/gitlab/bulk_add_permission.rake b/lib/tasks/gitlab/bulk_add_permission.rake index 5dbf7d61e06..83dd870fa31 100644 --- a/lib/tasks/gitlab/bulk_add_permission.rake +++ b/lib/tasks/gitlab/bulk_add_permission.rake @@ -4,13 +4,13 @@ namespace :gitlab do task all_users_to_all_projects: :environment do |t, args| user_ids = User.where(admin: false).pluck(:id) admin_ids = User.where(admin: true).pluck(:id) - projects_ids = Project.pluck(:id) + project_ids = Project.pluck(:id) - puts "Importing #{user_ids.size} users into #{projects_ids.size} projects" - ProjectMember.add_users_into_projects(projects_ids, user_ids, ProjectMember::DEVELOPER) + puts "Importing #{user_ids.size} users into #{project_ids.size} projects" + ProjectMember.add_users_to_projects(project_ids, user_ids, ProjectMember::DEVELOPER) - puts "Importing #{admin_ids.size} admins into #{projects_ids.size} projects" - ProjectMember.add_users_into_projects(projects_ids, admin_ids, ProjectMember::MASTER) + puts "Importing #{admin_ids.size} admins into #{project_ids.size} projects" + ProjectMember.add_users_to_projects(project_ids, admin_ids, ProjectMember::MASTER) end desc "GitLab | Add a specific user to all projects (as a developer)" @@ -18,7 +18,7 @@ namespace :gitlab do user = User.find_by(email: args.email) project_ids = Project.pluck(:id) puts "Importing #{user.email} users into #{project_ids.size} projects" - ProjectMember.add_users_into_projects(project_ids, Array.wrap(user.id), ProjectMember::DEVELOPER) + ProjectMember.add_users_to_projects(project_ids, Array.wrap(user.id), ProjectMember::DEVELOPER) end desc "GitLab | Add all users to all groups (admin users are added as owners)" diff --git a/lib/tasks/gitlab/web_hook.rake b/lib/tasks/gitlab/web_hook.rake index f467cc0ee29..49530e7a372 100644 --- a/lib/tasks/gitlab/web_hook.rake +++ b/lib/tasks/gitlab/web_hook.rake @@ -26,10 +26,10 @@ namespace :gitlab do namespace_path = ENV['NAMESPACE'] projects = find_projects(namespace_path) - projects_ids = projects.pluck(:id) + project_ids = projects.pluck(:id) puts "Removing webhooks with the url '#{web_hook_url}' ... " - count = WebHook.where(url: web_hook_url, project_id: projects_ids, type: 'ProjectHook').delete_all + count = WebHook.where(url: web_hook_url, project_id: project_ids, type: 'ProjectHook').delete_all puts "#{count} webhooks were removed." end diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb index ba622dfb9be..e7c0c506463 100644 --- a/spec/models/members/project_member_spec.rb +++ b/spec/models/members/project_member_spec.rb @@ -101,7 +101,7 @@ describe ProjectMember, models: true do end end - describe '.add_users_into_projects' do + describe '.add_users_to_projects' do before do @project_1 = create :project @project_2 = create :project @@ -109,7 +109,7 @@ describe ProjectMember, models: true do @user_1 = create :user @user_2 = create :user - ProjectMember.add_users_into_projects( + ProjectMember.add_users_to_projects( [@project_1.id, @project_2.id], [@user_1.id, @user_2.id], ProjectMember::MASTER -- cgit v1.2.1 From b3a9ad6a57d34632cd011b242faad4584c25274f Mon Sep 17 00:00:00 2001 From: dixpac Date: Thu, 4 Aug 2016 11:53:23 +0200 Subject: Add description to text/plain emails Added to * new_issue_email * new_merge_request_email --- CHANGELOG | 1 + app/views/notify/new_issue_email.text.erb | 2 ++ app/views/notify/new_merge_request_email.text.erb | 2 ++ 3 files changed, 5 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index b36997dcba9..c6efbfa7ea4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -66,6 +66,7 @@ v 8.11.0 (unreleased) - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) - Fix RequestProfiler::Middleware error when code is reloaded in development - Catch what warden might throw when profiling requests to re-throw it + - Add description to new_issue email and new_merge_request_email in text/plain content type. !5663 (dixpac) - Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker v 8.10.4 (unreleased) diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb index fc64c98038b..ca5c2f2688c 100644 --- a/app/views/notify/new_issue_email.text.erb +++ b/app/views/notify/new_issue_email.text.erb @@ -3,3 +3,5 @@ New Issue was created. Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %> Author: <%= @issue.author_name %> Assignee: <%= @issue.assignee_name %> + +<%= @issue.description %> diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb index d4aad8d1862..3c8f178ac77 100644 --- a/app/views/notify/new_merge_request_email.text.erb +++ b/app/views/notify/new_merge_request_email.text.erb @@ -6,3 +6,5 @@ New Merge Request <%= @merge_request.to_reference %> Author: <%= @merge_request.author_name %> Assignee: <%= @merge_request.assignee_name %> +<%= @merge_request.description %> + -- cgit v1.2.1 From e9015b3541cbd0641a38ec0056e81591dda84688 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 4 Aug 2016 16:00:18 +0300 Subject: Set consistency in list text height css Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/lists.scss | 11 ++++++++--- app/assets/stylesheets/framework/variables.scss | 2 ++ app/assets/stylesheets/pages/groups.scss | 8 +------- app/assets/stylesheets/pages/projects.scss | 8 +------- 4 files changed, 12 insertions(+), 17 deletions(-) diff --git a/app/assets/stylesheets/framework/lists.scss b/app/assets/stylesheets/framework/lists.scss index 2c40ec430ca..965fcc06518 100644 --- a/app/assets/stylesheets/framework/lists.scss +++ b/app/assets/stylesheets/framework/lists.scss @@ -114,6 +114,12 @@ ul.content-list { font-size: $list-font-size; color: $list-text-color; + &.no-description { + .title { + line-height: $list-text-height; + } + } + .title { font-weight: 600; } @@ -134,12 +140,11 @@ ul.content-list { } .controls { - padding-top: 1px; float: right; > .control-text { margin-right: $gl-padding-top; - line-height: 40px; + line-height: $list-text-height; &:last-child { margin-right: 0; @@ -150,7 +155,7 @@ ul.content-list { > .btn-group { margin-right: $gl-padding-top; display: inline-block; - margin-top: 4px; + margin-top: 3px; margin-bottom: 4px; &:last-child { diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 1882d4e888d..06cd80bf3e4 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -43,6 +43,8 @@ $gl-header-color: $gl-title-color; $list-font-size: $gl-font-size; $list-title-color: $gl-title-color; $list-text-color: $gl-text-color; +$list-text-height: 42px; + /* * Markdown diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index 2a3acc3eb4c..b657ca47d38 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -23,15 +23,9 @@ } .group-row { - &.no-description { - .group-name { - line-height: 44px; - } - } - .stats { float: right; - line-height: 44px; + line-height: $list-text-height; color: $gl-gray; span { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 4409477916f..d91c8e61165 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -512,18 +512,12 @@ pre.light-well { .project-row { border-color: $table-border-color; - &.no-description { - .project { - line-height: 40px; - } - } - .project-full-name { @include str-truncated; } .controls { - line-height: 40px; + line-height: $list-text-height; a:hover { text-decoration: none; -- cgit v1.2.1 From 4e0c62c69631e4553618bf05d42ae6f8d38684ac Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 4 Aug 2016 16:31:10 +0300 Subject: Remove unnecessary empty line after css var Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/framework/variables.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 06cd80bf3e4..ca720022539 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -45,7 +45,6 @@ $list-title-color: $gl-title-color; $list-text-color: $gl-text-color; $list-text-height: 42px; - /* * Markdown */ -- cgit v1.2.1 From 705085db0c3b869f62f1b0f742686cc2082001fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 4 Aug 2016 16:00:31 +0200 Subject: Move abilities by subject class to a dedicated method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This will avoid lame conflicts when merging CE to EE Signed-off-by: Rémy Coutable --- app/models/ability.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/ability.rb b/app/models/ability.rb index d95a2507199..d9113ffd99a 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -6,6 +6,10 @@ class Ability return [] unless user.is_a?(User) return [] if user.blocked? + abilities_by_subject_class(user: user, subject: subject) + end + + def abilities_by_subject_class(user:, subject:) case subject when CommitStatus then commit_status_abilities(user, subject) when Project then project_abilities(user, subject) -- cgit v1.2.1 From f301d547c2a3ca2a3f1fd07f9ddc4eb451e70244 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 4 Aug 2016 15:27:53 +0100 Subject: Add a data migration to fix some missing timestamps in the members table (again) --- ...20160804150737_add_timestamps_to_members_again.rb | 20 ++++++++++++++++++++ db/schema.rb | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20160804150737_add_timestamps_to_members_again.rb diff --git a/db/migrate/20160804150737_add_timestamps_to_members_again.rb b/db/migrate/20160804150737_add_timestamps_to_members_again.rb new file mode 100644 index 00000000000..60f4e7af90f --- /dev/null +++ b/db/migrate/20160804150737_add_timestamps_to_members_again.rb @@ -0,0 +1,20 @@ +# rubocop:disable all +# 20141121133009_add_timestamps_to_members.rb was meant to ensure that all +# rows in the members table had created_at and updated_at set, following an +# error in a previous migration. This failed to set all rows in at least one +# case: https://gitlab.com/gitlab-org/gitlab-ce/issues/20568 +# +# Why this happened is lost in the mists of time, so repeat the SQL query +# without speculation, just in case more than one person was affected. +class AddTimestampsToMembersAgain < ActiveRecord::Migration + + def up + execute "UPDATE members SET created_at = NOW() WHERE created_at IS NULL" + execute "UPDATE members SET updated_at = NOW() WHERE updated_at IS NULL" + end + + def down + # no change + end + +end diff --git a/db/schema.rb b/db/schema.rb index dc28842758a..71980a6d51f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20160802010328) do +ActiveRecord::Schema.define(version: 20160804150737) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" -- cgit v1.2.1 From 3ed6473352f3332d0e2ec9d65e777fdc3e4a06e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 4 Aug 2016 16:41:36 +0200 Subject: Update CHANGELOG for 8.10.4 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7381a70e3d7..77bcea54cf9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -69,11 +69,13 @@ v 8.11.0 (unreleased) - Catch what warden might throw when profiling requests to re-throw it - Speed up and reduce memory usage of Commit#repo_changes, Repository#expire_avatar_cache and IrkerWorker -v 8.10.4 (unreleased) - - Fix Import/Export error checking versions +v 8.10.5 (unreleased) -v 8.10.4 (unreleased) - - Fix Import/Export project import not working in HA mode +v 8.10.4 + - Don't close referenced upstream issues from a forked project. + - Fixes issue with dropdowns `enter` key not working correctly. !5544 + - Fix Import/Export project import not working in HA mode. !5618 + - Fix Import/Export error checking versions. !5638 v 8.10.3 - Fix Import/Export issue importing milestones and labels not associated properly. !5426 -- cgit v1.2.1 From c462dcec4df0314b468178f9a6d1f9bbf5cd029d Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 4 Aug 2016 17:17:31 +0200 Subject: Added guide about migrations and downtime This guide describes various commonly used operations and the impact they have on the availability of a GitLab instance. --- doc/development/README.md | 1 + doc/development/what_requires_downtime.md | 153 ++++++++++++++++++++++++++++++ 2 files changed, 154 insertions(+) create mode 100644 doc/development/what_requires_downtime.md diff --git a/doc/development/README.md b/doc/development/README.md index 11aa50b89af..7b5f7ff8ad3 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -31,6 +31,7 @@ - [Rake tasks](rake_tasks.md) for development - [Shell commands](shell_commands.md) in the GitLab codebase - [Sidekiq debugging](sidekiq_debugging.md) +- [What requires downtime?](what_requires_downtime.md) ## Compliance diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md new file mode 100644 index 00000000000..abd693cf72d --- /dev/null +++ b/doc/development/what_requires_downtime.md @@ -0,0 +1,153 @@ +# What requires downtime? + +When working with a database certain operations can be performed without taking +GitLab offline, others do require a downtime period. This guide describes +various operations and their impact. + +## Adding Columns + +On PostgreSQL you can safely add a new column to an existing table as long as it +does **not** have a default value. For example, this query would not require +downtime: + +```sql +ALTER TABLE projects ADD COLUMN random_value int; +``` + +Add a column _with_ a default however does require downtime. For example, +consider this query: + +```sql +ALTER TABLE projects ADD COLUMN random_value int DEFAULT 42; +``` + +This requires updating every single row in the `projects` table so that +`random_value` is set to `42` by default. This requires updating all rows and +indexes in a table. This in turn acquires enough locks on the table for it to +effectively block any other queries. + +As of MySQL 5.6 adding a column to a table is still quite an expensive +operation, even when using `ALGORITHM=INPLACE` and `LOCK=NONE`. This means +downtime _may_ be required when modifying large tables as otherwise the +operation could potentially take hours to complete. + +## Dropping Columns + +On PostgreSQL you can safely remove an existing column without the need for +downtime. When you drop a column in PostgreSQL it's not immediately removed, +instead it is simply disabled. The data is removed on the next vacuum run. + +On MySQL this operation requires downtime. + +While database wise dropping a column may be fine on PostgreSQL this operation +still requires downtime because the application code may still be using the +column that was removed. For example, consider the following migration: + +```ruby +class MyMigration < ActiveRecord::Migration + def change + remove_column :projects, :dummy + end +end +``` + +Now imagine that the GitLab instance is running and actively uses the `dummy` +column. If we were to run the migration this would result in the GitLab instance +producing errors whenever it tries to use the `dummy` column. + +As a result of the above downtime _is_ required when removing a column, even +when using PostgreSQL. + +## Changing Column Constraints + +Generally changing column constraints requires checking all rows in the table to +see if they meet the new constraint, unless a constraint is _removed_. For +example, changing a column that previously allowed NULL values to not allow NULL +values requires the database to verify all existing rows. + +The specific behaviour varies a bit between databases but in general the safest +approach is to assume changing constraints requires downtime. + +## Changing Column Types + +This operation requires downtime. + +## Adding Indexes + +Adding indexes is an expensive process that blocks INSERT and UPDATE queries for +the duration. When using PostgreSQL one can work arounds this by using the +`CONCURRENTLY` option: + +```sql +CREATE INDEX CONCURRENTLY index_name ON projects (column_name); +``` + +Migrations can take advantage of this by using the method +`add_concurrent_index`. For example: + +```ruby +class MyMigration < ActiveRecord::Migration + def change + add_concurrent_index :projects, :column_name + end +end +``` + +When running this on PostgreSQL the `CONCURRENTLY` option mentioned above is +used. On MySQL this method produces a regular `CREATE INDEX` query. + +MySQL doesn't really have a workaround for this. Supposedly it _can_ create +indexes without the need for downtime but only for variable width columns. The +details on this are a bit sketchy. Since it's better to be safe than sorry one +should assume that adding indexes requires downtime on MySQL. + +## Dropping Indexes + +Dropping an index does not require downtime on both PostgreSQL and MySQL. + +## Adding Tables + +This operation is safe as there's no code using the table just yet. + +## Dropping Tables + +This operation requires downtime as application code may still be using the +table. + +## Adding Foreign Keys + +Adding foreign keys acquires an exclusive lock on both the source and target +tables in PostgreSQL. This requires downtime as otherwise the entire application +grinds to a halt for the duration of the operation. + +On MySQL this operation also requires downtime _unless_ foreign key checks are +disabled. Because this means checks aren't enforced this is not ideal, as such +one should assume MySQL also requires downtime. + +## Removing Foreign Keys + +This operation should not require downtime on both PostgreSQL and MySQL. + +## Updating Data + +Updating data should generally be safe. The exception to this is data that's +being migrated from one version to another while the application still produces +data in the old version. + +For example, imagine the application writes the string `'dog'` to a column but +it really is meant to write `'cat'` instead. One might think that the following +migration is all that is needed to solve this problem: + +```ruby +class MyMigration < ActiveRecord::Migration + def up + execute("UPDATE some_table SET column = 'cat' WHERE column = 'dog';") + end +end +``` + +Unfortunately this is not enough. Because the application is still running and +using the old value this may result in the table still containing rows where +`column` is set to `dog`, even after the migration finished. + +In these cases downtime _is_ required, even for rarely updated tables. -- cgit v1.2.1 From 98260f36cf697665509b89288c4208007d8ad6ce Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 4 Aug 2016 17:01:47 +0100 Subject: Add missing DOWNTIME constant to the AddTimestampsToMembersAgain migration --- db/migrate/20160804150737_add_timestamps_to_members_again.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/db/migrate/20160804150737_add_timestamps_to_members_again.rb b/db/migrate/20160804150737_add_timestamps_to_members_again.rb index 60f4e7af90f..6691ba57fbb 100644 --- a/db/migrate/20160804150737_add_timestamps_to_members_again.rb +++ b/db/migrate/20160804150737_add_timestamps_to_members_again.rb @@ -7,6 +7,7 @@ # Why this happened is lost in the mists of time, so repeat the SQL query # without speculation, just in case more than one person was affected. class AddTimestampsToMembersAgain < ActiveRecord::Migration + DOWNTIME = false def up execute "UPDATE members SET created_at = NOW() WHERE created_at IS NULL" -- cgit v1.2.1 From 9955dc29a863ad997efe2926875c29f963ba94d4 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Date: Tue, 12 Jul 2016 17:52:36 -0500 Subject: Update timeago to shorter representation --- CHANGELOG | 1 + app/assets/javascripts/application.js | 6 ++-- .../javascripts/lib/utils/datetime_utility.js | 38 +++++++++++++++++++++- app/helpers/application_helper.rb | 8 +++-- .../projects/ci/pipelines/_pipeline.html.haml | 2 +- spec/helpers/application_helper_spec.rb | 17 ++++++++-- 6 files changed, 64 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 77bcea54cf9..9e5d00c01f8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -19,6 +19,7 @@ v 8.11.0 (unreleased) - Add "No one can push" as an option for protected branches. !5081 - Improve performance of AutolinkFilter#text_parse by using XPath - Environments have an url to link to + - Update `timeago` plugin to use multiple string/locale settings - Remove unused images (ClemMakesApps) - Limit git rev-list output count to one in forced push check - Clean up unused routes (Josef Strzibny) diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js index 127e568adc9..f1aab067351 100644 --- a/app/assets/javascripts/application.js +++ b/app/assets/javascripts/application.js @@ -287,7 +287,7 @@ $('.page-with-sidebar').toggleClass('page-sidebar-collapsed page-sidebar-expanded').removeClass('page-sidebar-pinned'); $('.navbar-fixed-top').removeClass('header-pinned-nav'); } - return $document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) { + $document.off('click', '.js-nav-pin').on('click', '.js-nav-pin', function(e) { var $page, $pinBtn, $tooltip, $topNav, doPinNav, tooltipText; e.preventDefault(); $pinBtn = $(e.currentTarget); @@ -315,6 +315,8 @@ $tooltip.find('.tooltip-inner').text(tooltipText); return $pinBtn.attr('title', tooltipText).tooltip('fixTitle'); }); - }); + // Custom time ago + gl.utils.shortTimeAgo($('.js-short-timeago')); + }); }).call(this); diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index e817261f210..10afa7e4329 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -8,13 +8,16 @@ base.utils = {}; } w.gl.utils.days = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']; + w.gl.utils.formatDate = function(datetime) { return dateFormat(datetime, 'mmm d, yyyy h:MMtt Z'); }; + w.gl.utils.getDayName = function(date) { return this.days[date.getDay()]; }; - return w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago) { + + w.gl.utils.localTimeAgo = function($timeagoEls, setTimeago) { if (setTimeago == null) { setTimeago = true; } @@ -31,6 +34,39 @@ }); } }; + + w.gl.utils.shortTimeAgo = function($el) { + var shortLocale, tmpLocale; + shortLocale = { + prefixAgo: null, + prefixFromNow: null, + suffixAgo: 'ago', + suffixFromNow: 'from now', + seconds: '1 min', + minute: '1 min', + minutes: '%d mins', + hour: '1 hr', + hours: '%d hrs', + day: '1 day', + days: '%d days', + month: '1 month', + months: '%d months', + year: '1 year', + years: '%d years', + wordSeparator: ' ', + numbers: [] + }; + tmpLocale = $.timeago.settings.strings; + $el.each(function(el) { + var $el1; + $el1 = $(this); + return $el1.attr('title', gl.utils.formatDate($el.attr('datetime'))); + }); + $.timeago.settings.strings = shortLocale; + $el.timeago(); + $.timeago.settings.strings = tmpLocale; + }; + })(window); }).call(this); diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 50de93d4bdf..c3613bc67dd 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -163,9 +163,13 @@ module ApplicationHelper # `html_class` argument is provided. # # Returns an HTML-safe String - def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false) + def time_ago_with_tooltip(time, placement: 'top', html_class: '', skip_js: false, short_format: false) + css_classes = short_format ? 'js-short-timeago' : 'js-timeago' + css_classes << " #{html_class}" unless html_class.blank? + css_classes << ' js-timeago-pending' unless skip_js + element = content_tag :time, time.to_s, - class: "#{html_class} js-timeago #{"js-timeago-pending" unless skip_js}", + class: css_classes, datetime: time.to_time.getutc.iso8601, title: time.to_time.in_time_zone.to_s(:medium), data: { toggle: 'tooltip', placement: placement, container: 'body' } diff --git a/app/views/projects/ci/pipelines/_pipeline.html.haml b/app/views/projects/ci/pipelines/_pipeline.html.haml index 558c35553da..9a594877803 100644 --- a/app/views/projects/ci/pipelines/_pipeline.html.haml +++ b/app/views/projects/ci/pipelines/_pipeline.html.haml @@ -53,7 +53,7 @@ - if pipeline.finished_at %p.finished-at = icon("calendar") - #{time_ago_with_tooltip(pipeline.finished_at)} + #{time_ago_with_tooltip(pipeline.finished_at, short_format: true, skip_js: true)} %td.pipeline-actions .controls.hidden-xs.pull-right diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index bb28866f010..3e15a137e33 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -218,12 +218,12 @@ describe ApplicationHelper do end it 'includes a default js-timeago class' do - expect(element.attr('class')).to eq 'time_ago js-timeago js-timeago-pending' + expect(element.attr('class')).to eq 'js-timeago js-timeago-pending' end it 'accepts a custom html_class' do expect(element(html_class: 'custom_class').attr('class')). - to eq 'custom_class js-timeago js-timeago-pending' + to eq 'js-timeago custom_class js-timeago-pending' end it 'accepts a custom tooltip placement' do @@ -244,6 +244,19 @@ describe ApplicationHelper do it 'converts to Time' do expect { helper.time_ago_with_tooltip(Date.today) }.not_to raise_error end + + it 'add class for the short format and includes inline script' do + timeago_element = element(short_format: 'short') + expect(timeago_element.attr('class')).to eq 'js-short-timeago js-timeago-pending' + script_element = timeago_element.next_element + expect(script_element.name).to eq 'script' + end + + it 'add class for the short format and does not include inline script' do + timeago_element = element(short_format: 'short', skip_js: true) + expect(timeago_element.attr('class')).to eq 'js-short-timeago' + expect(timeago_element.next_element).to eq nil + end end describe 'render_markup' do -- cgit v1.2.1 From c9e15be9ab37f2e209d6f51a19fb0e0e11f17db9 Mon Sep 17 00:00:00 2001 From: winniehell Date: Fri, 5 Aug 2016 00:53:08 +0200 Subject: Add failing test for #7032 --- spec/lib/banzai/filter/relative_link_filter_spec.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb index 224baca8030..bda8d2ce38a 100644 --- a/spec/lib/banzai/filter/relative_link_filter_spec.rb +++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb @@ -84,6 +84,11 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do to eq "/#{project_path}/blob/#{ref}/doc/api/README.md" end + it 'ignores absolute URLs with two leading slashes' do + doc = filter(link('//doc/api/README.md')) + expect(doc.at_css('a')['href']).to eq '//doc/api/README.md' + end + it 'rebuilds relative URL for a file in the repo' do doc = filter(link('doc/api/README.md')) expect(doc.at_css('a')['href']). -- cgit v1.2.1 From b791dcc05b379a64c1370bc4be8d0aac60b9c31b Mon Sep 17 00:00:00 2001 From: winniehell Date: Fri, 5 Aug 2016 01:22:50 +0200 Subject: Ignore URLs starting with // (!5677) --- CHANGELOG | 1 + lib/banzai/filter/relative_link_filter.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 77bcea54cf9..78b1565668a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 8.11.0 (unreleased) - Convert switch icon into icon font (ClemMakesApps) - Remove magic comments (`# encoding: UTF-8`) from Ruby files. !5456 (winniehell) - Add support for relative links starting with ./ or / to RelativeLinkFilter (winniehell) + - Ignore URLs starting with // in Markdown links !5677 (winniehell) - Fix CI status icon link underline (ClemMakesApps) - The Repository class is now instrumented - Cache the commit author in RequestStore to avoid extra lookups in PostReceive diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb index 5b73fc8fcee..46762d401fb 100644 --- a/lib/banzai/filter/relative_link_filter.rb +++ b/lib/banzai/filter/relative_link_filter.rb @@ -35,6 +35,7 @@ module Banzai def process_link_attr(html_attr) return if html_attr.blank? + return if html_attr.value.start_with?('//') uri = URI(html_attr.value) if uri.relative? && uri.path.present? @@ -92,7 +93,7 @@ module Banzai parts = request_path.split('/') parts.pop if uri_type(request_path) != :tree - path.sub!(%r{^\./}, '') + path.sub!(%r{\A\./}, '') while path.start_with?('../') parts.pop -- cgit v1.2.1 From 3bf62c9d5ad26aabffe764b385b6acb7be5a9898 Mon Sep 17 00:00:00 2001 From: winniehell Date: Mon, 25 Jul 2016 00:58:34 +0200 Subject: Add examples to repository files API (!5465) --- doc/api/repository_files.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/doc/api/repository_files.md b/doc/api/repository_files.md index 623063f357b..1b8ee88b4ed 100644 --- a/doc/api/repository_files.md +++ b/doc/api/repository_files.md @@ -12,6 +12,10 @@ Allows you to receive information about file in repository like name, size, cont GET /projects/:id/repository/files ``` +```bash +curl -X GET -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/models/key.rb&ref=master' +``` + Example response: ```json @@ -39,6 +43,10 @@ Parameters: POST /projects/:id/repository/files ``` +```bash +curl -X POST -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&content=some%20content&commit_message=create%20a%20new%20file' +``` + Example response: ```json @@ -62,6 +70,10 @@ Parameters: PUT /projects/:id/repository/files ``` +```bash +curl -X PUT -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&content=some%20other%20content&commit_message=update%20file' +``` + Example response: ```json @@ -94,6 +106,10 @@ Currently gitlab-shell has a boolean return code, preventing GitLab from specify DELETE /projects/:id/repository/files ``` +```bash +curl -X PUT -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' 'https://gitlab.example.com/api/v3/projects/13083/repository/files?file_path=app/project.rb&branch_name=master&commit_message=delete%20file' +``` + Example response: ```json -- cgit v1.2.1 From 9ef1ad054885917651120ea0ac2666adfb490e66 Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Tue, 2 Aug 2016 14:14:19 +0200 Subject: Bump gitlab_git to lazy load compare commits --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 7465230fafe..5f4a3294e67 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -64,6 +64,7 @@ v 8.11.0 (unreleased) - Make error pages responsive (Takuya Noguchi) - Fix skip_repo parameter being ignored when destroying a namespace - Change requests_profiles resource constraint to catch virtually any file + - Bump gitlab_git to lazy load compare commits - Reduce number of queries made for merge_requests/:id/diffs - Sensible state specific default sort order for issues and merge requests !5453 (tomb0y) - Fix RequestProfiler::Middleware error when code is reloaded in development diff --git a/Gemfile b/Gemfile index 5d113db686f..8f94ee72a32 100644 --- a/Gemfile +++ b/Gemfile @@ -53,7 +53,7 @@ gem 'browser', '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem 'gitlab_git', '~> 10.4.3' +gem 'gitlab_git', '~> 10.4.5' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes diff --git a/Gemfile.lock b/Gemfile.lock index 460a6c409e7..870f9397b9e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -278,7 +278,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) - gitlab_git (10.4.3) + gitlab_git (10.4.5) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -870,7 +870,7 @@ DEPENDENCIES github-linguist (~> 4.7.0) github-markup (~> 1.4) gitlab-flowdock-git-hook (~> 1.0.1) - gitlab_git (~> 10.4.3) + gitlab_git (~> 10.4.5) gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) -- cgit v1.2.1 From ef589219d2aecc9caeaf53e4e9d5eb49f6fb590f Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 5 Aug 2016 17:19:37 +0200 Subject: Add 'run tests' docs from GDK --- doc/development/rake_tasks.md | 30 ++++++++++++++++++++++++++---- 1 file changed, 26 insertions(+), 4 deletions(-) diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index 8852dbcb19e..a7175f3f87e 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -14,11 +14,33 @@ Note: `db:setup` calls `db:seed` but this does nothing. ## Run tests -This runs all test suites present in GitLab. +In order to run the test you can use the following commands: +- `rake spinach` to run the spinach suite +- `rake spec` to run the rspec suite +- `rake teaspoon` to run the teaspoon test suite +- `rake gitlab:test` to run all the tests -``` -bundle exec rake test -``` +Note: Both `rake spinach` and `rake spec` takes significant time to pass. +Instead of running full test suite locally you can save a lot of time by running +a single test or directory related to your changes. After you submit merge request +CI will run full test suite for you. Green CI status in the merge request means +full test suite is passed. + +Note: You can't run `rspec .` since this will try to run all the `_spec.rb` +files it can find, also the ones in `/tmp` + +To run a single test file you can use: + +- `bundle exec rspec spec/controllers/commit_controller_spec.rb` for a rspec test +- `bundle exec spinach features/project/issues/milestones.feature` for a spinach test + +To run several tests inside one directory: + +- `bundle exec rspec spec/requests/api/` for the rspec tests if you want to test API only +- `bundle exec spinach features/profile/` for the spinach tests if you want to test only profile pages + +If you want to use [Spring](https://github.com/rails/spring) set +`ENABLE_SPRING=1` in your environment. ## Generate searchable docs for source code -- cgit v1.2.1 From a55aaabcf7970e15b574c3b9dc130f22113c403f Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 15 Jul 2016 10:24:23 +0100 Subject: Added changelog item for issuable form dropdowns --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index b6532cafbcd..48235e2c1f7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -169,6 +169,9 @@ v 8.10.0 - Fix check for New Branch button on Issue page. !4630 (winniehell) - Fix GFM autocomplete not working on wiki pages - Fixed enter key not triggering click on first row when searching in a dropdown + - Updated dropdowns in issuable form to use new GitLab dropdown style + - Make images fit to the size of the viewport !4810 + - Fix check for New Branch button on Issue page !4630 (winniehell) - Fix MR-auto-close text added to description. !4836 - Support U2F devices in Firefox. !5177 - Fix issue, preventing users w/o push access to sort tags. !5105 (redetection) -- cgit v1.2.1