From dffd33252f029901e33883935b20f6b0368d819b Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 25 Sep 2016 11:55:14 +0200 Subject: Move reply by email docs to a new location [ci skip] --- config/gitlab.yml.example | 2 +- doc/README.md | 2 +- doc/administration/reply_by_email.md | 302 +++++++++++++++++++ doc/administration/reply_by_email_postfix_setup.md | 324 +++++++++++++++++++++ doc/administration/restart_gitlab.md | 2 +- doc/incoming_email/README.md | 303 +------------------ doc/incoming_email/postfix.md | 322 +------------------- doc/install/installation.md | 2 +- lib/tasks/gitlab/check.rake | 6 +- 9 files changed, 635 insertions(+), 630 deletions(-) create mode 100644 doc/administration/reply_by_email.md create mode 100644 doc/administration/reply_by_email_postfix_setup.md diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 1470a6e2550..b26c9f7ccc9 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -111,7 +111,7 @@ production: &base ## Reply by email # Allow users to comment on issues and merge requests by replying to notification emails. - # For documentation on how to set this up, see http://doc.gitlab.com/ce/incoming_email/README.html + # For documentation on how to set this up, see http://doc.gitlab.com/ce/administration/reply_by_email.html incoming_email: enabled: false diff --git a/doc/README.md b/doc/README.md index dd0eb97489e..cb378e68c60 100644 --- a/doc/README.md +++ b/doc/README.md @@ -42,7 +42,7 @@ - [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [Update](update/README.md) Update guides to upgrade your installation. - [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. -- [Reply by email](incoming_email/README.md) Allow users to comment on issues and merge requests by replying to notification emails. +- [Reply by email](administration/reply_by_email.md) Allow users to comment on issues and merge requests by replying to notification emails. - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE. - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. diff --git a/doc/administration/reply_by_email.md b/doc/administration/reply_by_email.md new file mode 100644 index 00000000000..5a9a1582877 --- /dev/null +++ b/doc/administration/reply_by_email.md @@ -0,0 +1,302 @@ +# Reply by email + +GitLab can be set up to allow users to comment on issues and merge requests by +replying to notification emails. + +## Requirement + +Reply by email requires an IMAP-enabled email account. GitLab allows you to use +three strategies for this feature: +- using email sub-addressing +- using a dedicated email address +- using a catch-all mailbox + +### Email sub-addressing + +**If your provider or server supports email sub-addressing, we recommend using it.** + +[Sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing) is +a feature where any email to `user+some_arbitrary_tag@example.com` will end up +in the mailbox for `user@example.com`, and is supported by providers such as +Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix +mail server which you can run on-premises. + +### Dedicated email address + +This solution is really simple to set up: you just have to create an email +address dedicated to receive your users' replies to GitLab notifications. + +### Catch-all mailbox + +A [catch-all mailbox](https://en.wikipedia.org/wiki/Catch-all) for a domain will +"catch all" the emails addressed to the domain that do not exist in the mail +server. + +## How it works? + +### 1. GitLab sends a notification email + +When GitLab sends a notification and Reply by email is enabled, the `Reply-To` +header is set to the address defined in your GitLab configuration, with the +`%{key}` placeholder (if present) replaced by a specific "reply key". In +addition, this "reply key" is also added to the `References` header. + +### 2. You reply to the notification email + +When you reply to the notification email, your email client will: + +- send the email to the `Reply-To` address it got from the notification email +- set the `In-Reply-To` header to the value of the `Message-ID` header from the + notification email +- set the `References` header to the value of the `Message-ID` plus the value of + the notification email's `References` header. + +### 3. GitLab receives your reply to the notification email + +When GitLab receives your reply, it will look for the "reply key" in the +following headers, in this order: + +1. the `To` header +1. the `References` header + +If it finds a reply key, it will be able to leave your reply as a comment on +the entity the notification was about (issue, merge request, commit...). + +For more details about the `Message-ID`, `In-Reply-To`, and `References headers`, +please consult [RFC 5322](https://tools.ietf.org/html/rfc5322#section-3.6.4). + +## Set it up + +If you want to use Gmail / Google Apps with Reply by email, make sure you have +[IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) +and [allowed less secure apps to access the account](https://support.google.com/accounts/answer/6010255). + +To set up a basic Postfix mail server with IMAP access on Ubuntu, follow +[these instructions](./postfix.md). + +### Omnibus package installations + +1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the + feature and fill in the details for your specific IMAP server and email account: + + ```ruby + # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com + gitlab_rails['incoming_email_enabled'] = true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + gitlab_rails['incoming_email_email'] = "incoming" + # Email account password + gitlab_rails['incoming_email_password'] = "[REDACTED]" + + # IMAP server host + gitlab_rails['incoming_email_host'] = "gitlab.example.com" + # IMAP server port + gitlab_rails['incoming_email_port'] = 143 + # Whether the IMAP server uses SSL + gitlab_rails['incoming_email_ssl'] = false + # Whether the IMAP server uses StartTLS + gitlab_rails['incoming_email_start_tls'] = false + + # The mailbox where incoming mail will end up. Usually "inbox". + gitlab_rails['incoming_email_mailbox_name'] = "inbox" + ``` + + ```ruby + # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + gitlab_rails['incoming_email_enabled'] = true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" + # Email account password + gitlab_rails['incoming_email_password'] = "[REDACTED]" + + # IMAP server host + gitlab_rails['incoming_email_host'] = "imap.gmail.com" + # IMAP server port + gitlab_rails['incoming_email_port'] = 993 + # Whether the IMAP server uses SSL + gitlab_rails['incoming_email_ssl'] = true + # Whether the IMAP server uses StartTLS + gitlab_rails['incoming_email_start_tls'] = false + + # The mailbox where incoming mail will end up. Usually "inbox". + gitlab_rails['incoming_email_mailbox_name'] = "inbox" + ``` + +1. Reconfigure GitLab and restart mailroom for the changes to take effect: + + ```sh + sudo gitlab-ctl reconfigure + sudo gitlab-ctl restart mailroom + ``` + +1. Verify that everything is configured correctly: + + ```sh + sudo gitlab-rake gitlab:incoming_email:check + ``` + +1. Reply by email should now be working. + +### Installations from source + +1. Go to the GitLab installation directory: + + ```sh + cd /home/git/gitlab + ``` + +1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature + and fill in the details for your specific IMAP server and email account: + + ```sh + sudo editor config/gitlab.yml + ``` + + ```yaml + # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com + incoming_email: + enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + address: "incoming+%{key}@gitlab.example.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "incoming" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "gitlab.example.com" + # IMAP server port + port: 143 + # Whether the IMAP server uses SSL + ssl: false + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + ``` + + ```yaml + # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + incoming_email: + enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + address: "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "gitlab-incoming@gmail.com" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "imap.gmail.com" + # IMAP server port + port: 993 + # Whether the IMAP server uses SSL + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + ``` + +1. Enable `mail_room` in the init script at `/etc/default/gitlab`: + + ```sh + sudo mkdir -p /etc/default + echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab + ``` + +1. Restart GitLab: + + ```sh + sudo service gitlab restart + ``` + +1. Verify that everything is configured correctly: + + ```sh + sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production + ``` + +1. Reply by email should now be working. + +### Development + +1. Go to the GitLab installation directory. + +1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account: + + ```yaml + # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com + incoming_email: + enabled: true + + # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. + # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). + address: "gitlab-incoming+%{key}@gmail.com" + + # Email account username + # With third party providers, this is usually the full email address. + # With self-hosted email servers, this is usually the user part of the email address. + user: "gitlab-incoming@gmail.com" + # Email account password + password: "[REDACTED]" + + # IMAP server host + host: "imap.gmail.com" + # IMAP server port + port: 993 + # Whether the IMAP server uses SSL + ssl: true + # Whether the IMAP server uses StartTLS + start_tls: false + + # The mailbox where incoming mail will end up. Usually "inbox". + mailbox: "inbox" + ``` + + As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`. + +1. Uncomment the `mail_room` line in your `Procfile`: + + ```yaml + mail_room: bundle exec mail_room -q -c config/mail_room.yml + ``` + +1. Restart GitLab: + + ```sh + bundle exec foreman start + ``` + +1. Verify that everything is configured correctly: + + ```sh + bundle exec rake gitlab:incoming_email:check RAILS_ENV=development + ``` + +1. Reply by email should now be working. diff --git a/doc/administration/reply_by_email_postfix_setup.md b/doc/administration/reply_by_email_postfix_setup.md new file mode 100644 index 00000000000..22f10489a6c --- /dev/null +++ b/doc/administration/reply_by_email_postfix_setup.md @@ -0,0 +1,324 @@ +# Set up Postfix for Reply by email + +This document will take you through the steps of setting up a basic Postfix mail +server with IMAP authentication on Ubuntu, to be used with [Reply by email]. + +The instructions make the assumption that you will be using the email address `incoming@gitlab.example.com`, that is, username `incoming` on host `gitlab.example.com`. Don't forget to change it to your actual host when executing the example code snippets. + +## Configure your server firewall + +1. Open up port 25 on your server so that people can send email into the server over SMTP. +2. If the mail server is different from the server running GitLab, open up port 143 on your server so that GitLab can read email from the server over IMAP. + +## Install packages + +1. Install the `postfix` package if it is not installed already: + + ```sh + sudo apt-get install postfix + ``` + + When asked about the environment, select 'Internet Site'. When asked to confirm the hostname, make sure it matches `gitlab.example.com`. + +1. Install the `mailutils` package. + + ```sh + sudo apt-get install mailutils + ``` + +## Create user + +1. Create a user for incoming email. + + ```sh + sudo useradd -m -s /bin/bash incoming + ``` + +1. Set a password for this user. + + ```sh + sudo passwd incoming + ``` + + Be sure not to forget this, you'll need it later. + +## Test the out-of-the-box setup + +1. Connect to the local SMTP server: + + ```sh + telnet localhost 25 + ``` + + You should see a prompt like this: + + ```sh + Trying 127.0.0.1... + Connected to localhost. + Escape character is '^]'. + 220 gitlab.example.com ESMTP Postfix (Ubuntu) + ``` + + If you get a `Connection refused` error instead, verify that `postfix` is running: + + ```sh + sudo postfix status + ``` + + If it is not, start it: + + ```sh + sudo postfix start + ``` + +1. Send the new `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: + + ``` + ehlo localhost + mail from: root@localhost + rcpt to: incoming@localhost + data + Subject: Re: Some issue + + Sounds good! + . + quit + ``` + + _**Note:** The `.` is a literal period on its own line._ + + _**Note:** If you receive an error after entering `rcpt to: incoming@localhost` + then your Postfix `my_network` configuration is not correct. The error will + say 'Temporary lookup failure'. See + [Configure Postfix to receive email from the Internet](#configure-postfix-to-receive-email-from-the-internet)._ + +1. Check if the `incoming` user received the email: + + ```sh + su - incoming + mail + ``` + + You should see output like this: + + ``` + "/var/mail/incoming": 1 message 1 unread + >U 1 root@localhost 59/2842 Re: Some issue + ``` + + Quit the mail app: + + ```sh + q + ``` + +1. Log out of the `incoming` account and go back to being `root`: + + ```sh + logout + ``` + +## Configure Postfix to use Maildir-style mailboxes + +Courier, which we will install later to add IMAP authentication, requires mailboxes to have the Maildir format, rather than mbox. + +1. Configure Postfix to use Maildir-style mailboxes: + + ```sh + sudo postconf -e "home_mailbox = Maildir/" + ``` + +1. Restart Postfix: + + ```sh + sudo /etc/init.d/postfix restart + ``` + +1. Test the new setup: + + 1. Follow steps 1 and 2 of _[Test the out-of-the-box setup](#test-the-out-of-the-box-setup)_. + 1. Check if the `incoming` user received the email: + + ```sh + su - incoming + MAIL=/home/incoming/Maildir + mail + ``` + + You should see output like this: + + ``` + "/home/incoming/Maildir": 1 message 1 unread + >U 1 root@localhost 59/2842 Re: Some issue + ``` + + Quit the mail app: + + ```sh + q + ``` + + _**Note:** If `mail` returns an error `Maildir: Is a directory` then your + version of `mail` doesn't support Maildir style mailboxes. Install + `heirloom-mailx` by running `sudo apt-get install heirloom-mailx`. Then, + try the above steps again, substituting `heirloom-mailx` for the `mail` + command._ + +1. Log out of the `incoming` account and go back to being `root`: + + ```sh + logout + ``` + +## Install the Courier IMAP server + +1. Install the `courier-imap` package: + + ```sh + sudo apt-get install courier-imap + ``` + +## Configure Postfix to receive email from the internet + +1. Let Postfix know about the domains that it should consider local: + + ```sh + sudo postconf -e "mydestination = gitlab.example.com, localhost.localdomain, localhost" + ``` + +1. Let Postfix know about the IPs that it should consider part of the LAN: + + We'll assume `192.168.1.0/24` is your local LAN. You can safely skip this step if you don't have other machines in the same local network. + + ```sh + sudo postconf -e "mynetworks = 127.0.0.0/8, 192.168.1.0/24" + ``` + +1. Configure Postfix to receive mail on all interfaces, which includes the internet: + + ```sh + sudo postconf -e "inet_interfaces = all" + ``` + +1. Configure Postfix to use the `+` delimiter for sub-addressing: + + ```sh + sudo postconf -e "recipient_delimiter = +" + ``` + +1. Restart Postfix: + + ```sh + sudo service postfix restart + ``` + +## Test the final setup + +1. Test SMTP under the new setup: + + 1. Connect to the SMTP server: + + ```sh + telnet gitlab.example.com 25 + ``` + + You should see a prompt like this: + + ```sh + Trying 123.123.123.123... + Connected to gitlab.example.com. + Escape character is '^]'. + 220 gitlab.example.com ESMTP Postfix (Ubuntu) + ``` + + If you get a `Connection refused` error instead, make sure your firewall is setup to allow inbound traffic on port 25. + + 1. Send the `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: + + ``` + ehlo gitlab.example.com + mail from: root@gitlab.example.com + rcpt to: incoming@gitlab.example.com + data + Subject: Re: Some issue + + Sounds good! + . + quit + ``` + + (Note: The `.` is a literal period on its own line) + + 1. Check if the `incoming` user received the email: + + ```sh + su - incoming + MAIL=/home/incoming/Maildir + mail + ``` + + You should see output like this: + + ``` + "/home/incoming/Maildir": 1 message 1 unread + >U 1 root@gitlab.example.com 59/2842 Re: Some issue + ``` + + Quit the mail app: + + ```sh + q + ``` + + 1. Log out of the `incoming` account and go back to being `root`: + + ```sh + logout + ``` + +1. Test IMAP under the new setup: + + 1. Connect to the IMAP server: + + ```sh + telnet gitlab.example.com 143 + ``` + + You should see a prompt like this: + + ```sh + Trying 123.123.123.123... + Connected to mail.example.gitlab.com. + Escape character is '^]'. + - OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION] Courier-IMAP ready. Copyright 1998-2011 Double Precision, Inc. See COPYING for distribution information. + ``` + + 1. Sign in as the `incoming` user to test IMAP, by entering the following into the IMAP prompt: + + ``` + a login incoming PASSWORD + ``` + + Replace PASSWORD with the password you set on the `incoming` user earlier. + + You should see output like this: + + ``` + a OK LOGIN Ok. + ``` + + 1. Disconnect from the IMAP server: + + ```sh + a logout + ``` + +## Done! + +If all the tests were successful, Postfix is all set up and ready to receive email! Continue with the [Reply by email](./README.md) guide to configure GitLab. + +--- + +_This document was adapted from https://help.ubuntu.com/community/PostfixBasicSetupHowto, by contributors to the Ubuntu documentation wiki._ + +[reply by email]: reply_by_email.md diff --git a/doc/administration/restart_gitlab.md b/doc/administration/restart_gitlab.md index 483060395dd..b561c2f82aa 100644 --- a/doc/administration/restart_gitlab.md +++ b/doc/administration/restart_gitlab.md @@ -139,7 +139,7 @@ If you are using other init systems, like systemd, you can check the [omnibus-dl]: https://about.gitlab.com/downloads/ "Download the Omnibus packages" [install]: ../install/installation.md "Documentation to install GitLab from source" -[mailroom]: ../incoming_email/README.md "Used for replying by email in GitLab issues and merge requests" +[mailroom]: reply_by_email.md "Used for replying by email in GitLab issues and merge requests" [chef]: https://www.chef.io/chef/ "Chef official website" [src-service]: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/support/init.d/gitlab "GitLab init service file" [gl-recipes]: https://gitlab.com/gitlab-org/gitlab-recipes/tree/master/init "GitLab Recipes repository" diff --git a/doc/incoming_email/README.md b/doc/incoming_email/README.md index 5a9a1582877..db0f03f2c98 100644 --- a/doc/incoming_email/README.md +++ b/doc/incoming_email/README.md @@ -1,302 +1 @@ -# Reply by email - -GitLab can be set up to allow users to comment on issues and merge requests by -replying to notification emails. - -## Requirement - -Reply by email requires an IMAP-enabled email account. GitLab allows you to use -three strategies for this feature: -- using email sub-addressing -- using a dedicated email address -- using a catch-all mailbox - -### Email sub-addressing - -**If your provider or server supports email sub-addressing, we recommend using it.** - -[Sub-addressing](https://en.wikipedia.org/wiki/Email_address#Sub-addressing) is -a feature where any email to `user+some_arbitrary_tag@example.com` will end up -in the mailbox for `user@example.com`, and is supported by providers such as -Gmail, Google Apps, Yahoo! Mail, Outlook.com and iCloud, as well as the Postfix -mail server which you can run on-premises. - -### Dedicated email address - -This solution is really simple to set up: you just have to create an email -address dedicated to receive your users' replies to GitLab notifications. - -### Catch-all mailbox - -A [catch-all mailbox](https://en.wikipedia.org/wiki/Catch-all) for a domain will -"catch all" the emails addressed to the domain that do not exist in the mail -server. - -## How it works? - -### 1. GitLab sends a notification email - -When GitLab sends a notification and Reply by email is enabled, the `Reply-To` -header is set to the address defined in your GitLab configuration, with the -`%{key}` placeholder (if present) replaced by a specific "reply key". In -addition, this "reply key" is also added to the `References` header. - -### 2. You reply to the notification email - -When you reply to the notification email, your email client will: - -- send the email to the `Reply-To` address it got from the notification email -- set the `In-Reply-To` header to the value of the `Message-ID` header from the - notification email -- set the `References` header to the value of the `Message-ID` plus the value of - the notification email's `References` header. - -### 3. GitLab receives your reply to the notification email - -When GitLab receives your reply, it will look for the "reply key" in the -following headers, in this order: - -1. the `To` header -1. the `References` header - -If it finds a reply key, it will be able to leave your reply as a comment on -the entity the notification was about (issue, merge request, commit...). - -For more details about the `Message-ID`, `In-Reply-To`, and `References headers`, -please consult [RFC 5322](https://tools.ietf.org/html/rfc5322#section-3.6.4). - -## Set it up - -If you want to use Gmail / Google Apps with Reply by email, make sure you have -[IMAP access enabled](https://support.google.com/mail/troubleshooter/1668960?hl=en#ts=1665018) -and [allowed less secure apps to access the account](https://support.google.com/accounts/answer/6010255). - -To set up a basic Postfix mail server with IMAP access on Ubuntu, follow -[these instructions](./postfix.md). - -### Omnibus package installations - -1. Find the `incoming_email` section in `/etc/gitlab/gitlab.rb`, enable the - feature and fill in the details for your specific IMAP server and email account: - - ```ruby - # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com - gitlab_rails['incoming_email_enabled'] = true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - gitlab_rails['incoming_email_address'] = "incoming+%{key}@gitlab.example.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - gitlab_rails['incoming_email_email'] = "incoming" - # Email account password - gitlab_rails['incoming_email_password'] = "[REDACTED]" - - # IMAP server host - gitlab_rails['incoming_email_host'] = "gitlab.example.com" - # IMAP server port - gitlab_rails['incoming_email_port'] = 143 - # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_ssl'] = false - # Whether the IMAP server uses StartTLS - gitlab_rails['incoming_email_start_tls'] = false - - # The mailbox where incoming mail will end up. Usually "inbox". - gitlab_rails['incoming_email_mailbox_name'] = "inbox" - ``` - - ```ruby - # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - gitlab_rails['incoming_email_enabled'] = true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - gitlab_rails['incoming_email_address'] = "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - gitlab_rails['incoming_email_email'] = "gitlab-incoming@gmail.com" - # Email account password - gitlab_rails['incoming_email_password'] = "[REDACTED]" - - # IMAP server host - gitlab_rails['incoming_email_host'] = "imap.gmail.com" - # IMAP server port - gitlab_rails['incoming_email_port'] = 993 - # Whether the IMAP server uses SSL - gitlab_rails['incoming_email_ssl'] = true - # Whether the IMAP server uses StartTLS - gitlab_rails['incoming_email_start_tls'] = false - - # The mailbox where incoming mail will end up. Usually "inbox". - gitlab_rails['incoming_email_mailbox_name'] = "inbox" - ``` - -1. Reconfigure GitLab and restart mailroom for the changes to take effect: - - ```sh - sudo gitlab-ctl reconfigure - sudo gitlab-ctl restart mailroom - ``` - -1. Verify that everything is configured correctly: - - ```sh - sudo gitlab-rake gitlab:incoming_email:check - ``` - -1. Reply by email should now be working. - -### Installations from source - -1. Go to the GitLab installation directory: - - ```sh - cd /home/git/gitlab - ``` - -1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature - and fill in the details for your specific IMAP server and email account: - - ```sh - sudo editor config/gitlab.yml - ``` - - ```yaml - # Configuration for Postfix mail server, assumes mailbox incoming@gitlab.example.com - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "incoming+%{key}@gitlab.example.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "incoming" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "gitlab.example.com" - # IMAP server port - port: 143 - # Whether the IMAP server uses SSL - ssl: false - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - ``` - - ```yaml - # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "gitlab-incoming@gmail.com" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "imap.gmail.com" - # IMAP server port - port: 993 - # Whether the IMAP server uses SSL - ssl: true - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - ``` - -1. Enable `mail_room` in the init script at `/etc/default/gitlab`: - - ```sh - sudo mkdir -p /etc/default - echo 'mail_room_enabled=true' | sudo tee -a /etc/default/gitlab - ``` - -1. Restart GitLab: - - ```sh - sudo service gitlab restart - ``` - -1. Verify that everything is configured correctly: - - ```sh - sudo -u git -H bundle exec rake gitlab:incoming_email:check RAILS_ENV=production - ``` - -1. Reply by email should now be working. - -### Development - -1. Go to the GitLab installation directory. - -1. Find the `incoming_email` section in `config/gitlab.yml`, enable the feature and fill in the details for your specific IMAP server and email account: - - ```yaml - # Configuration for Gmail / Google Apps, assumes mailbox gitlab-incoming@gmail.com - incoming_email: - enabled: true - - # The email address including the `%{key}` placeholder that will be replaced to reference the item being replied to. - # The placeholder can be omitted but if present, it must appear in the "user" part of the address (before the `@`). - address: "gitlab-incoming+%{key}@gmail.com" - - # Email account username - # With third party providers, this is usually the full email address. - # With self-hosted email servers, this is usually the user part of the email address. - user: "gitlab-incoming@gmail.com" - # Email account password - password: "[REDACTED]" - - # IMAP server host - host: "imap.gmail.com" - # IMAP server port - port: 993 - # Whether the IMAP server uses SSL - ssl: true - # Whether the IMAP server uses StartTLS - start_tls: false - - # The mailbox where incoming mail will end up. Usually "inbox". - mailbox: "inbox" - ``` - - As mentioned, the part after `+` is ignored, and this will end up in the mailbox for `gitlab-incoming@gmail.com`. - -1. Uncomment the `mail_room` line in your `Procfile`: - - ```yaml - mail_room: bundle exec mail_room -q -c config/mail_room.yml - ``` - -1. Restart GitLab: - - ```sh - bundle exec foreman start - ``` - -1. Verify that everything is configured correctly: - - ```sh - bundle exec rake gitlab:incoming_email:check RAILS_ENV=development - ``` - -1. Reply by email should now be working. +This document was moved to [administration/reply_by_email](../administration/reply_by_email.md). diff --git a/doc/incoming_email/postfix.md b/doc/incoming_email/postfix.md index 787d21f7f8f..90833238ac5 100644 --- a/doc/incoming_email/postfix.md +++ b/doc/incoming_email/postfix.md @@ -1,321 +1 @@ -# Set up Postfix for Reply by email - -This document will take you through the steps of setting up a basic Postfix mail server with IMAP authentication on Ubuntu, to be used with Reply by email. - -The instructions make the assumption that you will be using the email address `incoming@gitlab.example.com`, that is, username `incoming` on host `gitlab.example.com`. Don't forget to change it to your actual host when executing the example code snippets. - -## Configure your server firewall - -1. Open up port 25 on your server so that people can send email into the server over SMTP. -2. If the mail server is different from the server running GitLab, open up port 143 on your server so that GitLab can read email from the server over IMAP. - -## Install packages - -1. Install the `postfix` package if it is not installed already: - - ```sh - sudo apt-get install postfix - ``` - - When asked about the environment, select 'Internet Site'. When asked to confirm the hostname, make sure it matches `gitlab.example.com`. - -1. Install the `mailutils` package. - - ```sh - sudo apt-get install mailutils - ``` - -## Create user - -1. Create a user for incoming email. - - ```sh - sudo useradd -m -s /bin/bash incoming - ``` - -1. Set a password for this user. - - ```sh - sudo passwd incoming - ``` - - Be sure not to forget this, you'll need it later. - -## Test the out-of-the-box setup - -1. Connect to the local SMTP server: - - ```sh - telnet localhost 25 - ``` - - You should see a prompt like this: - - ```sh - Trying 127.0.0.1... - Connected to localhost. - Escape character is '^]'. - 220 gitlab.example.com ESMTP Postfix (Ubuntu) - ``` - - If you get a `Connection refused` error instead, verify that `postfix` is running: - - ```sh - sudo postfix status - ``` - - If it is not, start it: - - ```sh - sudo postfix start - ``` - -1. Send the new `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: - - ``` - ehlo localhost - mail from: root@localhost - rcpt to: incoming@localhost - data - Subject: Re: Some issue - - Sounds good! - . - quit - ``` - - _**Note:** The `.` is a literal period on its own line._ - - _**Note:** If you receive an error after entering `rcpt to: incoming@localhost` - then your Postfix `my_network` configuration is not correct. The error will - say 'Temporary lookup failure'. See - [Configure Postfix to receive email from the Internet](#configure-postfix-to-receive-email-from-the-internet)._ - -1. Check if the `incoming` user received the email: - - ```sh - su - incoming - mail - ``` - - You should see output like this: - - ``` - "/var/mail/incoming": 1 message 1 unread - >U 1 root@localhost 59/2842 Re: Some issue - ``` - - Quit the mail app: - - ```sh - q - ``` - -1. Log out of the `incoming` account and go back to being `root`: - - ```sh - logout - ``` - -## Configure Postfix to use Maildir-style mailboxes - -Courier, which we will install later to add IMAP authentication, requires mailboxes to have the Maildir format, rather than mbox. - -1. Configure Postfix to use Maildir-style mailboxes: - - ```sh - sudo postconf -e "home_mailbox = Maildir/" - ``` - -1. Restart Postfix: - - ```sh - sudo /etc/init.d/postfix restart - ``` - -1. Test the new setup: - - 1. Follow steps 1 and 2 of _[Test the out-of-the-box setup](#test-the-out-of-the-box-setup)_. - 1. Check if the `incoming` user received the email: - - ```sh - su - incoming - MAIL=/home/incoming/Maildir - mail - ``` - - You should see output like this: - - ``` - "/home/incoming/Maildir": 1 message 1 unread - >U 1 root@localhost 59/2842 Re: Some issue - ``` - - Quit the mail app: - - ```sh - q - ``` - - _**Note:** If `mail` returns an error `Maildir: Is a directory` then your - version of `mail` doesn't support Maildir style mailboxes. Install - `heirloom-mailx` by running `sudo apt-get install heirloom-mailx`. Then, - try the above steps again, substituting `heirloom-mailx` for the `mail` - command._ - -1. Log out of the `incoming` account and go back to being `root`: - - ```sh - logout - ``` - -## Install the Courier IMAP server - -1. Install the `courier-imap` package: - - ```sh - sudo apt-get install courier-imap - ``` - -## Configure Postfix to receive email from the internet - -1. Let Postfix know about the domains that it should consider local: - - ```sh - sudo postconf -e "mydestination = gitlab.example.com, localhost.localdomain, localhost" - ``` - -1. Let Postfix know about the IPs that it should consider part of the LAN: - - We'll assume `192.168.1.0/24` is your local LAN. You can safely skip this step if you don't have other machines in the same local network. - - ```sh - sudo postconf -e "mynetworks = 127.0.0.0/8, 192.168.1.0/24" - ``` - -1. Configure Postfix to receive mail on all interfaces, which includes the internet: - - ```sh - sudo postconf -e "inet_interfaces = all" - ``` - -1. Configure Postfix to use the `+` delimiter for sub-addressing: - - ```sh - sudo postconf -e "recipient_delimiter = +" - ``` - -1. Restart Postfix: - - ```sh - sudo service postfix restart - ``` - -## Test the final setup - -1. Test SMTP under the new setup: - - 1. Connect to the SMTP server: - - ```sh - telnet gitlab.example.com 25 - ``` - - You should see a prompt like this: - - ```sh - Trying 123.123.123.123... - Connected to gitlab.example.com. - Escape character is '^]'. - 220 gitlab.example.com ESMTP Postfix (Ubuntu) - ``` - - If you get a `Connection refused` error instead, make sure your firewall is setup to allow inbound traffic on port 25. - - 1. Send the `incoming` user a dummy email to test SMTP, by entering the following into the SMTP prompt: - - ``` - ehlo gitlab.example.com - mail from: root@gitlab.example.com - rcpt to: incoming@gitlab.example.com - data - Subject: Re: Some issue - - Sounds good! - . - quit - ``` - - (Note: The `.` is a literal period on its own line) - - 1. Check if the `incoming` user received the email: - - ```sh - su - incoming - MAIL=/home/incoming/Maildir - mail - ``` - - You should see output like this: - - ``` - "/home/incoming/Maildir": 1 message 1 unread - >U 1 root@gitlab.example.com 59/2842 Re: Some issue - ``` - - Quit the mail app: - - ```sh - q - ``` - - 1. Log out of the `incoming` account and go back to being `root`: - - ```sh - logout - ``` - -1. Test IMAP under the new setup: - - 1. Connect to the IMAP server: - - ```sh - telnet gitlab.example.com 143 - ``` - - You should see a prompt like this: - - ```sh - Trying 123.123.123.123... - Connected to mail.example.gitlab.com. - Escape character is '^]'. - - OK [CAPABILITY IMAP4rev1 UIDPLUS CHILDREN NAMESPACE THREAD=ORDEREDSUBJECT THREAD=REFERENCES SORT QUOTA IDLE ACL ACL2=UNION] Courier-IMAP ready. Copyright 1998-2011 Double Precision, Inc. See COPYING for distribution information. - ``` - - 1. Sign in as the `incoming` user to test IMAP, by entering the following into the IMAP prompt: - - ``` - a login incoming PASSWORD - ``` - - Replace PASSWORD with the password you set on the `incoming` user earlier. - - You should see output like this: - - ``` - a OK LOGIN Ok. - ``` - - 1. Disconnect from the IMAP server: - - ```sh - a logout - ``` - -## Done! - -If all the tests were successful, Postfix is all set up and ready to receive email! Continue with the [Reply by email](./README.md) guide to configure GitLab. - ---------- - -_This document was adapted from https://help.ubuntu.com/community/PostfixBasicSetupHowto, by contributors to the Ubuntu documentation wiki._ +This document was moved to [administration/reply_by_email_postfix_setup](../administration/reply_by_email_postfix_setup.md). diff --git a/doc/install/installation.md b/doc/install/installation.md index 3ac813aa914..da3f92f9a6c 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -563,7 +563,7 @@ Using a self-signed certificate is discouraged but if you must use it follow the ### Enable Reply by email -See the ["Reply by email" documentation](../incoming_email/README.md) for more information on how to set this up. +See the ["Reply by email" documentation](../administration/reply_by_email.md) for more information on how to set this up. ### LDAP Authentication diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 5f4a6bbfa35..2ae48a970ce 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -671,7 +671,7 @@ namespace :gitlab do "Enable mail_room in the init.d configuration." ) for_more_information( - "doc/incoming_email/README.md" + "doc/administration/reply_by_email.md" ) fix_and_rerun end @@ -690,7 +690,7 @@ namespace :gitlab do "Enable mail_room in your Procfile." ) for_more_information( - "doc/incoming_email/README.md" + "doc/administration/reply_by_email.md" ) fix_and_rerun end @@ -747,7 +747,7 @@ namespace :gitlab do "Check that the information in config/gitlab.yml is correct" ) for_more_information( - "doc/incoming_email/README.md" + "doc/administration/reply_by_email.md" ) fix_and_rerun end -- cgit v1.2.1 From 6207dc9037601d5188c86876c2ff79d6ddbbe540 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 25 Sep 2016 12:16:14 +0200 Subject: Move monitoring/ to new location --- doc/README.md | 4 +- doc/administration/monitoring/health_check.md | 66 +++++++ .../monitoring/img/health_check_token.png | Bin 0 -> 6630 bytes .../monitoring/performance/gitlab_configuration.md | 40 +++++ .../performance/grafana_configuration.md | 111 ++++++++++++ .../performance/img/grafana_dashboard_dropdown.png | Bin 0 -> 14368 bytes .../performance/img/grafana_dashboard_import.png | Bin 0 -> 18267 bytes .../img/grafana_data_source_configuration.png | Bin 0 -> 26060 bytes .../performance/img/grafana_data_source_empty.png | Bin 0 -> 21821 bytes .../performance/img/grafana_save_icon.png | Bin 0 -> 9107 bytes .../img/metrics_gitlab_configuration_settings.png | Bin 0 -> 61357 bytes .../performance/influxdb_configuration.md | 193 ++++++++++++++++++++ .../monitoring/performance/influxdb_schema.md | 97 +++++++++++ .../monitoring/performance/introduction.md | 65 +++++++ doc/monitoring/health_check.md | 67 +------ doc/monitoring/img/health_check_token.png | Bin 6630 -> 0 bytes doc/monitoring/performance/gitlab_configuration.md | 41 +---- .../performance/grafana_configuration.md | 112 +----------- .../performance/influxdb_configuration.md | 194 +-------------------- doc/monitoring/performance/influxdb_schema.md | 98 +---------- doc/monitoring/performance/introduction.md | 66 +------ 21 files changed, 580 insertions(+), 574 deletions(-) create mode 100644 doc/administration/monitoring/health_check.md create mode 100644 doc/administration/monitoring/img/health_check_token.png create mode 100644 doc/administration/monitoring/performance/gitlab_configuration.md create mode 100644 doc/administration/monitoring/performance/grafana_configuration.md create mode 100644 doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png create mode 100644 doc/administration/monitoring/performance/img/grafana_dashboard_import.png create mode 100644 doc/administration/monitoring/performance/img/grafana_data_source_configuration.png create mode 100644 doc/administration/monitoring/performance/img/grafana_data_source_empty.png create mode 100644 doc/administration/monitoring/performance/img/grafana_save_icon.png create mode 100644 doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png create mode 100644 doc/administration/monitoring/performance/influxdb_configuration.md create mode 100644 doc/administration/monitoring/performance/influxdb_schema.md create mode 100644 doc/administration/monitoring/performance/introduction.md delete mode 100644 doc/monitoring/img/health_check_token.png diff --git a/doc/README.md b/doc/README.md index dd0eb97489e..5a6f467c17c 100644 --- a/doc/README.md +++ b/doc/README.md @@ -46,8 +46,8 @@ - [Migrate GitLab CI to CE/EE](migrate_ci_to_ce/README.md) Follow this guide to migrate your existing GitLab CI data to GitLab CE/EE. - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. -- [GitLab Performance Monitoring](monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. -- [Monitoring uptime](monitoring/health_check.md) Check the server status using the health check endpoint. +- [GitLab Performance Monitoring](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. +- [Monitoring uptime](administration/monitoring/health_check.md) Check the server status using the health check endpoint. - [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong - [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs. - [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability. diff --git a/doc/administration/monitoring/health_check.md b/doc/administration/monitoring/health_check.md new file mode 100644 index 00000000000..eac57bc3de4 --- /dev/null +++ b/doc/administration/monitoring/health_check.md @@ -0,0 +1,66 @@ +# Health Check + +> [Introduced][ce-3888] in GitLab 8.8. + +GitLab provides a health check endpoint for uptime monitoring on the `health_check` web +endpoint. The health check reports on the overall system status based on the status of +the database connection, the state of the database migrations, and the ability to write +and access the cache. This endpoint can be provided to uptime monitoring services like +[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. + +## Access Token + +An access token needs to be provided while accessing the health check endpoint. The current +accepted token can be found on the `admin/health_check` page of your GitLab instance. + +![access token](img/health_check_token.png) + +The access token can be passed as a URL parameter: + +``` +https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN +``` + +or as an HTTP header: + +```bash +curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json +``` + +## Using the Endpoint + +Once you have the access token, health information can be retrieved as plain text, JSON, +or XML using the `health_check` endpoint: + +- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` + +You can also ask for the status of specific services: + +- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` + +For example, the JSON output of the following health check: + +```bash +curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json +``` + +would be like: + +``` +{"healthy":true,"message":"success"} +``` + +## Status + +On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint +will return a valid successful HTTP status code, and a `success` message. Ideally your +uptime monitoring should look for the success message. + +[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 +[pingdom]: https://www.pingdom.com +[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html +[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring diff --git a/doc/administration/monitoring/img/health_check_token.png b/doc/administration/monitoring/img/health_check_token.png new file mode 100644 index 00000000000..2d7c82a65a8 Binary files /dev/null and b/doc/administration/monitoring/img/health_check_token.png differ diff --git a/doc/administration/monitoring/performance/gitlab_configuration.md b/doc/administration/monitoring/performance/gitlab_configuration.md new file mode 100644 index 00000000000..771584268d9 --- /dev/null +++ b/doc/administration/monitoring/performance/gitlab_configuration.md @@ -0,0 +1,40 @@ +# GitLab Configuration + +GitLab Performance Monitoring is disabled by default. To enable it and change any of its +settings, navigate to the Admin area in **Settings > Metrics** +(`/admin/application_settings`). + +The minimum required settings you need to set are the InfluxDB host and port. +Make sure _Enable InfluxDB Metrics_ is checked and hit **Save** to save the +changes. + +--- + +![GitLab Performance Monitoring Admin Settings](img/metrics_gitlab_configuration_settings.png) + +--- + +Finally, a restart of all GitLab processes is required for the changes to take +effect: + +```bash +# For Omnibus installations +sudo gitlab-ctl restart + +# For installations from source +sudo service gitlab restart +``` + +## Pending Migrations + +When any migrations are pending, the metrics are disabled until the migrations +have been performed. + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [InfluxDB Configuration](influxdb_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md) diff --git a/doc/administration/monitoring/performance/grafana_configuration.md b/doc/administration/monitoring/performance/grafana_configuration.md new file mode 100644 index 00000000000..7947b0fedc4 --- /dev/null +++ b/doc/administration/monitoring/performance/grafana_configuration.md @@ -0,0 +1,111 @@ +# Grafana Configuration + +[Grafana](http://grafana.org/) is a tool that allows you to visualize time +series metrics through graphs and dashboards. It supports several backend +data stores, including InfluxDB. GitLab writes performance data to InfluxDB +and Grafana will allow you to query InfluxDB to display useful graphs. + +For the easiest installation and configuration, install Grafana on the same +server as InfluxDB. For larger installations, you may want to split out these +services. + +## Installation + +Grafana supplies package repositories (Yum/Apt) for easy installation. +See [Grafana installation documentation](http://docs.grafana.org/installation/) +for detailed steps. + +> **Note**: Before starting Grafana for the first time, set the admin user +and password in `/etc/grafana/grafana.ini`. Otherwise, the default password +will be `admin`. + +## Configuration + +Login as the admin user. Expand the menu by clicking the Grafana logo in the +top left corner. Choose 'Data Sources' from the menu. Then, click 'Add new' +in the top bar. + +![Grafana empty data source page](img/grafana_data_source_empty.png) + +Fill in the configuration details for the InfluxDB data source. Save and +Test Connection to ensure the configuration is correct. + +- **Name**: InfluxDB +- **Default**: Checked +- **Type**: InfluxDB 0.9.x (Even if you're using InfluxDB 0.10.x) +- **Url**: https://localhost:8086 (Or the remote URL if you've installed InfluxDB +on a separate server) +- **Access**: proxy +- **Database**: gitlab +- **User**: admin (Or the username configured when setting up InfluxDB) +- **Password**: The password configured when you set up InfluxDB + +![Grafana data source configurations](img/grafana_data_source_configuration.png) + +## Apply retention policies and create continuous queries + +If you intend to import the GitLab provided Grafana dashboards, you will need to +set up the right retention policies and continuous queries. The easiest way of +doing this is by using the [influxdb-management](https://gitlab.com/gitlab-org/influxdb-management) +repository. + +To use this repository you must first clone it: + +``` +git clone https://gitlab.com/gitlab-org/influxdb-management.git +cd influxdb-management +``` + +Next you must install the required dependencies: + +``` +gem install bundler +bundle install +``` + +Now you must configure the repository by first copying `.env.example` to `.env` +and then editing the `.env` file to contain the correct InfluxDB settings. Once +configured you can simply run `bundle exec rake` and the InfluxDB database will +be configured for you. + +For more information see the [influxdb-management README](https://gitlab.com/gitlab-org/influxdb-management/blob/master/README.md). + +## Import Dashboards + +You can now import a set of default dashboards that will give you a good +start on displaying useful information. GitLab has published a set of default +[Grafana dashboards][grafana-dashboards] to get you started. Clone the +repository or download a zip/tarball, then follow these steps to import each +JSON file. + +Open the dashboard dropdown menu and click 'Import' + +![Grafana dashboard dropdown](img/grafana_dashboard_dropdown.png) + +Click 'Choose file' and browse to the location where you downloaded or cloned +the dashboard repository. Pick one of the JSON files to import. + +![Grafana dashboard import](img/grafana_dashboard_import.png) + +Once the dashboard is imported, be sure to click save icon in the top bar. If +you do not save the dashboard after importing it will be removed when you +navigate away. + +![Grafana save icon](img/grafana_save_icon.png) + +Repeat this process for each dashboard you wish to import. + +Alternatively you can automatically import all the dashboards into your Grafana +instance. See the README of the [Grafana dashboards][grafana-dashboards] +repository for more information on this process. + +[grafana-dashboards]: https://gitlab.com/gitlab-org/grafana-dashboards + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Installation/Configuration](influxdb_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) diff --git a/doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png b/doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png new file mode 100644 index 00000000000..7e34fad71ce Binary files /dev/null and b/doc/administration/monitoring/performance/img/grafana_dashboard_dropdown.png differ diff --git a/doc/administration/monitoring/performance/img/grafana_dashboard_import.png b/doc/administration/monitoring/performance/img/grafana_dashboard_import.png new file mode 100644 index 00000000000..f97624365c7 Binary files /dev/null and b/doc/administration/monitoring/performance/img/grafana_dashboard_import.png differ diff --git a/doc/administration/monitoring/performance/img/grafana_data_source_configuration.png b/doc/administration/monitoring/performance/img/grafana_data_source_configuration.png new file mode 100644 index 00000000000..7d50e4c88c2 Binary files /dev/null and b/doc/administration/monitoring/performance/img/grafana_data_source_configuration.png differ diff --git a/doc/administration/monitoring/performance/img/grafana_data_source_empty.png b/doc/administration/monitoring/performance/img/grafana_data_source_empty.png new file mode 100644 index 00000000000..aa39a53acae Binary files /dev/null and b/doc/administration/monitoring/performance/img/grafana_data_source_empty.png differ diff --git a/doc/administration/monitoring/performance/img/grafana_save_icon.png b/doc/administration/monitoring/performance/img/grafana_save_icon.png new file mode 100644 index 00000000000..c740e33cd1c Binary files /dev/null and b/doc/administration/monitoring/performance/img/grafana_save_icon.png differ diff --git a/doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png b/doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png new file mode 100644 index 00000000000..db396423e30 Binary files /dev/null and b/doc/administration/monitoring/performance/img/metrics_gitlab_configuration_settings.png differ diff --git a/doc/administration/monitoring/performance/influxdb_configuration.md b/doc/administration/monitoring/performance/influxdb_configuration.md new file mode 100644 index 00000000000..c30cd2950d8 --- /dev/null +++ b/doc/administration/monitoring/performance/influxdb_configuration.md @@ -0,0 +1,193 @@ +# InfluxDB Configuration + +The default settings provided by [InfluxDB] are not sufficient for a high traffic +GitLab environment. The settings discussed in this document are based on the +settings GitLab uses for GitLab.com, depending on your own needs you may need to +further adjust them. + +If you are intending to run InfluxDB on the same server as GitLab, make sure +you have plenty of RAM since InfluxDB can use quite a bit depending on traffic. + +Unless you are going with a budget setup, it's advised to run it separately. + +## Requirements + +- InfluxDB 0.9.5 or newer +- A fairly modern version of Linux +- At least 4GB of RAM +- At least 10GB of storage for InfluxDB data + +Note that the RAM and storage requirements can differ greatly depending on the +amount of data received/stored. To limit the amount of stored data users can +look into [InfluxDB Retention Policies][influxdb-retention]. + +## Installation + +Installing InfluxDB is out of the scope of this document. Please refer to the +[InfluxDB documentation]. + +## InfluxDB Server Settings + +Since InfluxDB has many settings that users may wish to customize themselves +(e.g. what port to run InfluxDB on), we'll only cover the essentials. + +The configuration file in question is usually located at +`/etc/influxdb/influxdb.conf`. Whenever you make a change in this file, +InfluxDB needs to be restarted. + +### Storage Engine + +InfluxDB comes with different storage engines and as of InfluxDB 0.9.5 a new +storage engine is available, called [TSM Tree]. All users **must** use the new +`tsm1` storage engine as this [will be the default engine][tsm1-commit] in +upcoming InfluxDB releases. + +Make sure you have the following in your configuration file: + +``` +[data] + dir = "/var/lib/influxdb/data" + engine = "tsm1" +``` + +### Admin Panel + +Production environments should have the InfluxDB admin panel **disabled**. This +feature can be disabled by adding the following to your InfluxDB configuration +file: + +``` +[admin] + enabled = false +``` + +### HTTP + +HTTP is required when using the [InfluxDB CLI] or other tools such as Grafana, +thus it should be enabled. When enabling make sure to _also_ enable +authentication: + +``` +[http] + enabled = true + auth-enabled = true +``` + +_**Note:** Before you enable authentication, you might want to [create an +admin user](#create-a-new-admin-user)._ + +### UDP + +GitLab writes data to InfluxDB via UDP and thus this must be enabled. Enabling +UDP can be done using the following settings: + +``` +[[udp]] + enabled = true + bind-address = ":8089" + database = "gitlab" + batch-size = 1000 + batch-pending = 5 + batch-timeout = "1s" + read-buffer = 209715200 +``` + +This does the following: + +1. Enable UDP and bind it to port 8089 for all addresses. +2. Store any data received in the "gitlab" database. +3. Define a batch of points to be 1000 points in size and allow a maximum of + 5 batches _or_ flush them automatically after 1 second. +4. Define a UDP read buffer size of 200 MB. + +One of the most important settings here is the UDP read buffer size as if this +value is set too low, packets will be dropped. You must also make sure the OS +buffer size is set to the same value, the default value is almost never enough. + +To set the OS buffer size to 200 MB, on Linux you can run the following command: + +```bash +sysctl -w net.core.rmem_max=209715200 +``` + +To make this permanent, add the following to `/etc/sysctl.conf` and restart the +server: + +```bash +net.core.rmem_max=209715200 +``` + +It is **very important** to make sure the buffer sizes are large enough to +handle all data sent to InfluxDB as otherwise you _will_ lose data. The above +buffer sizes are based on the traffic for GitLab.com. Depending on the amount of +traffic, users may be able to use a smaller buffer size, but we highly recommend +using _at least_ 100 MB. + +When enabling UDP, users should take care to not expose the port to the public, +as doing so will allow anybody to write data into your InfluxDB database (as +[InfluxDB's UDP protocol][udp] doesn't support authentication). We recommend either +whitelisting the allowed IP addresses/ranges, or setting up a VLAN and only +allowing traffic from members of said VLAN. + +## Create a new admin user + +If you want to [enable authentication](#http), you might want to [create an +admin user][influx-admin]: + +``` +influx -execute "CREATE USER jeff WITH PASSWORD '1234' WITH ALL PRIVILEGES" +``` + +## Create the `gitlab` database + +Once you get InfluxDB up and running, you need to create a database for GitLab. +Make sure you have changed the [storage engine](#storage-engine) to `tsm1` +before creating a database. + +_**Note:** If you [created an admin user](#create-a-new-admin-user) and enabled +[HTTP authentication](#http), remember to append the username (`-username `) +and password (`-password `) you set earlier to the commands below._ + +Run the following command to create a database named `gitlab`: + +```bash +influx -execute 'CREATE DATABASE gitlab' +``` + +The name **must** be `gitlab`, do not use any other name. + +Next, make sure that the database was successfully created: + +```bash +influx -execute 'SHOW DATABASES' +``` + +The output should be similar to: + +``` +name: databases +--------------- +name +_internal +gitlab +``` + +That's it! Now your GitLab instance should send data to InfluxDB. + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md) + +[influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management +[influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/ +[influxdb cli]: https://docs.influxdata.com/influxdb/v0.9/tools/shell/ +[udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ +[influxdb]: https://influxdata.com/time-series-platform/influxdb/ +[tsm tree]: https://influxdata.com/blog/new-storage-engine-time-structured-merge-tree/ +[tsm1-commit]: https://github.com/influxdata/influxdb/commit/15d723dc77651bac83e09e2b1c94be480966cb0d +[influx-admin]: https://docs.influxdata.com/influxdb/v0.9/administration/authentication_and_authorization/#create-a-new-admin-user diff --git a/doc/administration/monitoring/performance/influxdb_schema.md b/doc/administration/monitoring/performance/influxdb_schema.md new file mode 100644 index 00000000000..eff0e29f58d --- /dev/null +++ b/doc/administration/monitoring/performance/influxdb_schema.md @@ -0,0 +1,97 @@ +# InfluxDB Schema + +The following measurements are currently stored in InfluxDB: + +- `PROCESS_file_descriptors` +- `PROCESS_gc_statistics` +- `PROCESS_memory_usage` +- `PROCESS_method_calls` +- `PROCESS_object_counts` +- `PROCESS_transactions` +- `PROCESS_views` +- `events` + +Here, `PROCESS` is replaced with either `rails` or `sidekiq` depending on the +process type. In all series, any form of duration is stored in milliseconds. + +## PROCESS_file_descriptors + +This measurement contains the number of open file descriptors over time. The +value field `value` contains the number of descriptors. + +## PROCESS_gc_statistics + +This measurement contains Ruby garbage collection statistics such as the amount +of minor/major GC runs (relative to the last sampling interval), the time spent +in garbage collection cycles, and all fields/values returned by `GC.stat`. + +## PROCESS_memory_usage + +This measurement contains the process' memory usage (in bytes) over time. The +value field `value` contains the number of bytes. + +## PROCESS_method_calls + +This measurement contains the methods called during a transaction along with +their duration, and a name of the transaction action that invoked the method (if +available). The method call duration is stored in the value field `duration`, +while the method name is stored in the tag `method`. The tag `action` contains +the full name of the transaction action. Both the `method` and `action` fields +are in the following format: + +``` +ClassName#method_name +``` + +For example, a method called by the `show` method in the `UsersController` class +would have `action` set to `UsersController#show`. + +## PROCESS_object_counts + +This measurement is used to store retained Ruby objects (per class) and the +amount of retained objects. The number of objects is stored in the `count` value +field while the class name is stored in the `type` tag. + +## PROCESS_transactions + +This measurement is used to store basic transaction details such as the time it +took to complete a transaction, how much time was spent in SQL queries, etc. The +following value fields are available: + +| Value | Description | +| ----- | ----------- | +| `duration` | The total duration of the transaction | +| `allocated_memory` | The amount of bytes allocated while the transaction was running. This value is only reliable when using single-threaded application servers | +| `method_duration` | The total time spent in method calls | +| `sql_duration` | The total time spent in SQL queries | +| `view_duration` | The total time spent in views | + +## PROCESS_views + +This measurement is used to store view rendering timings for a transaction. The +following value fields are available: + +| Value | Description | +| ----- | ----------- | +| `duration` | The rendering time of the view | +| `view` | The path of the view, relative to the application's root directory | + +The `action` tag contains the action name of the transaction that rendered the +view. + +## events + +This measurement is used to store generic events such as the number of Git +pushes, Emails sent, etc. Each point in this measurement has a single value +field called `count`. The value of this field is simply set to `1`. Each point +also has at least one tag: `event`. This tag's value is set to the event name. +Depending on the event type additional tags may be available as well. + +--- + +Read more on: + +- [Introduction to GitLab Performance Monitoring](introduction.md) +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Configuration](influxdb_configuration.md) +- [Grafana Install/Configuration](grafana_configuration.md) diff --git a/doc/administration/monitoring/performance/introduction.md b/doc/administration/monitoring/performance/introduction.md new file mode 100644 index 00000000000..79904916b7e --- /dev/null +++ b/doc/administration/monitoring/performance/introduction.md @@ -0,0 +1,65 @@ +# GitLab Performance Monitoring + +GitLab comes with its own application performance measuring system as of GitLab +8.4, simply called "GitLab Performance Monitoring". GitLab Performance Monitoring is available in both the +Community and Enterprise editions. + +Apart from this introduction, you are advised to read through the following +documents in order to understand and properly configure GitLab Performance Monitoring: + +- [GitLab Configuration](gitlab_configuration.md) +- [InfluxDB Install/Configuration](influxdb_configuration.md) +- [InfluxDB Schema](influxdb_schema.md) +- [Grafana Install/Configuration](grafana_configuration.md) + +## Introduction to GitLab Performance Monitoring + +GitLab Performance Monitoring makes it possible to measure a wide variety of statistics +including (but not limited to): + +- The time it took to complete a transaction (a web request or Sidekiq job). +- The time spent in running SQL queries and rendering HAML views. +- The time spent executing (instrumented) Ruby methods. +- Ruby object allocations, and retained objects in particular. +- System statistics such as the process' memory usage and open file descriptors. +- Ruby garbage collection statistics. + +Metrics data is written to [InfluxDB][influxdb] over [UDP][influxdb-udp]. Stored +data can be visualized using [Grafana][grafana] or any other application that +supports reading data from InfluxDB. Alternatively data can be queried using the +InfluxDB CLI. + +## Metric Types + +Two types of metrics are collected: + +1. Transaction specific metrics. +1. Sampled metrics, collected at a certain interval in a separate thread. + +### Transaction Metrics + +Transaction metrics are metrics that can be associated with a single +transaction. This includes statistics such as the transaction duration, timings +of any executed SQL queries, time spent rendering HAML views, etc. These metrics +are collected for every Rack request and Sidekiq job processed. + +### Sampled Metrics + +Sampled metrics are metrics that can't be associated with a single transaction. +Examples include garbage collection statistics and retained Ruby objects. These +metrics are collected at a regular interval. This interval is made up out of two +parts: + +1. A user defined interval. +1. A randomly generated offset added on top of the interval, the same offset + can't be used twice in a row. + +The actual interval can be anywhere between a half of the defined interval and a +half above the interval. For example, for a user defined interval of 15 seconds +the actual interval can be anywhere between 7.5 and 22.5. The interval is +re-generated for every sampling run instead of being generated once and re-used +for the duration of the process' lifetime. + +[influxdb]: https://influxdata.com/time-series-platform/influxdb/ +[influxdb-udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ +[grafana]: http://grafana.org/ diff --git a/doc/monitoring/health_check.md b/doc/monitoring/health_check.md index eac57bc3de4..23ae1b17258 100644 --- a/doc/monitoring/health_check.md +++ b/doc/monitoring/health_check.md @@ -1,66 +1 @@ -# Health Check - -> [Introduced][ce-3888] in GitLab 8.8. - -GitLab provides a health check endpoint for uptime monitoring on the `health_check` web -endpoint. The health check reports on the overall system status based on the status of -the database connection, the state of the database migrations, and the ability to write -and access the cache. This endpoint can be provided to uptime monitoring services like -[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. - -## Access Token - -An access token needs to be provided while accessing the health check endpoint. The current -accepted token can be found on the `admin/health_check` page of your GitLab instance. - -![access token](img/health_check_token.png) - -The access token can be passed as a URL parameter: - -``` -https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN -``` - -or as an HTTP header: - -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` - -## Using the Endpoint - -Once you have the access token, health information can be retrieved as plain text, JSON, -or XML using the `health_check` endpoint: - -- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` - -You can also ask for the status of specific services: - -- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` - -For example, the JSON output of the following health check: - -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` - -would be like: - -``` -{"healthy":true,"message":"success"} -``` - -## Status - -On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint -will return a valid successful HTTP status code, and a `success` message. Ideally your -uptime monitoring should look for the success message. - -[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 -[pingdom]: https://www.pingdom.com -[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html -[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring +This document was moved to [administration/monitoring/health_check](../administration/monitoring/health_check.md). diff --git a/doc/monitoring/img/health_check_token.png b/doc/monitoring/img/health_check_token.png deleted file mode 100644 index 2d7c82a65a8..00000000000 Binary files a/doc/monitoring/img/health_check_token.png and /dev/null differ diff --git a/doc/monitoring/performance/gitlab_configuration.md b/doc/monitoring/performance/gitlab_configuration.md index 771584268d9..a669bb28904 100644 --- a/doc/monitoring/performance/gitlab_configuration.md +++ b/doc/monitoring/performance/gitlab_configuration.md @@ -1,40 +1 @@ -# GitLab Configuration - -GitLab Performance Monitoring is disabled by default. To enable it and change any of its -settings, navigate to the Admin area in **Settings > Metrics** -(`/admin/application_settings`). - -The minimum required settings you need to set are the InfluxDB host and port. -Make sure _Enable InfluxDB Metrics_ is checked and hit **Save** to save the -changes. - ---- - -![GitLab Performance Monitoring Admin Settings](img/metrics_gitlab_configuration_settings.png) - ---- - -Finally, a restart of all GitLab processes is required for the changes to take -effect: - -```bash -# For Omnibus installations -sudo gitlab-ctl restart - -# For installations from source -sudo service gitlab restart -``` - -## Pending Migrations - -When any migrations are pending, the metrics are disabled until the migrations -have been performed. - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [InfluxDB Configuration](influxdb_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) -- [Grafana Install/Configuration](grafana_configuration.md) +This document was moved to [administration/monitoring/performance/gitlab_configuration](../administration/monitoring/performance/gitlab_configuration.md). diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md index 7947b0fedc4..93320b40174 100644 --- a/doc/monitoring/performance/grafana_configuration.md +++ b/doc/monitoring/performance/grafana_configuration.md @@ -1,111 +1 @@ -# Grafana Configuration - -[Grafana](http://grafana.org/) is a tool that allows you to visualize time -series metrics through graphs and dashboards. It supports several backend -data stores, including InfluxDB. GitLab writes performance data to InfluxDB -and Grafana will allow you to query InfluxDB to display useful graphs. - -For the easiest installation and configuration, install Grafana on the same -server as InfluxDB. For larger installations, you may want to split out these -services. - -## Installation - -Grafana supplies package repositories (Yum/Apt) for easy installation. -See [Grafana installation documentation](http://docs.grafana.org/installation/) -for detailed steps. - -> **Note**: Before starting Grafana for the first time, set the admin user -and password in `/etc/grafana/grafana.ini`. Otherwise, the default password -will be `admin`. - -## Configuration - -Login as the admin user. Expand the menu by clicking the Grafana logo in the -top left corner. Choose 'Data Sources' from the menu. Then, click 'Add new' -in the top bar. - -![Grafana empty data source page](img/grafana_data_source_empty.png) - -Fill in the configuration details for the InfluxDB data source. Save and -Test Connection to ensure the configuration is correct. - -- **Name**: InfluxDB -- **Default**: Checked -- **Type**: InfluxDB 0.9.x (Even if you're using InfluxDB 0.10.x) -- **Url**: https://localhost:8086 (Or the remote URL if you've installed InfluxDB -on a separate server) -- **Access**: proxy -- **Database**: gitlab -- **User**: admin (Or the username configured when setting up InfluxDB) -- **Password**: The password configured when you set up InfluxDB - -![Grafana data source configurations](img/grafana_data_source_configuration.png) - -## Apply retention policies and create continuous queries - -If you intend to import the GitLab provided Grafana dashboards, you will need to -set up the right retention policies and continuous queries. The easiest way of -doing this is by using the [influxdb-management](https://gitlab.com/gitlab-org/influxdb-management) -repository. - -To use this repository you must first clone it: - -``` -git clone https://gitlab.com/gitlab-org/influxdb-management.git -cd influxdb-management -``` - -Next you must install the required dependencies: - -``` -gem install bundler -bundle install -``` - -Now you must configure the repository by first copying `.env.example` to `.env` -and then editing the `.env` file to contain the correct InfluxDB settings. Once -configured you can simply run `bundle exec rake` and the InfluxDB database will -be configured for you. - -For more information see the [influxdb-management README](https://gitlab.com/gitlab-org/influxdb-management/blob/master/README.md). - -## Import Dashboards - -You can now import a set of default dashboards that will give you a good -start on displaying useful information. GitLab has published a set of default -[Grafana dashboards][grafana-dashboards] to get you started. Clone the -repository or download a zip/tarball, then follow these steps to import each -JSON file. - -Open the dashboard dropdown menu and click 'Import' - -![Grafana dashboard dropdown](img/grafana_dashboard_dropdown.png) - -Click 'Choose file' and browse to the location where you downloaded or cloned -the dashboard repository. Pick one of the JSON files to import. - -![Grafana dashboard import](img/grafana_dashboard_import.png) - -Once the dashboard is imported, be sure to click save icon in the top bar. If -you do not save the dashboard after importing it will be removed when you -navigate away. - -![Grafana save icon](img/grafana_save_icon.png) - -Repeat this process for each dashboard you wish to import. - -Alternatively you can automatically import all the dashboards into your Grafana -instance. See the README of the [Grafana dashboards][grafana-dashboards] -repository for more information on this process. - -[grafana-dashboards]: https://gitlab.com/gitlab-org/grafana-dashboards - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Installation/Configuration](influxdb_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) +This document was moved to [administration/monitoring/performance/grafana_configuration](../administration/monitoring/performance/grafana_configuration.md). diff --git a/doc/monitoring/performance/influxdb_configuration.md b/doc/monitoring/performance/influxdb_configuration.md index c30cd2950d8..02647de1eb0 100644 --- a/doc/monitoring/performance/influxdb_configuration.md +++ b/doc/monitoring/performance/influxdb_configuration.md @@ -1,193 +1 @@ -# InfluxDB Configuration - -The default settings provided by [InfluxDB] are not sufficient for a high traffic -GitLab environment. The settings discussed in this document are based on the -settings GitLab uses for GitLab.com, depending on your own needs you may need to -further adjust them. - -If you are intending to run InfluxDB on the same server as GitLab, make sure -you have plenty of RAM since InfluxDB can use quite a bit depending on traffic. - -Unless you are going with a budget setup, it's advised to run it separately. - -## Requirements - -- InfluxDB 0.9.5 or newer -- A fairly modern version of Linux -- At least 4GB of RAM -- At least 10GB of storage for InfluxDB data - -Note that the RAM and storage requirements can differ greatly depending on the -amount of data received/stored. To limit the amount of stored data users can -look into [InfluxDB Retention Policies][influxdb-retention]. - -## Installation - -Installing InfluxDB is out of the scope of this document. Please refer to the -[InfluxDB documentation]. - -## InfluxDB Server Settings - -Since InfluxDB has many settings that users may wish to customize themselves -(e.g. what port to run InfluxDB on), we'll only cover the essentials. - -The configuration file in question is usually located at -`/etc/influxdb/influxdb.conf`. Whenever you make a change in this file, -InfluxDB needs to be restarted. - -### Storage Engine - -InfluxDB comes with different storage engines and as of InfluxDB 0.9.5 a new -storage engine is available, called [TSM Tree]. All users **must** use the new -`tsm1` storage engine as this [will be the default engine][tsm1-commit] in -upcoming InfluxDB releases. - -Make sure you have the following in your configuration file: - -``` -[data] - dir = "/var/lib/influxdb/data" - engine = "tsm1" -``` - -### Admin Panel - -Production environments should have the InfluxDB admin panel **disabled**. This -feature can be disabled by adding the following to your InfluxDB configuration -file: - -``` -[admin] - enabled = false -``` - -### HTTP - -HTTP is required when using the [InfluxDB CLI] or other tools such as Grafana, -thus it should be enabled. When enabling make sure to _also_ enable -authentication: - -``` -[http] - enabled = true - auth-enabled = true -``` - -_**Note:** Before you enable authentication, you might want to [create an -admin user](#create-a-new-admin-user)._ - -### UDP - -GitLab writes data to InfluxDB via UDP and thus this must be enabled. Enabling -UDP can be done using the following settings: - -``` -[[udp]] - enabled = true - bind-address = ":8089" - database = "gitlab" - batch-size = 1000 - batch-pending = 5 - batch-timeout = "1s" - read-buffer = 209715200 -``` - -This does the following: - -1. Enable UDP and bind it to port 8089 for all addresses. -2. Store any data received in the "gitlab" database. -3. Define a batch of points to be 1000 points in size and allow a maximum of - 5 batches _or_ flush them automatically after 1 second. -4. Define a UDP read buffer size of 200 MB. - -One of the most important settings here is the UDP read buffer size as if this -value is set too low, packets will be dropped. You must also make sure the OS -buffer size is set to the same value, the default value is almost never enough. - -To set the OS buffer size to 200 MB, on Linux you can run the following command: - -```bash -sysctl -w net.core.rmem_max=209715200 -``` - -To make this permanent, add the following to `/etc/sysctl.conf` and restart the -server: - -```bash -net.core.rmem_max=209715200 -``` - -It is **very important** to make sure the buffer sizes are large enough to -handle all data sent to InfluxDB as otherwise you _will_ lose data. The above -buffer sizes are based on the traffic for GitLab.com. Depending on the amount of -traffic, users may be able to use a smaller buffer size, but we highly recommend -using _at least_ 100 MB. - -When enabling UDP, users should take care to not expose the port to the public, -as doing so will allow anybody to write data into your InfluxDB database (as -[InfluxDB's UDP protocol][udp] doesn't support authentication). We recommend either -whitelisting the allowed IP addresses/ranges, or setting up a VLAN and only -allowing traffic from members of said VLAN. - -## Create a new admin user - -If you want to [enable authentication](#http), you might want to [create an -admin user][influx-admin]: - -``` -influx -execute "CREATE USER jeff WITH PASSWORD '1234' WITH ALL PRIVILEGES" -``` - -## Create the `gitlab` database - -Once you get InfluxDB up and running, you need to create a database for GitLab. -Make sure you have changed the [storage engine](#storage-engine) to `tsm1` -before creating a database. - -_**Note:** If you [created an admin user](#create-a-new-admin-user) and enabled -[HTTP authentication](#http), remember to append the username (`-username `) -and password (`-password `) you set earlier to the commands below._ - -Run the following command to create a database named `gitlab`: - -```bash -influx -execute 'CREATE DATABASE gitlab' -``` - -The name **must** be `gitlab`, do not use any other name. - -Next, make sure that the database was successfully created: - -```bash -influx -execute 'SHOW DATABASES' -``` - -The output should be similar to: - -``` -name: databases ---------------- -name -_internal -gitlab -``` - -That's it! Now your GitLab instance should send data to InfluxDB. - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) -- [Grafana Install/Configuration](grafana_configuration.md) - -[influxdb-retention]: https://docs.influxdata.com/influxdb/v0.9/query_language/database_management/#retention-policy-management -[influxdb documentation]: https://docs.influxdata.com/influxdb/v0.9/ -[influxdb cli]: https://docs.influxdata.com/influxdb/v0.9/tools/shell/ -[udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ -[influxdb]: https://influxdata.com/time-series-platform/influxdb/ -[tsm tree]: https://influxdata.com/blog/new-storage-engine-time-structured-merge-tree/ -[tsm1-commit]: https://github.com/influxdata/influxdb/commit/15d723dc77651bac83e09e2b1c94be480966cb0d -[influx-admin]: https://docs.influxdata.com/influxdb/v0.9/administration/authentication_and_authorization/#create-a-new-admin-user +This document was moved to [administration/monitoring/performance/influxdb_configuration](../administration/monitoring/performance/influxdb_configuration.md). diff --git a/doc/monitoring/performance/influxdb_schema.md b/doc/monitoring/performance/influxdb_schema.md index eff0e29f58d..a989e323e04 100644 --- a/doc/monitoring/performance/influxdb_schema.md +++ b/doc/monitoring/performance/influxdb_schema.md @@ -1,97 +1 @@ -# InfluxDB Schema - -The following measurements are currently stored in InfluxDB: - -- `PROCESS_file_descriptors` -- `PROCESS_gc_statistics` -- `PROCESS_memory_usage` -- `PROCESS_method_calls` -- `PROCESS_object_counts` -- `PROCESS_transactions` -- `PROCESS_views` -- `events` - -Here, `PROCESS` is replaced with either `rails` or `sidekiq` depending on the -process type. In all series, any form of duration is stored in milliseconds. - -## PROCESS_file_descriptors - -This measurement contains the number of open file descriptors over time. The -value field `value` contains the number of descriptors. - -## PROCESS_gc_statistics - -This measurement contains Ruby garbage collection statistics such as the amount -of minor/major GC runs (relative to the last sampling interval), the time spent -in garbage collection cycles, and all fields/values returned by `GC.stat`. - -## PROCESS_memory_usage - -This measurement contains the process' memory usage (in bytes) over time. The -value field `value` contains the number of bytes. - -## PROCESS_method_calls - -This measurement contains the methods called during a transaction along with -their duration, and a name of the transaction action that invoked the method (if -available). The method call duration is stored in the value field `duration`, -while the method name is stored in the tag `method`. The tag `action` contains -the full name of the transaction action. Both the `method` and `action` fields -are in the following format: - -``` -ClassName#method_name -``` - -For example, a method called by the `show` method in the `UsersController` class -would have `action` set to `UsersController#show`. - -## PROCESS_object_counts - -This measurement is used to store retained Ruby objects (per class) and the -amount of retained objects. The number of objects is stored in the `count` value -field while the class name is stored in the `type` tag. - -## PROCESS_transactions - -This measurement is used to store basic transaction details such as the time it -took to complete a transaction, how much time was spent in SQL queries, etc. The -following value fields are available: - -| Value | Description | -| ----- | ----------- | -| `duration` | The total duration of the transaction | -| `allocated_memory` | The amount of bytes allocated while the transaction was running. This value is only reliable when using single-threaded application servers | -| `method_duration` | The total time spent in method calls | -| `sql_duration` | The total time spent in SQL queries | -| `view_duration` | The total time spent in views | - -## PROCESS_views - -This measurement is used to store view rendering timings for a transaction. The -following value fields are available: - -| Value | Description | -| ----- | ----------- | -| `duration` | The rendering time of the view | -| `view` | The path of the view, relative to the application's root directory | - -The `action` tag contains the action name of the transaction that rendered the -view. - -## events - -This measurement is used to store generic events such as the number of Git -pushes, Emails sent, etc. Each point in this measurement has a single value -field called `count`. The value of this field is simply set to `1`. Each point -also has at least one tag: `event`. This tag's value is set to the event name. -Depending on the event type additional tags may be available as well. - ---- - -Read more on: - -- [Introduction to GitLab Performance Monitoring](introduction.md) -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Configuration](influxdb_configuration.md) -- [Grafana Install/Configuration](grafana_configuration.md) +This document was moved to [administration/monitoring/performance/influxdb_schema](../administration/monitoring/performance/influxdb_schema.md). diff --git a/doc/monitoring/performance/introduction.md b/doc/monitoring/performance/introduction.md index 79904916b7e..ab3f3ac1664 100644 --- a/doc/monitoring/performance/introduction.md +++ b/doc/monitoring/performance/introduction.md @@ -1,65 +1 @@ -# GitLab Performance Monitoring - -GitLab comes with its own application performance measuring system as of GitLab -8.4, simply called "GitLab Performance Monitoring". GitLab Performance Monitoring is available in both the -Community and Enterprise editions. - -Apart from this introduction, you are advised to read through the following -documents in order to understand and properly configure GitLab Performance Monitoring: - -- [GitLab Configuration](gitlab_configuration.md) -- [InfluxDB Install/Configuration](influxdb_configuration.md) -- [InfluxDB Schema](influxdb_schema.md) -- [Grafana Install/Configuration](grafana_configuration.md) - -## Introduction to GitLab Performance Monitoring - -GitLab Performance Monitoring makes it possible to measure a wide variety of statistics -including (but not limited to): - -- The time it took to complete a transaction (a web request or Sidekiq job). -- The time spent in running SQL queries and rendering HAML views. -- The time spent executing (instrumented) Ruby methods. -- Ruby object allocations, and retained objects in particular. -- System statistics such as the process' memory usage and open file descriptors. -- Ruby garbage collection statistics. - -Metrics data is written to [InfluxDB][influxdb] over [UDP][influxdb-udp]. Stored -data can be visualized using [Grafana][grafana] or any other application that -supports reading data from InfluxDB. Alternatively data can be queried using the -InfluxDB CLI. - -## Metric Types - -Two types of metrics are collected: - -1. Transaction specific metrics. -1. Sampled metrics, collected at a certain interval in a separate thread. - -### Transaction Metrics - -Transaction metrics are metrics that can be associated with a single -transaction. This includes statistics such as the transaction duration, timings -of any executed SQL queries, time spent rendering HAML views, etc. These metrics -are collected for every Rack request and Sidekiq job processed. - -### Sampled Metrics - -Sampled metrics are metrics that can't be associated with a single transaction. -Examples include garbage collection statistics and retained Ruby objects. These -metrics are collected at a regular interval. This interval is made up out of two -parts: - -1. A user defined interval. -1. A randomly generated offset added on top of the interval, the same offset - can't be used twice in a row. - -The actual interval can be anywhere between a half of the defined interval and a -half above the interval. For example, for a user defined interval of 15 seconds -the actual interval can be anywhere between 7.5 and 22.5. The interval is -re-generated for every sampling run instead of being generated once and re-used -for the duration of the process' lifetime. - -[influxdb]: https://influxdata.com/time-series-platform/influxdb/ -[influxdb-udp]: https://docs.influxdata.com/influxdb/v0.9/write_protocols/udp/ -[grafana]: http://grafana.org/ +This document was moved to [administration/monitoring/performance/introduction](../administration/monitoring/performance/introduction.md). -- cgit v1.2.1 From 44d5be2e8a248657165f932d859a3be4f1ee9089 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 25 Sep 2016 12:24:35 +0200 Subject: Add links to internal docs in Metrics section in the admin area --- app/views/admin/application_settings/_form.html.haml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 0d79ca7dc52..c4c68cd7891 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -221,7 +221,11 @@ %fieldset %legend Metrics %p - These settings require a restart to take effect. + Setup InfluxDB to measure a wide variety of statistics like the time spent + in running SQL queries. These settings require a + = link_to 'restart', help_page_path('administration/restart_gitlab') + to take effect. + = link_to icon('question-circle'), help_page_path('administration/monitoring/performance/introduction') .form-group .col-sm-offset-2.col-sm-10 .checkbox -- cgit v1.2.1 From 05745737c659098d3cc1e9ae0f8eedddac7b3603 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 26 Sep 2016 14:21:39 +0200 Subject: Explain the extra chmod --- lib/gitlab/workhorse.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 5d33f98e89e..594439a5d4b 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -111,7 +111,7 @@ module Gitlab def write_secret bytes = SecureRandom.random_bytes(SECRET_LENGTH) File.open(secret_path, 'w:BINARY', 0600) do |f| - f.chmod(0600) + f.chmod(0600) # If the file already existed, the '0600' passed to 'open' above was a no-op. f.write(Base64.strict_encode64(bytes)) end end -- cgit v1.2.1 From ffec230f537776959f40e66c3cb0809bb9c173b1 Mon Sep 17 00:00:00 2001 From: TMate Software Support Date: Tue, 27 Sep 2016 16:28:19 +0000 Subject: Update migrating_from_svn.md Documentation updated to cover Git/SVN mirror approach to migration from SVN to GitLab. --- doc/workflow/importing/migrating_from_svn.md | 92 +++++++++++++++++++++++++++- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md index 4828bb5dce6..76839315c53 100644 --- a/doc/workflow/importing/migrating_from_svn.md +++ b/doc/workflow/importing/migrating_from_svn.md @@ -4,6 +4,94 @@ Subversion (SVN) is a central version control system (VCS) while Git is a distributed version control system. There are some major differences between the two, for more information consult your favorite search engine. +## Overview + +There are two approaches to SVN to Git migration: + +#### [Git/SVN Mirror](#mirror) + + Make GitLab repository mirror SVN project. + + Git and SVN project are kept in sync; use either one or another. + + Smoothens migration process and allows to manage migration risks. + +#### [Cut over migration](#cutover) + + Translate existing data and history from SVN to Git. + + Fire and forget approach, good for smaller teams. + +## Smooth migration with a Git/SVN mirror using SubGit + +#### Prerequisites + +Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions follow this +[instruction](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html). + +Download SubGit tool from [https://subgit.com/download/](https://subgit.com/download/) + +Unpack downloaded SubGit zip archive to `/opt` directory, subgit will be available +at `/opt/subgit-VERSION/bin/subgit` + +#### Configuration + +In GitLab create new empty repository. In filesystem it will be located at +`/var/opt/gitlab/git-data/repositories/USER/REPOS.git` path. + +Run SubGit to set up a Git/SVN mirror. Make sure `subgit` command is ran +on behalf of the same user that runs GitLab. + +``` +subgit configure --layout auto SVN_PROJECT_URL GIT_REPOS_PATH +``` + +Adjust authors and branches mappings, if necessary: + +``` +edit GIT_REPOS_PATH/subgit/authors.txt +edit GIT_REPOS_PATH/subgit/config +``` + +For more information regarding SubGit configuration options, refer to +[documentation](https://subgit.com/documentation.html) at SubGit web site. + +#### Initial translation + +Run `subgit` to perform initial translation of existing SVN revisions into +Git repository: + +``` +subgit install GIT_REPOS_PATH +``` + +After initial translation is completed, GitLab Git repository and SVN project +will be kept in sync by `subgit` - new Git commits will be translated to SVN +revisions and new SVN revisions will be translated to Git commits. Mirror works +transparently and does not require any special commands. + +Would you prefer to perform one-time cut over migration with `subgit` use +`import` command in place of `install`: + +``` +subgit import GIT_REPOS_PATH +``` + +#### Licensing + +Running SubGit in a mirror mode requires [registration](https://subgit.com/pricing.html). Registration is free for Open Source, +Academic and Startup projects. + +We're currently working on deeper GitLab/SubGit intergation. You may track our +progress at [this issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/990). + +#### Support + +For any questions related to SVN to GitLab migration with SubGit contact us at [support@subgit.com](mailto:support@subgit.com). We support +all our users. + +## Cut over migration with svn2git + If you are currently using an SVN repository, you can migrate the repository to Git and GitLab. We recommend a hard cut over - run the migration command once and then have all developers start using the new GitLab repository immediately. @@ -74,6 +162,4 @@ git push --tags origin ## Contribute to this guide We welcome all contributions that would expand this guide with instructions on -how to migrate from SVN and other version control systems. - - +how to migrate from SVN and other version control systems. \ No newline at end of file -- cgit v1.2.1 From ec50d20d7ca90acb93ff7bf3dd45478a5b467682 Mon Sep 17 00:00:00 2001 From: john McGehee Date: Wed, 28 Sep 2016 15:45:32 -0700 Subject: Rebase and resolve conflicts in backup doc for !3761 --- doc/raketasks/backup_restore.md | 57 ++++++++++++++++++++++++----------------- 1 file changed, 34 insertions(+), 23 deletions(-) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 3f4056dc440..faa7e9eaa44 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -2,34 +2,45 @@ ![backup banner](backup_hrz.png) -## Create a backup of the GitLab system - -A backup creates an archive file that contains the database, all repositories and all attachments. -This archive will be saved in backup_path (see `config/gitlab.yml`). -The filename will be `[TIMESTAMP]_gitlab_backup.tar`. This timestamp can be used to restore an specific backup. -You can only restore a backup to exactly the same version of GitLab that you created it -on, for example 7.2.1. The best way to migrate your repositories from one server to +An application data backup creates an archive file that contains the database, +all repositories and all attachments. +This archive will be saved in `backup_path`, which is specified in the +`config/gitlab.yml` file. +The filename will be `[TIMESTAMP]_gitlab_backup.tar`, where `TIMESTAMP` +identifies the time at which each backup was created. + +You can only restore a backup to exactly the same version of GitLab on which it +was created. The best way to migrate your repositories from one server to another is through backup restore. -You need to keep separate copies of `/etc/gitlab/gitlab-secrets.json` and -`/etc/gitlab/gitlab.rb` (for omnibus packages) or -`/home/git/gitlab/config/secrets.yml` (for installations from source). This file -contains the database encryption keys used for two-factor authentication and CI -secret variables, among other things. If you restore a GitLab backup without -restoring the database encryption key, users who have two-factor authentication -enabled will lose access to your GitLab server. +To restore a backup, you will also need to restore `/etc/gitlab/gitlab-secrets.json` +(for omnibus packages) or `/home/git/gitlab/.secret` (for installations +from source). This file contains the database encryption key used +for two-factor authentication. If you fail to restore this encryption key file +along with the application data backup, users with two-factor +authentication enabled will lose access to your GitLab server. + +## Create a backup of the GitLab system +Use this command if you've installed GitLab with the Omnibus package: ``` -# use this command if you've installed GitLab with the Omnibus package sudo gitlab-rake gitlab:backup:create - -# if you've installed GitLab from source +``` +Use this if you've installed GitLab from source: +``` sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` -Also you can choose what should be backed up by adding environment variable SKIP. Available options: db, -uploads (attachments), repositories, builds(CI build output logs), artifacts (CI build artifacts), lfs (LFS objects). -Use a comma to specify several options at the same time. +You can specify that portions of the application data be skipped using the +environment variable `SKIP`. You can skip: +- `db` +- `uploads` (attachments) +- `repositories` +- `builds` (CI build output logs) +- `artifacts` (CI build artifacts) +- `lfs` (LFS objects) + +Separate multiple data types to skip using a comma. For example: ``` sudo gitlab-rake gitlab:backup:create SKIP=db,uploads @@ -69,7 +80,7 @@ Deleting old backups... [SKIPPING] Starting with GitLab 7.4 you can let the backup script upload the '.tar' file it creates. It uses the [Fog library](http://fog.io/) to perform the upload. In the example below we use Amazon S3 for storage. -But Fog also lets you use [other storage providers](http://fog.io/storage/). +Fog also supports [other storage providers](http://fog.io/storage/). For omnibus packages: @@ -161,7 +172,7 @@ with the name of your bucket: ### Uploading to locally mounted shares You may also send backups to a mounted share (`NFS` / `CIFS` / `SMB` / etc.) by -using the [`Local`](https://github.com/fog/fog-local#usage) storage provider. +using the Fog [`Local`](https://github.com/fog/fog-local#usage) storage provider. The directory pointed to by the `local_root` key **must** be owned by the `git` user **when mounted** (mounting with the `uid=` of the `git` user for `CIFS` and `SMB`) or the user that you are executing the backup tasks under (for omnibus @@ -228,7 +239,7 @@ of using encryption in the first place! If you use an Omnibus package please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration). If you have a cookbook installation there should be a copy of your configuration in Chef. -If you have an installation from source, please consider backing up your `config/secrets.yml` file, `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079). +If you installed from source, please consider backing up your `config/secrets.yml` file, `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079). At the very **minimum** you should backup `/etc/gitlab/gitlab.rb` and `/etc/gitlab/gitlab-secrets.json` (Omnibus), or -- cgit v1.2.1 From 069d739efd17d8a6af5f3798a37e8f2cbfb02e5b Mon Sep 17 00:00:00 2001 From: John McGehee Date: Thu, 29 Sep 2016 20:38:43 +0000 Subject: Added "CI secret variables" per @axil. --- doc/raketasks/backup_restore.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index faa7e9eaa44..91c2df7d464 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -15,9 +15,9 @@ another is through backup restore. To restore a backup, you will also need to restore `/etc/gitlab/gitlab-secrets.json` (for omnibus packages) or `/home/git/gitlab/.secret` (for installations -from source). This file contains the database encryption key used -for two-factor authentication. If you fail to restore this encryption key file -along with the application data backup, users with two-factor +from source). This file contains the database encryption key and CI secret +variables used for two-factor authentication. If you fail to restore this +encryption key file along with the application data backup, users with two-factor authentication enabled will lose access to your GitLab server. ## Create a backup of the GitLab system -- cgit v1.2.1 From d90166172e91dbd63062c24d61e6566e57079a45 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 30 Sep 2016 11:38:56 +0200 Subject: Cache gems in CI on tags --- .gitlab-ci.yml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 3f3873e57c1..899e0b6b105 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -332,3 +332,16 @@ pages: - public only: - master + +# Insurance in case a gem needed by one of our releases gets yanked from +# rubygems.org in the future. +cache gems: + only: + - tags + variables: + SETUP_DB: "false" + script: + - bundle package --all --all-platforms + artifacts: + paths: + - vendor/cache -- cgit v1.2.1 From 4d20083bd7e8839bfef64267535fd3c947a3b374 Mon Sep 17 00:00:00 2001 From: Linus G Thiel Date: Wed, 5 Oct 2016 18:13:49 +0200 Subject: Respond with 404 Not Found for non-existent tags Non-existent tags should be handled with 404 Not Found. --- CHANGELOG | 1 + app/controllers/projects/tags_controller.rb | 2 ++ spec/controllers/projects/tags_controller_spec.rb | 14 ++++++++++++++ 3 files changed, 17 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e410d73d1f6..ea54f37a22e 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.12.0 (unreleased) + - Respond with 404 Not Found for non-existent tags - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Add ability to fork to a specific namespace using API. (ritave) diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 6ea8ee62bc5..40899abf6ee 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -20,6 +20,8 @@ class Projects::TagsController < Projects::ApplicationController def show @tag = @repository.find_tag(params[:id]) + return render_404 if @tag.nil? + @release = @project.releases.find_or_initialize_by(tag: @tag.name) @commit = @repository.commit(@tag.target) end diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index a6995145cc1..5e661c2c41d 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -17,4 +17,18 @@ describe Projects::TagsController do expect(assigns(:releases)).not_to include(invalid_release) end end + + describe 'GET show' do + before { get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, id: id } + + context "valid tag" do + let(:id) { 'v1.0.0' } + it { is_expected.to respond_with(:success) } + end + + context "invalid tag" do + let(:id) { 'latest' } + it { is_expected.to respond_with(:not_found) } + end + end end -- cgit v1.2.1 From 6b9671388d523a03b058e1cc467de77d805fc7a2 Mon Sep 17 00:00:00 2001 From: Linus G Thiel Date: Wed, 5 Oct 2016 18:13:49 +0200 Subject: Respond with 404 Not Found for non-existent tags Non-existent tags should be handled with 404 Not Found. --- CHANGELOG | 2 ++ app/controllers/projects/tags_controller.rb | 2 ++ spec/controllers/projects/tags_controller_spec.rb | 14 ++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 07b2b23003b..1b461f54729 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -90,6 +90,8 @@ v 8.12.1 - Fix issue with search filter labels not displaying v 8.12.0 +v 8.12.0 (unreleased) + - Respond with 404 Not Found for non-existent tags - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Bump fog-aws to v0.11.0 to support ap-south-1 region diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 6ea8ee62bc5..40899abf6ee 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -20,6 +20,8 @@ class Projects::TagsController < Projects::ApplicationController def show @tag = @repository.find_tag(params[:id]) + return render_404 if @tag.nil? + @release = @project.releases.find_or_initialize_by(tag: @tag.name) @commit = @repository.commit(@tag.target) end diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb index a6995145cc1..5e661c2c41d 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -17,4 +17,18 @@ describe Projects::TagsController do expect(assigns(:releases)).not_to include(invalid_release) end end + + describe 'GET show' do + before { get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, id: id } + + context "valid tag" do + let(:id) { 'v1.0.0' } + it { is_expected.to respond_with(:success) } + end + + context "invalid tag" do + let(:id) { 'latest' } + it { is_expected.to respond_with(:not_found) } + end + end end -- cgit v1.2.1 From ff378e19e6dc385f1c85b7704263129a56778752 Mon Sep 17 00:00:00 2001 From: Linus G Thiel Date: Wed, 5 Oct 2016 19:31:33 +0200 Subject: Respond with 404 Not Found for non-existent tags Non-existent tags should be handled with 404 Not Found. --- CHANGELOG | 3 +-- app/controllers/projects/tags_controller.rb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1b461f54729..fd480bf87be 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.13.0 (unreleased) + - Respond with 404 Not Found for non-existent tags (Linus Thiel) - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Use gitlab-shell v3.6.2 (GIT TRACE logging) @@ -90,8 +91,6 @@ v 8.12.1 - Fix issue with search filter labels not displaying v 8.12.0 -v 8.12.0 (unreleased) - - Respond with 404 Not Found for non-existent tags - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Bump fog-aws to v0.11.0 to support ap-south-1 region diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 40899abf6ee..8fea20cefef 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -20,7 +20,7 @@ class Projects::TagsController < Projects::ApplicationController def show @tag = @repository.find_tag(params[:id]) - return render_404 if @tag.nil? + return render_404 unless @tag @release = @project.releases.find_or_initialize_by(tag: @tag.name) @commit = @repository.commit(@tag.target) -- cgit v1.2.1 From 217244074fbf78e2aabe1a5019ae0e9c0501e258 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 4 Oct 2016 15:59:11 +0200 Subject: Move MWBS trigger from build to pipeline event --- app/models/ci/pipeline.rb | 16 +++++++++------- app/models/commit_status.rb | 4 ---- .../add_todo_when_build_fails_service.rb | 4 ++-- app/services/merge_requests/base_service.rb | 21 ++++++++++----------- .../merge_when_build_succeeds_service.rb | 9 +++++---- 5 files changed, 26 insertions(+), 28 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 2cf9892edc5..040ba20eb09 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -56,6 +56,10 @@ module Ci pipeline.finished_at = Time.now end + before_transition do |pipeline| + pipeline.update_duration + end + after_transition [:created, :pending] => :running do |pipeline| MergeRequest::Metrics.where(merge_request_id: pipeline.merge_requests.map(&:id)). update_all(latest_build_started_at: pipeline.started_at, latest_build_finished_at: nil) @@ -66,8 +70,8 @@ module Ci update_all(latest_build_finished_at: pipeline.finished_at) end - before_transition do |pipeline| - pipeline.update_duration + after_transition [:created, :pending, :running] => :success do |pipeline| + MergeRequests::MergeWhenBuildSucceedsService.new(pipeline.project, nil).trigger(pipeline) end after_transition do |pipeline, transition| @@ -292,11 +296,9 @@ module Ci # Merge requests for which the current pipeline is running against # the merge request's latest commit. def merge_requests - @merge_requests ||= - begin - project.merge_requests.where(source_branch: self.ref). - select { |merge_request| merge_request.pipeline.try(:id) == self.id } - end + @merge_requests ||= project.merge_requests + .where(source_branch: self.ref) + .select { |merge_request| merge_request.pipeline.try(:id) == self.id } end private diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 9fa8d17e74e..5a240bcf2c6 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -98,10 +98,6 @@ class CommitStatus < ActiveRecord::Base true end - after_transition [:created, :pending, :running] => :success do |commit_status| - MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) - end - after_transition any => :failed do |commit_status| MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status) end diff --git a/app/services/merge_requests/add_todo_when_build_fails_service.rb b/app/services/merge_requests/add_todo_when_build_fails_service.rb index 566049525cb..d572a928a42 100644 --- a/app/services/merge_requests/add_todo_when_build_fails_service.rb +++ b/app/services/merge_requests/add_todo_when_build_fails_service.rb @@ -2,14 +2,14 @@ module MergeRequests class AddTodoWhenBuildFailsService < MergeRequests::BaseService # Adds a todo to the parent merge_request when a CI build fails def execute(commit_status) - each_merge_request(commit_status) do |merge_request| + commit_status_merge_requests(commit_status) do |merge_request| todo_service.merge_request_build_failed(merge_request) end end # Closes any pending build failed todos for the parent MRs when a build is retried def close(commit_status) - each_merge_request(commit_status) do |merge_request| + commit_status_merge_requests(commit_status) do |merge_request| todo_service.merge_request_build_retried(merge_request) end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index d0d155b7ee1..a726dd98697 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -42,11 +42,9 @@ module MergeRequests super(:merge_request) end - def merge_request_from(commit_status) - branches = commit_status.ref - + def merge_requests_for(branches, sha) # This is for ref-less builds - branches ||= @project.repository.branch_names_contains(commit_status.sha) + branches ||= @project.repository.branch_names_contains(sha) return [] if branches.blank? @@ -56,14 +54,15 @@ module MergeRequests merge_requests.uniq.select(&:source_project) end - def each_merge_request(commit_status) - merge_request_from(commit_status).each do |merge_request| - pipeline = merge_request.pipeline - - next unless pipeline - next unless pipeline.sha == commit_status.sha + def pipeline_merge_requests(pipeline) + merge_requests_for(pipeline.ref, pipeline.sha).each do |merge_request| + yield merge_request + end + end - yield merge_request, pipeline + def commit_status_merge_requests(commit_status) + merge_requests_for(commit_status.ref, commit_status.sha).each do |merge_request| + yield merge_request end end end diff --git a/app/services/merge_requests/merge_when_build_succeeds_service.rb b/app/services/merge_requests/merge_when_build_succeeds_service.rb index 4ad5fb08311..dc159de0058 100644 --- a/app/services/merge_requests/merge_when_build_succeeds_service.rb +++ b/app/services/merge_requests/merge_when_build_succeeds_service.rb @@ -18,12 +18,13 @@ module MergeRequests merge_request.save end - # Triggers the automatic merge of merge_request once the build succeeds - def trigger(commit_status) - each_merge_request(commit_status) do |merge_request, pipeline| + # Triggers the automatic merge of merge_request once the pipeline succeeds + def trigger(pipeline) + return unless pipeline.success? + + pipeline_merge_requests(pipeline) do |merge_request| next unless merge_request.merge_when_build_succeeds? next unless merge_request.mergeable? - next unless pipeline.success? MergeWorker.perform_async(merge_request.id, merge_request.merge_user_id, merge_request.merge_params) end -- cgit v1.2.1 From 9824a69c8a4028b757a3d13d7ed4251589d20589 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 6 Oct 2016 12:19:50 +0200 Subject: Fix tests and code for MWBS event move to pipeline --- app/services/merge_requests/base_service.rb | 6 +++ .../merge_when_build_succeeds_service_spec.rb | 48 +++++++++++----------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index a726dd98697..4f4c1c77ec3 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -56,12 +56,18 @@ module MergeRequests def pipeline_merge_requests(pipeline) merge_requests_for(pipeline.ref, pipeline.sha).each do |merge_request| + next unless pipeline == merge_request.pipeline + yield merge_request end end def commit_status_merge_requests(commit_status) merge_requests_for(commit_status.ref, commit_status.sha).each do |merge_request| + pipeline = merge_request.pipeline + next unless pipeline + next unless pipeline.sha == commit_status.sha + yield merge_request end end diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index 520e906b21f..df8f8b61df5 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -58,42 +58,44 @@ describe MergeRequests::MergeWhenBuildSucceedsService do end describe "#trigger" do - context 'build with ref' do - let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") } + let(:merge_request_ref) { mr_merge_if_green_enabled.source_branch } + let(:merge_request_head) do + project.commit(mr_merge_if_green_enabled.source_branch).id + end - it "merges all merge requests with merge when build succeeds enabled" do - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) - allow(pipeline).to receive(:success?).and_return(true) + context 'when triggered by pipeline with valid ref and sha' do + let(:triggering_pipeline) do + create(:ci_pipeline, project: project, ref: merge_request_ref, + sha: merge_request_head, status: 'success') + end + it "merges all merge requests with merge when build succeeds enabled" do expect(MergeWorker).to receive(:perform_async) - service.trigger(build) + service.trigger(triggering_pipeline) end end - context 'triggered by an old build' do - let(:old_build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") } - let(:build) { create(:ci_build, ref: mr_merge_if_green_enabled.source_branch, status: "success") } - - it "merges all merge requests with merge when build succeeds enabled" do - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) - allow(pipeline).to receive(:success?).and_return(true) - allow(old_build).to receive(:sha).and_return('1234abcdef') + context 'when triggered by an old pipeline' do + let(:old_pipeline) do + create(:ci_pipeline, project: project, ref: merge_request_ref, + sha: '1234abcdef', status: 'success') + end + it 'it does not merge merge request' do expect(MergeWorker).not_to receive(:perform_async) - service.trigger(old_build) + service.trigger(old_pipeline) end end - context 'commit status without ref' do - let(:commit_status) { create(:generic_commit_status, status: 'success') } - - before { mr_merge_if_green_enabled } - - it "doesn't merge a requests for status on other branch" do - allow(project.repository).to receive(:branch_names_contains).with(commit_status.sha).and_return([]) + context 'when triggered by pipeline from a different branch' do + let(:unrelated_pipeline) do + create(:ci_pipeline, project: project, ref: 'feature', + sha: merge_request_head, status: 'success') + end + it 'does not merge request' do expect(MergeWorker).not_to receive(:perform_async) - service.trigger(commit_status) + service.trigger(unrelated_pipeline) end it 'discovers branches and merges all merge requests when status is success' do -- cgit v1.2.1 From cd58410811564130e6975c62fe77d2c9fccce46f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 6 Oct 2016 12:31:39 +0200 Subject: Remove support for branch-less builds in MWBS See !6675#note_16580143 --- app/services/merge_requests/base_service.rb | 20 ++++++++++---------- .../merge_when_build_succeeds_service_spec.rb | 13 +------------ 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 4f4c1c77ec3..58f69a41e14 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -42,20 +42,19 @@ module MergeRequests super(:merge_request) end - def merge_requests_for(branches, sha) - # This is for ref-less builds - branches ||= @project.repository.branch_names_contains(sha) + def merge_requests_for(branch) + origin_merge_requests = @project.origin_merge_requests + .opened.where(source_branch: branch).to_a - return [] if branches.blank? + fork_merge_requests = @project.fork_merge_requests + .opened.where(source_branch: branch).to_a - merge_requests = @project.origin_merge_requests.opened.where(source_branch: branches).to_a - merge_requests += @project.fork_merge_requests.opened.where(source_branch: branches).to_a - - merge_requests.uniq.select(&:source_project) + (origin_merge_requests + fork_merge_requests) + .uniq.select(&:source_project) end def pipeline_merge_requests(pipeline) - merge_requests_for(pipeline.ref, pipeline.sha).each do |merge_request| + merge_requests_for(pipeline.ref).each do |merge_request| next unless pipeline == merge_request.pipeline yield merge_request @@ -63,8 +62,9 @@ module MergeRequests end def commit_status_merge_requests(commit_status) - merge_requests_for(commit_status.ref, commit_status.sha).each do |merge_request| + merge_requests_for(commit_status.ref).each do |merge_request| pipeline = merge_request.pipeline + next unless pipeline next unless pipeline.sha == commit_status.sha diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index df8f8b61df5..03345ed9c15 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -97,20 +97,9 @@ describe MergeRequests::MergeWhenBuildSucceedsService do expect(MergeWorker).not_to receive(:perform_async) service.trigger(unrelated_pipeline) end - - it 'discovers branches and merges all merge requests when status is success' do - allow(project.repository).to receive(:branch_names_contains). - with(commit_status.sha).and_return([mr_merge_if_green_enabled.source_branch]) - allow(pipeline).to receive(:success?).and_return(true) - allow_any_instance_of(MergeRequest).to receive(:pipeline).and_return(pipeline) - allow(pipeline).to receive(:success?).and_return(true) - - expect(MergeWorker).to receive(:perform_async) - service.trigger(commit_status) - end end - context 'properly handles multiple stages' do + context 'when there are multiple stages in the pipeline' do let(:ref) { mr_merge_if_green_enabled.source_branch } let!(:build) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'build', stage: 'build') } let!(:test) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'test', stage: 'test') } -- cgit v1.2.1 From 492b4332a46fa0ae7d6547fe7417977f34c77b99 Mon Sep 17 00:00:00 2001 From: James Date: Mon, 8 Aug 2016 23:30:01 +0100 Subject: Added link to bulk assign issues to MR author. (Issue #18876) --- CHANGELOG | 1 + .../projects/merge_requests_controller.rb | 21 ++++++++- app/helpers/merge_requests_helper.rb | 13 ++++++ .../merge_requests/assign_issues_service.rb | 35 +++++++++++++++ .../projects/merge_requests/widget/_open.html.haml | 1 + config/routes/project.rb | 1 + .../projects/merge_requests_controller_spec.rb | 31 +++++++++++++ spec/features/merge_requests/assign_issues_spec.rb | 51 ++++++++++++++++++++++ .../merge_requests/assign_issues_service_spec.rb | 49 +++++++++++++++++++++ 9 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 app/services/merge_requests/assign_issues_service.rb create mode 100644 spec/features/merge_requests/assign_issues_spec.rb create mode 100644 spec/services/merge_requests/assign_issues_service_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 68962f20d0b..a273e58f65c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -55,6 +55,7 @@ v 8.13.0 (unreleased) - Fix broken repository 500 errors in project list - Fix Pipeline list commit column width should be adjusted - Close todos when accepting merge requests via the API !6486 (tonygambone) + - Ability to batch assign issues relating to a merge request to the author. !5725 (jamedjo) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 8c8c56228ad..8bbf3ec67b3 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -10,7 +10,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled before_action :merge_request, only: [ :edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check, - :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts + :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues ] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines] before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines] @@ -355,6 +355,25 @@ class Projects::MergeRequestsController < Projects::ApplicationController render layout: false end + def assign_related_issues + result = MergeRequests::AssignIssuesService.new(project, current_user, merge_request: @merge_request).execute + + respond_to do |format| + format.html do + case result[:count] + when 0 + flash[:error] = "Failed to assign you issues related to the merge request" + when 1 + flash[:notice] = "1 issue has been assigned to you" + else + flash[:notice] = "#{result[:count]} issues have been assigned to you" + end + + redirect_to(merge_request_path(@merge_request)) + end + end + end + def ci_status pipeline = @merge_request.pipeline if pipeline diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index 8abe7865fed..b0a76765d97 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -72,6 +72,19 @@ module MergeRequestsHelper ) end + def mr_assign_issues_link + issues = MergeRequests::AssignIssuesService.new(@project, + current_user, + merge_request: @merge_request, + closes_issues: mr_closes_issues + ).assignable_issues + path = assign_related_issues_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) + if issues.present? + pluralize_this_issue = issues.count > 1 ? "these issues" : "this issue" + link_to "Assign yourself to #{pluralize_this_issue}", path, method: :post + end + end + def source_branch_with_namespace(merge_request) branch = link_to(merge_request.source_branch, namespace_project_commits_path(merge_request.source_project.namespace, merge_request.source_project, merge_request.source_branch)) diff --git a/app/services/merge_requests/assign_issues_service.rb b/app/services/merge_requests/assign_issues_service.rb new file mode 100644 index 00000000000..2e84f844449 --- /dev/null +++ b/app/services/merge_requests/assign_issues_service.rb @@ -0,0 +1,35 @@ +module MergeRequests + class AssignIssuesService < BaseService + def assignable_issues + @assignable_issues ||= begin + if current_user == merge_request.author + closes_issues. + reject { |issue| issue.assignee_id? }. + select { |issue| can?(current_user, :admin_issue, issue) } + else + [] + end + end + end + + def execute + assignable_issues.each do |issue| + Issues::UpdateService.new(issue.project, current_user, assignee_id: current_user.id).execute(issue) + end + + { + count: assignable_issues.count + } + end + + private + + def merge_request + params[:merge_request] + end + + def closes_issues + @closes_issues ||= params[:closes_issues] || merge_request.closes_issues(current_user) + end + end +end diff --git a/app/views/projects/merge_requests/widget/_open.html.haml b/app/views/projects/merge_requests/widget/_open.html.haml index 6f5ee5f16c5..842b6df310d 100644 --- a/app/views/projects/merge_requests/widget/_open.html.haml +++ b/app/views/projects/merge_requests/widget/_open.html.haml @@ -35,3 +35,4 @@ Accepting this merge request will close #{"issue".pluralize(mr_closes_issues.size)} = succeed '.' do != markdown issues_sentence(mr_closes_issues), pipeline: :gfm, author: @merge_request.author + = mr_assign_issues_link diff --git a/config/routes/project.rb b/config/routes/project.rb index 224ec7e8324..6c0c2a1184f 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -277,6 +277,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: post :remove_wip get :diff_for_path post :resolve_conflicts + post :assign_related_issues end collection do diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 742edd8ba3d..18041bce482 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -708,4 +708,35 @@ describe Projects::MergeRequestsController do end end end + + describe 'POST assign_related_issues' do + let(:issue1) { create(:issue, project: project) } + let(:issue2) { create(:issue, project: project) } + + def post_assign_issues + merge_request.update!(description: "Closes #{issue1.to_reference} and #{issue2.to_reference}", + author: user, + source_branch: 'feature', + target_branch: 'master') + + post :assign_related_issues, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: merge_request.iid + end + + it 'shows a flash message on success' do + post_assign_issues + + expect(flash[:notice]).to eq '2 issues have been assigned to you' + end + + it 'correctly pluralizes flash message on success' do + issue2.update!(assignee: user) + + post_assign_issues + + expect(flash[:notice]).to eq '1 issue has been assigned to you' + end + end end diff --git a/spec/features/merge_requests/assign_issues_spec.rb b/spec/features/merge_requests/assign_issues_spec.rb new file mode 100644 index 00000000000..43cc6f2a2a7 --- /dev/null +++ b/spec/features/merge_requests/assign_issues_spec.rb @@ -0,0 +1,51 @@ +require 'rails_helper' + +feature 'Merge request issue assignment', js: true, feature: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue1) { create(:issue, project: project) } + let(:issue2) { create(:issue, project: project) } + let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user, description: "fixes #{issue1.to_reference} and #{issue2.to_reference}") } + let(:service) { MergeRequests::AssignIssuesService.new(merge_request, user, user, project) } + + before do + project.team << [user, :developer] + end + + def visit_merge_request(current_user = nil) + login_as(current_user || user) + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + context 'logged in as author' do + scenario 'updates related issues' do + visit_merge_request + click_link "Assign yourself to these issues" + + expect(page).to have_content "2 issues have been assigned to you" + end + + it 'returns user to the merge request' do + visit_merge_request + click_link "Assign yourself to these issues" + + expect(page).to have_content merge_request.description + end + + it "doesn't display if related issues are already assigned" do + [issue1, issue2].each { |issue| issue.update!(assignee: user) } + + visit_merge_request + + expect(page).not_to have_content "Assign yourself" + end + end + + context 'not MR author' do + it "doesn't not show assignment link" do + visit_merge_request(create(:user)) + + expect(page).not_to have_content "Assign yourself" + end + end +end diff --git a/spec/services/merge_requests/assign_issues_service_spec.rb b/spec/services/merge_requests/assign_issues_service_spec.rb new file mode 100644 index 00000000000..7aeb95a15ea --- /dev/null +++ b/spec/services/merge_requests/assign_issues_service_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe MergeRequests::AssignIssuesService, services: true do + let(:user) { create(:user) } + let(:project) { create(:project, :public) } + let(:issue) { create(:issue, project: project) } + let(:merge_request) { create(:merge_request, :simple, source_project: project, author: user, description: "fixes #{issue.to_reference}") } + let(:service) { described_class.new(project, user, merge_request: merge_request) } + + before do + project.team << [user, :developer] + end + + it 'finds unassigned issues fixed in merge request' do + expect(service.assignable_issues.map(&:id)).to include(issue.id) + end + + it 'ignores issues already assigned to any user' do + issue.update!(assignee: create(:user)) + + expect(service.assignable_issues).to be_empty + end + + it 'ignores issues the user cannot update assignee on' do + project.team.truncate + + expect(service.assignable_issues).to be_empty + end + + it 'ignores all issues unless current_user is merge_request.author' do + merge_request.update!(author: create(:user)) + + expect(service.assignable_issues).to be_empty + end + + it 'accepts precomputed data for closes_issues' do + issue2 = create(:issue, project: project) + service2 = described_class.new(project, + user, + merge_request: merge_request, + closes_issues: [issue, issue2]) + + expect(service2.assignable_issues.count).to eq 2 + end + + it 'assigns these to the merge request owner' do + expect { service.execute }.to change { issue.reload.assignee }.to(user) + end +end -- cgit v1.2.1 From a43baa056e69827c342e705e2d5ea8cfc67bfd9c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 6 Oct 2016 14:52:00 +0200 Subject: Rename pipeline workers to match current convention --- app/models/commit_status.rb | 4 ++-- app/workers/pipeline_process_worker.rb | 10 ++++++++++ app/workers/pipeline_update_worker.rb | 10 ++++++++++ app/workers/process_pipeline_worker.rb | 10 ---------- app/workers/update_pipeline_worker.rb | 10 ---------- spec/workers/pipeline_proccess_worker_spec.rb | 22 ++++++++++++++++++++++ spec/workers/pipeline_update_worker_spec.rb | 22 ++++++++++++++++++++++ spec/workers/process_pipeline_worker_spec.rb | 22 ---------------------- spec/workers/update_pipeline_worker_spec.rb | 22 ---------------------- 9 files changed, 66 insertions(+), 66 deletions(-) create mode 100644 app/workers/pipeline_process_worker.rb create mode 100644 app/workers/pipeline_update_worker.rb delete mode 100644 app/workers/process_pipeline_worker.rb delete mode 100644 app/workers/update_pipeline_worker.rb create mode 100644 spec/workers/pipeline_proccess_worker_spec.rb create mode 100644 spec/workers/pipeline_update_worker_spec.rb delete mode 100644 spec/workers/process_pipeline_worker_spec.rb delete mode 100644 spec/workers/update_pipeline_worker_spec.rb diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 5a240bcf2c6..3b575e5627d 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -89,10 +89,10 @@ class CommitStatus < ActiveRecord::Base break if transition.loopback? if commit_status.complete? - ProcessPipelineWorker.perform_async(pipeline.id) + PipelineProcessWorker.perform_async(pipeline.id) end - UpdatePipelineWorker.perform_async(pipeline.id) + PipelineUpdateWorker.perform_async(pipeline.id) end true diff --git a/app/workers/pipeline_process_worker.rb b/app/workers/pipeline_process_worker.rb new file mode 100644 index 00000000000..f44227d7086 --- /dev/null +++ b/app/workers/pipeline_process_worker.rb @@ -0,0 +1,10 @@ +class PipelineProcessWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(pipeline_id) + Ci::Pipeline.find_by(id: pipeline_id) + .try(:process!) + end +end diff --git a/app/workers/pipeline_update_worker.rb b/app/workers/pipeline_update_worker.rb new file mode 100644 index 00000000000..44a7f24e401 --- /dev/null +++ b/app/workers/pipeline_update_worker.rb @@ -0,0 +1,10 @@ +class PipelineUpdateWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(pipeline_id) + Ci::Pipeline.find_by(id: pipeline_id) + .try(:update_status) + end +end diff --git a/app/workers/process_pipeline_worker.rb b/app/workers/process_pipeline_worker.rb deleted file mode 100644 index 26ea5f1c24d..00000000000 --- a/app/workers/process_pipeline_worker.rb +++ /dev/null @@ -1,10 +0,0 @@ -class ProcessPipelineWorker - include Sidekiq::Worker - - sidekiq_options queue: :default - - def perform(pipeline_id) - Ci::Pipeline.find_by(id: pipeline_id) - .try(:process!) - end -end diff --git a/app/workers/update_pipeline_worker.rb b/app/workers/update_pipeline_worker.rb deleted file mode 100644 index 6ef5678073e..00000000000 --- a/app/workers/update_pipeline_worker.rb +++ /dev/null @@ -1,10 +0,0 @@ -class UpdatePipelineWorker - include Sidekiq::Worker - - sidekiq_options queue: :default - - def perform(pipeline_id) - Ci::Pipeline.find_by(id: pipeline_id) - .try(:update_status) - end -end diff --git a/spec/workers/pipeline_proccess_worker_spec.rb b/spec/workers/pipeline_proccess_worker_spec.rb new file mode 100644 index 00000000000..86e9d7f6684 --- /dev/null +++ b/spec/workers/pipeline_proccess_worker_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe PipelineProcessWorker do + describe '#perform' do + context 'when pipeline exists' do + let(:pipeline) { create(:ci_pipeline) } + + it 'processes pipeline' do + expect_any_instance_of(Ci::Pipeline).to receive(:process!) + + described_class.new.perform(pipeline.id) + end + end + + context 'when pipeline does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end diff --git a/spec/workers/pipeline_update_worker_spec.rb b/spec/workers/pipeline_update_worker_spec.rb new file mode 100644 index 00000000000..0b456cfd0da --- /dev/null +++ b/spec/workers/pipeline_update_worker_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe PipelineUpdateWorker do + describe '#perform' do + context 'when pipeline exists' do + let(:pipeline) { create(:ci_pipeline) } + + it 'updates pipeline status' do + expect_any_instance_of(Ci::Pipeline).to receive(:update_status) + + described_class.new.perform(pipeline.id) + end + end + + context 'when pipeline does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end diff --git a/spec/workers/process_pipeline_worker_spec.rb b/spec/workers/process_pipeline_worker_spec.rb deleted file mode 100644 index 7b5f98d5763..00000000000 --- a/spec/workers/process_pipeline_worker_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'spec_helper' - -describe ProcessPipelineWorker do - describe '#perform' do - context 'when pipeline exists' do - let(:pipeline) { create(:ci_pipeline) } - - it 'processes pipeline' do - expect_any_instance_of(Ci::Pipeline).to receive(:process!) - - described_class.new.perform(pipeline.id) - end - end - - context 'when pipeline does not exist' do - it 'does not raise exception' do - expect { described_class.new.perform(123) } - .not_to raise_error - end - end - end -end diff --git a/spec/workers/update_pipeline_worker_spec.rb b/spec/workers/update_pipeline_worker_spec.rb deleted file mode 100644 index fadc42b22f0..00000000000 --- a/spec/workers/update_pipeline_worker_spec.rb +++ /dev/null @@ -1,22 +0,0 @@ -require 'spec_helper' - -describe UpdatePipelineWorker do - describe '#perform' do - context 'when pipeline exists' do - let(:pipeline) { create(:ci_pipeline) } - - it 'updates pipeline status' do - expect_any_instance_of(Ci::Pipeline).to receive(:update_status) - - described_class.new.perform(pipeline.id) - end - end - - context 'when pipeline does not exist' do - it 'does not raise exception' do - expect { described_class.new.perform(123) } - .not_to raise_error - end - end - end -end -- cgit v1.2.1 From 67b85665777c7ee4d808a58bff8fbe199cfdbab2 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Thu, 6 Oct 2016 15:37:25 +0100 Subject: Try tmpfs for repository storage, etc --- .gitlab-ci.yml | 2 ++ spec/spec_helper.rb | 5 +++++ 2 files changed, 7 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 8645488335e..cb6f691058e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,6 +19,8 @@ variables: before_script: - source ./scripts/prepare_build.sh - cp config/gitlab.yml.example config/gitlab.yml + - mkdir -p tmp/tests + - mount -t tmpfs tmpfs tmp/tests || echo "tmpfs mount failed, falling back to disc" - bundle --version - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"' - retry gem install knapsack diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b19f5824236..f313bd4f249 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -50,6 +50,11 @@ RSpec.configure do |config| example.run Rails.cache = caching_store end + + config.after(:each) do + FileUtils.rm_rf("tmp/tests/repositories") + FileUtils.mkdir_p("tmp/tests/repositories") + end end FactoryGirl::SyntaxRunner.class_eval do -- cgit v1.2.1 From 90f49cc6969ec522457ddce8e864169f5c91e6aa Mon Sep 17 00:00:00 2001 From: Tjaart van der Walt Date: Thu, 6 Oct 2016 11:53:53 -0500 Subject: Update mail_room gem --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 30c8e801ea1..67fe6aa4e95 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -68,6 +68,7 @@ v 8.13.0 (unreleased) - Grouped pipeline dropdown is a scrollable container v 8.12.5 (unreleased) + - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread v 8.12.4 - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell) diff --git a/Gemfile b/Gemfile index 3e8ce8b2fc5..0cc98ad9210 100644 --- a/Gemfile +++ b/Gemfile @@ -323,7 +323,7 @@ gem 'newrelic_rpm', '~> 3.16' gem 'octokit', '~> 4.3.0' -gem 'mail_room', '~> 0.8' +gem 'mail_room', '~> 0.8.1' gem 'email_reply_parser', '~> 0.5.8' diff --git a/Gemfile.lock b/Gemfile.lock index 96b49faf727..817c1725879 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -399,7 +399,7 @@ GEM systemu (~> 2.6.2) mail (2.6.4) mime-types (>= 1.16, < 4) - mail_room (0.8.0) + mail_room (0.8.1) method_source (0.8.2) mime-types (2.99.3) mimemagic (0.3.0) @@ -886,7 +886,7 @@ DEPENDENCIES license_finder (~> 2.1.0) licensee (~> 8.0.0) loofah (~> 2.0.3) - mail_room (~> 0.8) + mail_room (~> 0.8.1) method_source (~> 0.8) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) -- cgit v1.2.1 From 1d35c5b3aed565f1da0fc696f078642540584ce0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Wed, 21 Sep 2016 02:09:31 -0300 Subject: Improve project policy spec --- app/policies/project_policy.rb | 18 +++-- spec/policies/project_policy_spec.rb | 147 +++++++++++++++++++++++++++++++---- 2 files changed, 143 insertions(+), 22 deletions(-) diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index be25c750d67..a806cf83782 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -98,7 +98,6 @@ class ProjectPolicy < BasePolicy can! :admin_milestone can! :admin_project_snippet can! :admin_project_member - can! :admin_merge_request can! :admin_note can! :admin_wiki can! :admin_project @@ -139,11 +138,18 @@ class ProjectPolicy < BasePolicy def team_access!(user) access = project.team.max_member_access(user.id) - guest_access! if access >= Gitlab::Access::GUEST - reporter_access! if access >= Gitlab::Access::REPORTER - team_member_reporter_access! if access >= Gitlab::Access::REPORTER - developer_access! if access >= Gitlab::Access::DEVELOPER - master_access! if access >= Gitlab::Access::MASTER + return if access < Gitlab::Access::GUEST + guest_access! + + return if access < Gitlab::Access::REPORTER + reporter_access! + team_member_reporter_access! + + return if access < Gitlab::Access::DEVELOPER + developer_access! + + return if access < Gitlab::Access::MASTER + master_access! end def archived_access! diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index a7a06744428..43c8d884a47 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -1,20 +1,68 @@ require 'spec_helper' describe ProjectPolicy, models: true do - let(:project) { create(:empty_project, :public) } let(:guest) { create(:user) } let(:reporter) { create(:user) } let(:dev) { create(:user) } let(:master) { create(:user) } let(:owner) { create(:user) } - let(:admin) { create(:admin) } + let(:project) { create(:empty_project, :public, namespace: owner.namespace) } - let(:users_ordered_by_permissions) do - [nil, guest, reporter, dev, master, owner, admin] + let(:guest_permissions) do + [ + :read_project, :read_board, :read_list, :read_wiki, :read_issue, :read_label, + :read_milestone, :read_project_snippet, :read_project_member, + :read_merge_request, :read_note, :create_project, :create_issue, :create_note, + :upload_file + ] end - let(:users_permissions) do - users_ordered_by_permissions.map { |u| Ability.allowed(u, project).size } + let(:reporter_permissions) do + [ + :download_code, :fork_project, :create_project_snippet, :update_issue, + :admin_issue, :admin_label, :admin_list, :read_commit_status, :read_build, + :read_container_image, :read_pipeline, :read_environment, :read_deployment + ] + end + + let(:team_member_reporter_permissions) do + [ + :build_download_code, :build_read_container_image + ] + end + + let(:developer_permissions) do + [ + :admin_merge_request, :update_merge_request, :create_commit_status, + :update_commit_status, :create_build, :update_build, :create_pipeline, + :update_pipeline, :create_merge_request, :create_wiki, :push_code, + :resolve_note, :create_container_image, :update_container_image, + :create_environment, :create_deployment + ] + end + + let(:master_permissions) do + [ + :push_code_to_protected_branches, :update_project_snippet, :update_environment, + :update_deployment, :admin_milestone, :admin_project_snippet, + :admin_project_member, :admin_note, :admin_wiki, :admin_project, + :admin_commit_status, :admin_build, :admin_container_image, + :admin_pipeline, :admin_environment, :admin_deployment + ] + end + + let(:public_permissions) do + [ + :download_code, :fork_project, :read_commit_status, :read_pipeline, + :read_container_image, :build_download_code, :build_read_container_image + ] + end + + let(:owner_permissions) do + [ + :change_namespace, :change_visibility_level, :rename_project, :remove_project, + :archive_project, :remove_fork_project, :destroy_merge_request, :destroy_issue + ] end before do @@ -22,16 +70,6 @@ describe ProjectPolicy, models: true do project.team << [master, :master] project.team << [dev, :developer] project.team << [reporter, :reporter] - - group = create(:group) - project.project_group_links.create( - group: group, - group_access: Gitlab::Access::MASTER) - group.add_owner(owner) - end - - it 'returns increasing permissions for each level' do - expect(users_permissions).to eq(users_permissions.sort.uniq) end it 'does not include the read_issue permission when the issue author is not a member of the private project' do @@ -46,4 +84,81 @@ describe ProjectPolicy, models: true do expect(Ability.allowed?(user, :read_issue, project)).to be_falsy end + + context 'abilities for non-public projects' do + let(:project) { create(:empty_project, namespace: owner.namespace) } + + subject { described_class.abilities(current_user, project).to_set } + + context 'with no user' do + let(:current_user) { nil } + + it { is_expected.to be_empty } + end + + context 'guests' do + let(:current_user) { guest } + + it do + is_expected.to include(*guest_permissions) + is_expected.not_to include(*reporter_permissions) + is_expected.not_to include(*team_member_reporter_permissions) + is_expected.not_to include(*developer_permissions) + is_expected.not_to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'reporter' do + let(:current_user) { reporter } + + it do + is_expected.to include(*guest_permissions) + is_expected.to include(*reporter_permissions) + is_expected.to include(*team_member_reporter_permissions) + is_expected.not_to include(*developer_permissions) + is_expected.not_to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'developer' do + let(:current_user) { dev } + + it do + is_expected.to include(*guest_permissions) + is_expected.to include(*reporter_permissions) + is_expected.to include(*team_member_reporter_permissions) + is_expected.to include(*developer_permissions) + is_expected.not_to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'master' do + let(:current_user) { master } + + it do + is_expected.to include(*guest_permissions) + is_expected.to include(*reporter_permissions) + is_expected.to include(*team_member_reporter_permissions) + is_expected.to include(*developer_permissions) + is_expected.to include(*master_permissions) + is_expected.not_to include(*owner_permissions) + end + end + + context 'owner' do + let(:current_user) { owner } + + it do + is_expected.to include(*guest_permissions) + is_expected.to include(*reporter_permissions) + is_expected.not_to include(*team_member_reporter_permissions) + is_expected.to include(*developer_permissions) + is_expected.to include(*master_permissions) + is_expected.to include(*owner_permissions) + end + end + end end -- cgit v1.2.1 From a98e4081d1b122d001438d17036f395fc82b9d5c Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 09:32:29 +0200 Subject: Process MWBS in successful pipeline asynchronously --- app/models/ci/pipeline.rb | 2 +- app/workers/pipeline_success_worker.rb | 12 ++++++++++++ spec/workers/pipeline_success_worker_spec.rb | 24 ++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 app/workers/pipeline_success_worker.rb create mode 100644 spec/workers/pipeline_success_worker_spec.rb diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 040ba20eb09..d3a6e0a11f0 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -71,7 +71,7 @@ module Ci end after_transition [:created, :pending, :running] => :success do |pipeline| - MergeRequests::MergeWhenBuildSucceedsService.new(pipeline.project, nil).trigger(pipeline) + PipelineSuccessWorker.perform_async(pipeline.id) end after_transition do |pipeline, transition| diff --git a/app/workers/pipeline_success_worker.rb b/app/workers/pipeline_success_worker.rb new file mode 100644 index 00000000000..5dd443fea59 --- /dev/null +++ b/app/workers/pipeline_success_worker.rb @@ -0,0 +1,12 @@ +class PipelineSuccessWorker + include Sidekiq::Worker + sidekiq_options queue: :default + + def perform(pipeline_id) + Ci::Pipeline.find_by(id: pipeline_id).try do |pipeline| + MergeRequests::MergeWhenBuildSucceedsService + .new(pipeline.project, nil) + .trigger(pipeline) + end + end +end diff --git a/spec/workers/pipeline_success_worker_spec.rb b/spec/workers/pipeline_success_worker_spec.rb new file mode 100644 index 00000000000..5e31cc2c8e7 --- /dev/null +++ b/spec/workers/pipeline_success_worker_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe PipelineSuccessWorker do + describe '#perform' do + context 'when pipeline exists' do + let(:pipeline) { create(:ci_pipeline, status: 'success') } + + it 'performs "merge when pipeline succeeds"' do + expect_any_instance_of( + MergeRequests::MergeWhenBuildSucceedsService + ).to receive(:trigger) + + described_class.new.perform(pipeline.id) + end + end + + context 'when pipeline does not exist' do + it 'does not raise exception' do + expect { described_class.new.perform(123) } + .not_to raise_error + end + end + end +end -- cgit v1.2.1 From 2f66969e43d25192017ebbe4ff56df213a2dae3f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 09:49:43 +0200 Subject: Improve merge when builds succeeds specs readability --- .../merge_when_build_succeeds_spec.rb | 44 ++++++++++++++-------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 60bc07bd1a0..8b6544269a7 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -2,18 +2,26 @@ require 'spec_helper' feature 'Merge When Build Succeeds', feature: true, js: true do let(:user) { create(:user) } + let(:project) { create(:project, :public) } - let(:project) { create(:project, :public) } - let(:merge_request) { create(:merge_request_with_diffs, source_project: project, author: user, title: "Bug NS-04") } + let(:merge_request) do + create(:merge_request_with_diffs, source_project: project, + author: user, + title: 'Bug NS-04') + end - before do - project.team << [user, :master] - project.enable_ci + let(:pipeline) do + create(:ci_pipeline, project: project, + sha: merge_request.diff_head_sha, + ref: merge_request.source_branch) end - context "Active build for Merge Request" do - let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) } - let!(:ci_build) { create(:ci_build, pipeline: pipeline) } + before { project.team << [user, :master] } + + context 'when there is active build for merge request' do + background do + create(:ci_build, pipeline: pipeline) + end before do login_as user @@ -41,21 +49,25 @@ feature 'Merge When Build Succeeds', feature: true, js: true do end end - context 'When it is enabled' do + context 'when merge when build succeeds is enabled' do let(:merge_request) do - create(:merge_request_with_diffs, :simple, source_project: project, author: user, - merge_user: user, title: "MepMep", merge_when_build_succeeds: true) + create(:merge_request_with_diffs, :simple, source_project: project, + author: user, + merge_user: user, + title: 'MepMep', + merge_when_build_succeeds: true) end - let!(:pipeline) { create(:ci_pipeline, project: project, sha: merge_request.diff_head_sha, ref: merge_request.source_branch) } - let!(:ci_build) { create(:ci_build, pipeline: pipeline) } + background do + create(:ci_build, pipeline: pipeline) + end before do login_as user visit_merge_request(merge_request) end - it 'cancels the automatic merge' do + it 'allows to cancel the automatic merge' do click_link "Cancel Automatic Merge" expect(page).to have_button "Merge When Build Succeeds" @@ -72,8 +84,8 @@ feature 'Merge When Build Succeeds', feature: true, js: true do end end - context 'Build is not active' do - it "does not allow for enabling" do + context 'when build is not active' do + it "does not allow to enable merge when build succeeds" do visit_merge_request(merge_request) expect(page).not_to have_link "Merge When Build Succeeds" end -- cgit v1.2.1 From e351ed2d11b611bf06eba8d64d2549fbf60fb605 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 10:10:46 +0200 Subject: Add test that checks actual merge for MWBS feature --- .../merge_requests/merge_when_build_succeeds_spec.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb index 8b6544269a7..bc2b0ff3e2c 100644 --- a/spec/features/merge_requests/merge_when_build_succeeds_spec.rb +++ b/spec/features/merge_requests/merge_when_build_succeeds_spec.rb @@ -58,7 +58,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do merge_when_build_succeeds: true) end - background do + let!(:build) do create(:ci_build, pipeline: pipeline) end @@ -72,7 +72,7 @@ feature 'Merge When Build Succeeds', feature: true, js: true do expect(page).to have_button "Merge When Build Succeeds" - visit_merge_request(merge_request) # Needed to refresh the page + visit_merge_request(merge_request) # refresh the page expect(page).to have_content "Canceled the automatic merge" end @@ -82,6 +82,17 @@ feature 'Merge When Build Succeeds', feature: true, js: true do click_link "Remove Source Branch When Merged" expect(page).to have_content "The source branch will be removed" end + + context 'when build succeeds' do + background { build.success } + + it 'merges merge request' do + visit_merge_request(merge_request) # refresh the page + + expect(page).to have_content 'The changes were merged' + expect(merge_request.reload).to be_merged + end + end end context 'when build is not active' do -- cgit v1.2.1 From bc57fc7cb29b40ae8111e391dfcbd6c55eb6c963 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 10:23:18 +0200 Subject: Update documentation according to changes in MWBS --- .../merge_requests/merge_when_build_succeeds.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/doc/user/project/merge_requests/merge_when_build_succeeds.md b/doc/user/project/merge_requests/merge_when_build_succeeds.md index 011f9cbc381..c138061fd40 100644 --- a/doc/user/project/merge_requests/merge_when_build_succeeds.md +++ b/doc/user/project/merge_requests/merge_when_build_succeeds.md @@ -1,16 +1,16 @@ # Merge When Build Succeeds When reviewing a merge request that looks ready to merge but still has one or -more CI builds running, you can set it to be merged automatically when all -builds succeed. This way, you don't have to wait for the builds to finish and -remember to merge the request manually. +more CI builds running, you can set it to be merged automatically when the +builds pipeline succeed. This way, you don't have to wait for the builds to +finish and remember to merge the request manually. ![Enable](img/merge_when_build_succeeds_enable.png) When you hit the "Merge When Build Succeeds" button, the status of the merge request will be updated to represent the impending merge. If you cannot wait -for the build to succeed and want to merge immediately, this option is available -in the dropdown menu on the right of the main button. +for the pipeline to succeed and want to merge immediately, this option is +available in the dropdown menu on the right of the main button. Both team developers and the author of the merge request have the option to cancel the automatic merge if they find a reason why it shouldn't be merged @@ -18,9 +18,9 @@ after all. ![Status](img/merge_when_build_succeeds_status.png) -When the build succeeds, the merge request will automatically be merged. When -the build fails, the author gets a chance to retry any failed builds, or to -push new commits to fix the failure. +When the pipeline succeeds, the merge request will automatically be merged. +When the pipeline fails, the author gets a chance to retry any failed builds, +or to push new commits to fix the failure. When the builds are retried and succeed on the second try, the merge request will automatically be merged after all. When the merge request is updated with @@ -40,7 +40,7 @@ hit **Save** for the changes to take effect. ![Only allow merge if build succeeds settings](img/merge_when_build_succeeds_only_if_succeeds_settings.png) -From now on, every time the build fails you will not be able to merge the merge -request from the UI, until you make the build pass. +From now on, every time the pipelinefails you will not be able to merge the +merge request from the UI, until you make all relevant builds pass. ![Only allow merge if build succeeds msg](img/merge_when_build_succeeds_only_if_succeeds_msg.png) -- cgit v1.2.1 From 6f7afaa8a0b5594b88cb5a4a2fe2a822ee1334a9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 10:30:40 +0200 Subject: Add Changelog entry for MWBS trigger improvements --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 68962f20d0b..909b2002e66 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.13.0 (unreleased) + - Improve Merge When Build Succeeds triggers and execute on pipeline success. (!6675) - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Use gitlab-shell v3.6.2 (GIT TRACE logging) -- cgit v1.2.1 From f5631ff262fcf84b79c35c34d5a5337440aa4621 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 7 Oct 2016 14:52:30 +0200 Subject: Fix ci pipeline processing with async jobs --- app/models/commit_status.rb | 31 ++++++++++++++++------------- app/services/ci/process_pipeline_service.rb | 2 ++ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 9fa8d17e74e..38554e7a0ca 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -1,6 +1,7 @@ class CommitStatus < ActiveRecord::Base include HasStatus include Importable + include AfterCommitQueue self.table_name = 'ci_builds' @@ -84,20 +85,6 @@ class CommitStatus < ActiveRecord::Base commit_status.update_attributes finished_at: Time.now end - after_transition do |commit_status, transition| - commit_status.pipeline.try do |pipeline| - break if transition.loopback? - - if commit_status.complete? - ProcessPipelineWorker.perform_async(pipeline.id) - end - - UpdatePipelineWorker.perform_async(pipeline.id) - end - - true - end - after_transition [:created, :pending, :running] => :success do |commit_status| MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) end @@ -105,10 +92,26 @@ class CommitStatus < ActiveRecord::Base after_transition any => :failed do |commit_status| MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status) end + + after_transition do: :schedule_pipeline_update end delegate :sha, :short_sha, to: :pipeline + def schedule_pipeline_update + run_after_commit(:process_pipeline!) + end + + def process_pipeline! + pipeline.try do |pipeline| + if complete? + ProcessPipelineWorker.perform_async(pipeline.id) + else + UpdatePipelineWorker.perform_async(pipeline.id) + end + end + end + def before_sha pipeline.before_sha || Gitlab::Git::BLANK_SHA end diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb index 36c93dddadb..d3dd30b2588 100644 --- a/app/services/ci/process_pipeline_service.rb +++ b/app/services/ci/process_pipeline_service.rb @@ -16,6 +16,8 @@ module Ci process_stage(index) end + @pipeline.update_status + # Return a flag if a when builds got enqueued new_builds.flatten.any? end -- cgit v1.2.1 From 4e34a495875aae0a9021da69330c292fd95a6955 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 28 Sep 2016 14:17:27 +0200 Subject: Debounce GfmAutoComplete setup and simplify code somewhat. --- CHANGELOG | 1 + app/assets/javascripts/gfm_auto_complete.js.es6 | 38 +++++++++---------------- 2 files changed, 15 insertions(+), 24 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 392a356c79a..16ce50c944e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -110,6 +110,7 @@ v 8.12.2 - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) - Fix resolve discussion buttons endpoint path - Refactor remnants of CoffeeScript destructured opts and super !6261 + - Prevent running GfmAutocomplete setup for each diff note !6569 v 8.12.1 - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST diff --git a/app/assets/javascripts/gfm_auto_complete.js.es6 b/app/assets/javascripts/gfm_auto_complete.js.es6 index d0786bf0053..845313b6b38 100644 --- a/app/assets/javascripts/gfm_auto_complete.js.es6 +++ b/app/assets/javascripts/gfm_auto_complete.js.es6 @@ -52,37 +52,27 @@ } } }, - setup: function(input) { + setup: _.debounce(function(input) { // Add GFM auto-completion to all input fields, that accept GFM input. this.input = input || $('.js-gfm-input'); // destroy previous instances this.destroyAtWho(); // set up instances this.setupAtWho(); - if (this.dataSource) { - if (!this.dataLoading && !this.cachedData) { - this.dataLoading = true; - setTimeout((function(_this) { - return function() { - var fetch; - fetch = _this.fetchData(_this.dataSource); - return fetch.done(function(data) { - _this.dataLoading = false; - return _this.loadData(data); - }); - }; - // We should wait until initializations are done - // and only trigger the last .setup since - // The previous .dataSource belongs to the previous issuable - // and the last one will have the **proper** .dataSource property - // TODO: Make this a singleton and turn off events when moving to another page - })(this), 1000); - } - if (this.cachedData != null) { - return this.loadData(this.cachedData); - } + + if (this.dataSource && !this.dataLoading && !this.cachedData) { + this.dataLoading = true; + return this.fetchData(this.dataSource) + .done((data) => { + this.dataLoading = false; + this.loadData(data); + }); + }; + + if (this.cachedData != null) { + return this.loadData(this.cachedData); } - }, + }, 1000), setupAtWho: function() { // Emoji this.input.atwho({ -- cgit v1.2.1 From 8d2de73a83b98741dbbbc21fe2cbcdaf7840996d Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Fri, 7 Oct 2016 17:16:42 +0100 Subject: fixup! Added link to bulk assign issues to MR author. (Issue #18876) --- app/controllers/projects/merge_requests_controller.rb | 2 ++ app/services/merge_requests/assign_issues_service.rb | 6 +++--- .../projects/merge_requests_controller_spec.rb | 17 +++++++++++++++++ 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 8bbf3ec67b3..17dc89f717b 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -29,6 +29,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController # Allow modify merge_request before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort] + before_action :authenticate_user!, only: [:assign_related_issues] + before_action :authorize_can_resolve_conflicts!, only: [:conflicts, :resolve_conflicts] def index diff --git a/app/services/merge_requests/assign_issues_service.rb b/app/services/merge_requests/assign_issues_service.rb index 2e84f844449..f636e5fec4f 100644 --- a/app/services/merge_requests/assign_issues_service.rb +++ b/app/services/merge_requests/assign_issues_service.rb @@ -3,9 +3,9 @@ module MergeRequests def assignable_issues @assignable_issues ||= begin if current_user == merge_request.author - closes_issues. - reject { |issue| issue.assignee_id? }. - select { |issue| can?(current_user, :admin_issue, issue) } + closes_issues.select do |issue| + !issue.assignee_id? && can?(current_user, :admin_issue, issue) + end else [] end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 18041bce482..2a68e5a2c9b 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -738,5 +738,22 @@ describe Projects::MergeRequestsController do expect(flash[:notice]).to eq '1 issue has been assigned to you' end + + it 'calls MergeRequests::AssignIssuesService' do + expect(MergeRequests::AssignIssuesService).to receive(:new). + with(project, user, merge_request: merge_request). + and_return(double(execute: {count: 1})) + + post_assign_issues + end + + it 'is skipped when not signed in' do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + sign_out(:user) + + expect(MergeRequests::AssignIssuesService).not_to receive(:new) + + post_assign_issues + end end end -- cgit v1.2.1 From 3530489761fbf892adbf335fe65c0c73b978da39 Mon Sep 17 00:00:00 2001 From: Georg G Date: Fri, 7 Oct 2016 19:15:14 +0200 Subject: Use defined colour for a language when available This commit changes the colours of languages in the language graph to the ones Linguist has defined. When there is no colour defined for a language in Linguist, it will fall back to the old method of finding a colour. --- app/controllers/projects/graphs_controller.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index 092ef32e6e3..f4f2e5841a0 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -35,15 +35,19 @@ class Projects::GraphsController < Projects::ApplicationController def languages @languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages total = @languages.map(&:last).sum + colors = Linguist::Language.colors. + select { |lang| @languages.include? lang.name }. + map { |lang| [lang.name, lang.color] }. + to_h @languages = @languages.map do |language| name, share = language - color = Digest::SHA256.hexdigest(name)[0...6] + color = colors[name] || "##{Digest::SHA256.hexdigest(name)[0...6]}" { value: (share.to_f * 100 / total).round(2), label: name, - color: "##{color}", - highlight: "##{color}" + color: color, + highlight: color } end -- cgit v1.2.1 From 4f1de5faacb6824bad2624b75537e9f4ddbb1207 Mon Sep 17 00:00:00 2001 From: Will Starms Date: Thu, 25 Aug 2016 11:48:08 -0500 Subject: Correct namespace validation to forbid bad names #21077 Adds .git and .atom to the master namespace regex Updates existing group tests and adds two new ones Updates path cleaning to also forbid .atom --- CHANGELOG | 1 + app/models/namespace.rb | 14 ++++++-------- lib/gitlab/regex.rb | 4 ++-- spec/features/groups_spec.rb | 32 +++++++++++++++++++++++++++++++- spec/models/namespace_spec.rb | 1 + 5 files changed, 41 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 5e775cec6d4..00db2f2d40f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.13.0 (unreleased) - Allow the Koding integration to be configured through the API - Add new issue button to each list on Issues Board - Added soft wrap button to repository file/blob editor + - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Fix todos page mobile viewport layout (ClemMakesApps) - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index b7f2b2bbe61..b67049f0f55 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -61,15 +61,13 @@ class Namespace < ActiveRecord::Base def clean_path(path) path = path.dup # Get the email username by removing everything after an `@` sign. - path.gsub!(/@.*\z/, "") - # Usernames can't end in .git, so remove it. - path.gsub!(/\.git\z/, "") - # Remove dashes at the start of the username. - path.gsub!(/\A-+/, "") - # Remove periods at the end of the username. - path.gsub!(/\.+\z/, "") + path.gsub!(/@.*\z/, "") # Remove everything that's not in the list of allowed characters. - path.gsub!(/[^a-zA-Z0-9_\-\.]/, "") + path.gsub!(/[^a-zA-Z0-9_\-\.]/, "") + # Remove trailing violations ('.atom', '.git', or '.') + path.gsub!(/(\.atom|\.git|\.)*\z/, "") + # Remove leading violations ('-') + path.gsub!(/\A\-+/, "") # Users with the great usernames of "." or ".." would end up with a blank username. # Work around that by setting their username to "blank", followed by a counter. diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 776bbcbb5d0..0d30e1bb92e 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -2,7 +2,7 @@ module Gitlab module Regex extend self - NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])'.freeze + NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*[a-zA-Z0-9_\-]|[a-zA-Z0-9_])(? Date: Fri, 7 Oct 2016 15:47:54 -0300 Subject: Update Gitlab Shell to fix errors moving projects between storages --- CHANGELOG | 1 + GITLAB_SHELL_VERSION | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5e775cec6d4..6405b0cf23d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 8.13.0 (unreleased) - Keep refs for each deployment - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Add more tests for calendar contribution (ClemMakesApps) + - Update Gitlab Shell to fix some problems with moving projects between storages - Cache rendered markdown in the database, rather than Redis - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Simplify Mentionable concern instance methods diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 4a788a01dad..0f44168a4d5 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -3.6.3 +3.6.4 -- cgit v1.2.1 From 4695bb8829679a75f66178b2ebf16059d90f6f33 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 8 Oct 2016 05:17:01 -0700 Subject: Use Hash rocket syntax to maintain Ruby 2.1 compatibility in spec --- spec/lib/banzai/object_renderer_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/banzai/object_renderer_spec.rb b/spec/lib/banzai/object_renderer_spec.rb index f5ff236105e..90da78a67dd 100644 --- a/spec/lib/banzai/object_renderer_spec.rb +++ b/spec/lib/banzai/object_renderer_spec.rb @@ -5,7 +5,7 @@ describe Banzai::ObjectRenderer do let(:user) { project.owner } def fake_object(attrs = {}) - object = double(attrs.merge("new_record?": true, "destroyed?": true)) + object = double(attrs.merge("new_record?" => true, "destroyed?" => true)) allow(object).to receive(:markdown_cache_field_for).with(:note).and_return(:note_html) allow(object).to receive(:banzai_render_context).with(:note).and_return(project: nil, author: nil) allow(object).to receive(:update_column).with(:note_html, anything).and_return(true) -- cgit v1.2.1 From e72f2cfcb23c80f3bf634cc339ed2a468d4e9fce Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 8 Oct 2016 09:25:06 -0700 Subject: Fix missing constraints causing route failures when usernames with periods are used Closes #23131 --- config/routes/user.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/config/routes/user.rb b/config/routes/user.rb index c287039ba26..54bbcb18f6a 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -15,7 +15,10 @@ devise_scope :user do end constraints(UserUrlConstrainer.new) do - scope(path: ':username', as: :user, controller: :users) do + scope(path: ':username', + as: :user, + constraints: { username: /[a-zA-Z.0-9_\-]+(? Date: Fri, 9 Sep 2016 11:59:36 -0500 Subject: Remove redundant mixins --- CHANGELOG | 1 + app/assets/stylesheets/framework/avatar.scss | 4 ++-- app/assets/stylesheets/framework/blocks.scss | 2 +- app/assets/stylesheets/framework/buttons.scss | 8 ++++---- app/assets/stylesheets/framework/forms.scss | 2 +- app/assets/stylesheets/framework/issue_box.scss | 2 +- app/assets/stylesheets/framework/markdown_area.scss | 2 +- app/assets/stylesheets/framework/mixins.scss | 11 ----------- app/assets/stylesheets/framework/mobile.scss | 2 +- app/assets/stylesheets/framework/selects.scss | 14 +++++++------- app/assets/stylesheets/framework/sidebar.scss | 6 +++--- app/assets/stylesheets/framework/typography.scss | 2 +- app/assets/stylesheets/pages/cycle_analytics.scss | 2 +- app/assets/stylesheets/pages/editor.scss | 2 +- app/assets/stylesheets/pages/events.scss | 2 +- app/assets/stylesheets/pages/labels.scss | 4 ++-- app/assets/stylesheets/pages/login.scss | 6 +++--- app/assets/stylesheets/pages/merge_requests.scss | 2 +- app/assets/stylesheets/pages/notes.scss | 2 +- app/assets/stylesheets/pages/profiles/preferences.scss | 4 ++-- app/assets/stylesheets/pages/projects.scss | 8 ++++---- app/assets/stylesheets/pages/status.scss | 2 +- 22 files changed, 40 insertions(+), 50 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8bfc5c97820..722731030d0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.13.0 (unreleased) - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Fix todos page mobile viewport layout (ClemMakesApps) - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) + - Remove redundant mixins (ClemMakesApps) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Fix that manual jobs would no longer block jobs in the next stage. !6604 diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index c79b22d4d21..98e301d3799 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -4,7 +4,7 @@ width: 40px; height: 40px; padding: 0; - @include border-radius($avatar_radius); + border-radius: $avatar_radius; border: 1px solid rgba(0, 0, 0, .1); &.avatar-inline { @@ -17,7 +17,7 @@ } &.avatar-tile { - @include border-radius(0); + border-radius: 0; border: none; } diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss index d315db4cb32..8002e56724b 100644 --- a/app/assets/stylesheets/framework/blocks.scss +++ b/app/assets/stylesheets/framework/blocks.scss @@ -133,7 +133,7 @@ } .identicon { - @include border-radius(50%); + border-radius: 50%; } } diff --git a/app/assets/stylesheets/framework/buttons.scss b/app/assets/stylesheets/framework/buttons.scss index d11b2fe7ec2..a7c8d782e9b 100644 --- a/app/assets/stylesheets/framework/buttons.scss +++ b/app/assets/stylesheets/framework/buttons.scss @@ -1,5 +1,5 @@ @mixin btn-default { - @include border-radius(3px); + border-radius: 3px; font-size: $gl-font-size; font-weight: 500; padding: $gl-vert-padding $gl-btn-padding; @@ -8,7 +8,7 @@ &:active { outline: none; background-color: $btn-active-gray; - @include box-shadow($gl-btn-active-background); + box-shadow: $gl-btn-active-background; } } @@ -43,7 +43,7 @@ &:active, &.active { - @include box-shadow ($gl-btn-active-background); + box-shadow: $gl-btn-active-background; background-color: $dark; border-color: $border-dark; @@ -279,7 +279,7 @@ } .active { - @include box-shadow($gl-btn-active-background); + box-shadow: $gl-btn-active-background; border: 1px solid #c6cacf !important; background-color: #e4e7ed !important; diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index a67d31de2f7..05e8ee0190d 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -73,7 +73,7 @@ label { } .form-control { - @include box-shadow(none); + box-shadow: none; border-radius: 3px; padding: $gl-vert-padding $gl-input-padding; } diff --git a/app/assets/stylesheets/framework/issue_box.scss b/app/assets/stylesheets/framework/issue_box.scss index 8bfc0d583c5..ba3930e03bd 100644 --- a/app/assets/stylesheets/framework/issue_box.scss +++ b/app/assets/stylesheets/framework/issue_box.scss @@ -16,7 +16,7 @@ margin-top: 5px; } - @include border-radius(3px); + border-radius: 3px; display: block; float: left; margin-right: 10px; diff --git a/app/assets/stylesheets/framework/markdown_area.scss b/app/assets/stylesheets/framework/markdown_area.scss index edea4ad00eb..6d28d98b283 100644 --- a/app/assets/stylesheets/framework/markdown_area.scss +++ b/app/assets/stylesheets/framework/markdown_area.scss @@ -86,7 +86,7 @@ } .markdown-area { - @include border-radius(0); + border-radius: 0; background: #fff; border: 1px solid #ddd; min-height: 140px; diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 1ec08cdef23..7c207969b0a 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -1,14 +1,3 @@ -/** - * Generic mixins - */ -@mixin box-shadow($shadow) { - box-shadow: $shadow; -} - -@mixin border-radius($radius) { - border-radius: $radius; -} - /** * Prefilled mixins * Mixins with fixed values diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index 76b93b23b95..9fe390eb09d 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -133,5 +133,5 @@ font-size: 20px; color: #777; z-index: 100; - @include box-shadow(0 1px 2px #ddd); + box-shadow: 0 1px 2px #ddd; } diff --git a/app/assets/stylesheets/framework/selects.scss b/app/assets/stylesheets/framework/selects.scss index bcd60391543..79cd26714a3 100644 --- a/app/assets/stylesheets/framework/selects.scss +++ b/app/assets/stylesheets/framework/selects.scss @@ -46,8 +46,8 @@ } .select2-drop { - @include box-shadow(rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0); - @include border-radius ($border-radius-default); + box-shadow: rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0; + border-radius: $border-radius-default; border: none; min-width: 175px; } @@ -72,7 +72,7 @@ .select2-container-active { .select2-choice, .select2-choices { - @include box-shadow(none); + box-shadow: none; } } @@ -82,13 +82,13 @@ outline: 0; background-image: none; background-color: $white-dark; - @include box-shadow($gl-btn-active-gradient); + box-shadow: $gl-btn-active-gradient; } } .select2-container-multi { .select2-choices { - @include border-radius($border-radius-default); + border-radius: $border-radius-default; border-color: $input-border; background: none; @@ -123,7 +123,7 @@ &.select2-container-active .select2-choices, &.select2-dropdown-open .select2-choices { border-color: $border-white-normal; - @include box-shadow($gl-btn-active-gradient); + box-shadow: $gl-btn-active-gradient; } } @@ -157,7 +157,7 @@ background-repeat: no-repeat; background-position: right 0 bottom 6px; border: 1px solid $input-border; - @include border-radius($border-radius-default); + border-radius: $border-radius-default; transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s; &:focus { diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss index 557ef7291cf..ec52f326eb9 100644 --- a/app/assets/stylesheets/framework/sidebar.scss +++ b/app/assets/stylesheets/framework/sidebar.scss @@ -4,7 +4,7 @@ &.page-sidebar-pinned { .sidebar-wrapper { - @include box-shadow(none); + box-shadow: none; } } @@ -17,7 +17,7 @@ width: 0; overflow: hidden; transition: width $sidebar-transition-duration; - @include box-shadow(2px 0 16px 0 $black-transparent); + box-shadow: 2px 0 16px 0 $black-transparent; } } @@ -100,7 +100,7 @@ .count { float: right; padding: 0 8px; - @include border-radius(6px); + border-radius: 6px; } } diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 9f2d53d5206..d099a884f54 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -116,7 +116,7 @@ font-size: 13px; line-height: 1.6em; overflow-x: auto; - @include border-radius(2px); + border-radius: 2px; } p > code { diff --git a/app/assets/stylesheets/pages/cycle_analytics.scss b/app/assets/stylesheets/pages/cycle_analytics.scss index 778471a34d7..d732008de3d 100644 --- a/app/assets/stylesheets/pages/cycle_analytics.scss +++ b/app/assets/stylesheets/pages/cycle_analytics.scss @@ -50,7 +50,7 @@ .bordered-box { border: 1px solid $border-color; - @include border-radius($border-radius-default); + border-radius: $border-radius-default; } diff --git a/app/assets/stylesheets/pages/editor.scss b/app/assets/stylesheets/pages/editor.scss index e1304335271..fcc5f32c738 100644 --- a/app/assets/stylesheets/pages/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -1,7 +1,7 @@ .file-editor { #editor { border: none; - @include border-radius(0); + border-radius: 0; height: 500px; margin: 0; padding: 0; diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss index 1d00da1266c..789d6237df8 100644 --- a/app/assets/stylesheets/pages/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -91,7 +91,7 @@ float: right; border: 1px solid #eee; padding: 5px; - @include border-radius(5px); + border-radius: 5px; background: $gray-light; margin-left: 10px; top: -6px; diff --git a/app/assets/stylesheets/pages/labels.scss b/app/assets/stylesheets/pages/labels.scss index 38c7cd98e41..d35e01f0de3 100644 --- a/app/assets/stylesheets/pages/labels.scss +++ b/app/assets/stylesheets/pages/labels.scss @@ -1,7 +1,7 @@ .suggest-colors { margin-top: 5px; a { - @include border-radius(4px); + border-radius: 4px; width: 30px; height: 30px; display: inline-block; @@ -17,7 +17,7 @@ overflow: hidden; a { - @include border-radius(0); + border-radius: 0; width: (100% / 7); margin-right: 0; margin-bottom: -5px; diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss index 403171d4532..a5ca509163d 100644 --- a/app/assets/stylesheets/pages/login.scss +++ b/app/assets/stylesheets/pages/login.scss @@ -73,12 +73,12 @@ height: auto; &.top { - @include border-radius(5px 5px 0 0); + border-radius: 5px 5px 0 0; margin-bottom: 0; } &.bottom { - @include border-radius(0 0 5px 5px); + border-radius: 0 0 5px 5px; border-top: 0; margin-bottom: 20px; } @@ -86,7 +86,7 @@ &.middle { border-top: 0; margin-bottom: 0; - @include border-radius(0); + border-radius: 0; } &:active, &:focus { diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index bc8693ae467..043f3f3afe1 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -6,7 +6,7 @@ background: $background-color; color: $gl-gray; border: 1px solid $border-color; - @include border-radius(2px); + border-radius: 2px; form { margin-bottom: 0; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 54124a3d658..d399f84a2ff 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -334,7 +334,7 @@ ul.notes { .add-diff-note { margin-top: -4px; - @include border-radius(40px); + border-radius: 40px; background: #fff; padding: 4px; font-size: 16px; diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss index e5859fe7384..f8da0983b77 100644 --- a/app/assets/stylesheets/pages/profiles/preferences.scss +++ b/app/assets/stylesheets/pages/profiles/preferences.scss @@ -4,7 +4,7 @@ text-align: center; .preview { - @include border-radius(4px); + border-radius: 4px; height: 80px; margin-bottom: 10px; @@ -47,7 +47,7 @@ width: 160px; img { - @include border-radius(4px); + border-radius: 4px; max-width: 100%; } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 87548dcb590..530fb0c0d05 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -354,7 +354,7 @@ a.deploy-project-label { justify-content: flex-start; .fork-thumbnail { - @include border-radius($border-radius-base); + border-radius: $border-radius-base; background-color: $white-light; border: 1px solid $border-white-light; height: 202px; @@ -371,7 +371,7 @@ a.deploy-project-label { background-color: $gray-light; border: 1px solid $gray-dark; margin: 0 auto; - @include border-radius(50%); + border-radius: 50%; i { font-size: 100px; color: $gray-dark; @@ -390,7 +390,7 @@ a.deploy-project-label { } img { - @include border-radius(50%); + border-radius: 50%; max-width: 100px; } } @@ -496,7 +496,7 @@ pre.light-well { } .light-well { - @include border-radius (2px); + border-radius: 2px; color: #5b6169; font-size: 13px; diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 0ee7ceecae5..c05f3d5ff32 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -4,7 +4,7 @@ margin-right: 10px; border: 1px solid #eee; white-space: nowrap; - @include border-radius(4px); + border-radius: 4px; &:hover { text-decoration: none; -- cgit v1.2.1 From 15433bc5d6a93e55f143dcb34dc3a807f6d4f02b Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 15 Sep 2016 11:45:16 -0500 Subject: Fix inconsistent options dropdown caret on mobile viewports --- CHANGELOG | 1 + app/views/projects/issues/show.html.haml | 2 +- app/views/projects/merge_requests/show/_mr_title.html.haml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 8bfc5c97820..d9ac4692a7d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 8.13.0 (unreleased) - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) - Speed-up group milestones show page + - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - Add tag shortcut from the Commit page. !6543 - Keep refs for each deployment diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index cbdea209847..9eac5e795d7 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -23,8 +23,8 @@ .issuable-actions .clearfix.issue-btn-group.dropdown %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } - = icon('caret-down') Options + = icon('caret-down') .dropdown-menu.dropdown-menu-align-right.hidden-lg %ul - if can?(current_user, :create_issue, @project) diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 9f2a0f5d99a..e7c5bca6a37 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -19,8 +19,8 @@ .issuable-actions .clearfix.issue-btn-group.dropdown %button.btn.btn-default.pull-left.hidden-md.hidden-lg{ type: "button", data: { toggle: "dropdown" } } - = icon('caret-down') Options + = icon('caret-down') .dropdown-menu.dropdown-menu-align-right.hidden-lg %ul %li{ class: merge_request_button_visibility(@merge_request, true) } -- cgit v1.2.1 From 04afdb613e2282fc6a2debb301a32dad08427f8a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 8 Oct 2016 20:24:43 +0200 Subject: Improve spec for merge when build succeeds feature --- .../merge_when_build_succeeds_service_spec.rb | 24 +++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index 520e906b21f..9a29e400654 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -110,9 +110,21 @@ describe MergeRequests::MergeWhenBuildSucceedsService do context 'properly handles multiple stages' do let(:ref) { mr_merge_if_green_enabled.source_branch } - let!(:build) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'build', stage: 'build') } - let!(:test) { create(:ci_build, :created, pipeline: pipeline, ref: ref, name: 'test', stage: 'test') } - let(:pipeline) { create(:ci_empty_pipeline, ref: mr_merge_if_green_enabled.source_branch, project: project) } + let(:sha) { project.commit(ref).id } + + let(:pipeline) do + create(:ci_empty_pipeline, ref: ref, sha: sha, project: project) + end + + let!(:build) do + create(:ci_build, :created, pipeline: pipeline, ref: ref, + name: 'build', stage: 'build') + end + + let!(:test) do + create(:ci_build, :created, pipeline: pipeline, ref: ref, + name: 'test', stage: 'test') + end before do # This behavior of MergeRequest: we instantiate a new object @@ -121,14 +133,16 @@ describe MergeRequests::MergeWhenBuildSucceedsService do end end - it "doesn't merge if some stages failed" do + it "doesn't merge if any of stages failed" do expect(MergeWorker).not_to receive(:perform_async) + build.success test.drop end - it 'merge when all stages succeeded' do + it 'merges when all stages succeeded' do expect(MergeWorker).to receive(:perform_async) + build.success test.success end -- cgit v1.2.1 From 74fd5cab155cb83ef47ba8f4e78ccfd714f73211 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 8 Oct 2016 20:25:16 +0200 Subject: Improve transitions and run hooks after transaction --- app/models/commit_status.rb | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 38554e7a0ca..81f041cf29e 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -85,33 +85,35 @@ class CommitStatus < ActiveRecord::Base commit_status.update_attributes finished_at: Time.now end + after_transition do |commit_status| + commit_status.run_after_commit do + pipeline.try do |pipeline| + if complete? + ProcessPipelineWorker.perform_async(pipeline.id) + else + UpdatePipelineWorker.perform_async(pipeline.id) + end + end + end + end + after_transition [:created, :pending, :running] => :success do |commit_status| - MergeRequests::MergeWhenBuildSucceedsService.new(commit_status.pipeline.project, nil).trigger(commit_status) + commit_status.run_after_commit do + MergeRequests::MergeWhenBuildSucceedsService + .new(pipeline.project, nil).trigger(self) + end end after_transition any => :failed do |commit_status| - MergeRequests::AddTodoWhenBuildFailsService.new(commit_status.pipeline.project, nil).execute(commit_status) + commit_status.run_after_commit do + MergeRequests::AddTodoWhenBuildFailsService + .new(pipeline.project, nil).execute(self) + end end - - after_transition do: :schedule_pipeline_update end delegate :sha, :short_sha, to: :pipeline - def schedule_pipeline_update - run_after_commit(:process_pipeline!) - end - - def process_pipeline! - pipeline.try do |pipeline| - if complete? - ProcessPipelineWorker.perform_async(pipeline.id) - else - UpdatePipelineWorker.perform_async(pipeline.id) - end - end - end - def before_sha pipeline.before_sha || Gitlab::Git::BLANK_SHA end -- cgit v1.2.1 From 3fb4d86c6dbb2298f6d5b0010ae0f6e26905b934 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 8 Oct 2016 20:34:45 +0200 Subject: Add temporary fix for race condition in MWBS --- app/models/commit_status.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index 81f041cf29e..c8b168f5526 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -99,6 +99,9 @@ class CommitStatus < ActiveRecord::Base after_transition [:created, :pending, :running] => :success do |commit_status| commit_status.run_after_commit do + # TODO, temporary fix for race condition + UpdatePipelineWorker.new.perform(pipeline.id) + MergeRequests::MergeWhenBuildSucceedsService .new(pipeline.project, nil).trigger(self) end -- cgit v1.2.1 From 904de2d64b1b63a82cfba92e02b6c25ff94b725b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 8 Oct 2016 20:42:09 +0200 Subject: Check for transition loopback in commit status --- app/models/commit_status.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index c8b168f5526..5d6d534cd31 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -85,7 +85,9 @@ class CommitStatus < ActiveRecord::Base commit_status.update_attributes finished_at: Time.now end - after_transition do |commit_status| + after_transition do |commit_status, transition| + return if transition.loopback? + commit_status.run_after_commit do pipeline.try do |pipeline| if complete? -- cgit v1.2.1 From ef7f16faaa2e640d7b5453174c1af72decd08217 Mon Sep 17 00:00:00 2001 From: Lemures Lemniscati Date: Sun, 9 Oct 2016 22:42:11 +0900 Subject: Fix a typo in doc/api/labels.md --- CHANGELOG | 1 + doc/api/labels.md | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index caa84707cfb..07f2e4ee1f4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -77,6 +77,7 @@ v 8.13.0 (unreleased) - Retouch environments list and deployments list - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container + - Fix a typo in doc/api/labels.md v 8.12.5 (unreleased) diff --git a/doc/api/labels.md b/doc/api/labels.md index 3653ccf304a..656232cc940 100644 --- a/doc/api/labels.md +++ b/doc/api/labels.md @@ -148,7 +148,7 @@ PUT /projects/:id/labels | --------------- | ------- | --------------------------------- | ------------------------------- | | `id` | integer | yes | The ID of the project | | `name` | string | yes | The name of the existing label | -| `new_name` | string | yes if `color` if not provided | The new name of the label | +| `new_name` | string | yes if `color` is not provided | The new name of the label | | `color` | string | yes if `new_name` is not provided | The new color of the label in 6-digit hex notation with leading `#` sign | | `description` | string | no | The new description of the label | -- cgit v1.2.1 From 90b0162c3b070a7e1fa88e5b0ebaad66e94d3fca Mon Sep 17 00:00:00 2001 From: Greg Laubenstein Date: Fri, 23 Sep 2016 12:29:32 -0500 Subject: reword html titles for merge requests and issues --- CHANGELOG | 1 + app/views/projects/issues/edit.html.haml | 2 +- app/views/projects/issues/show.html.haml | 2 +- app/views/projects/merge_requests/_show.html.haml | 2 +- app/views/projects/merge_requests/edit.html.haml | 2 +- 5 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index caa84707cfb..6b19e9ecf1e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -77,6 +77,7 @@ v 8.13.0 (unreleased) - Retouch environments list and deployments list - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container + - Cleanup issue and merge request page titles (Greg Laubenstein) v 8.12.5 (unreleased) diff --git a/app/views/projects/issues/edit.html.haml b/app/views/projects/issues/edit.html.haml index 7cf1923456e..3a6fbbc7fbc 100644 --- a/app/views/projects/issues/edit.html.haml +++ b/app/views/projects/issues/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", "#{@issue.title} (##{@issue.iid})", "Issues" +- page_title "Edit", "#{@issue.to_reference} #{@issue.title}", "Issues" %h3.page-title Edit Issue ##{@issue.iid} diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index b94d6f8633c..e8c673d48bd 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -1,4 +1,4 @@ -- page_title "#{@issue.title} (##{@issue.iid})", "Issues" +- page_title "#{@issue.to_reference} #{@issue.title}", "Issues" - page_description @issue.description - page_card_attributes @issue.card_attributes diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 46a2d862c91..47dd51639b5 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -1,4 +1,4 @@ -- page_title "#{@merge_request.title} (#{@merge_request.to_reference})", "Merge Requests" +- page_title "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests" - page_description @merge_request.description - page_card_attributes @merge_request.card_attributes - content_for :page_specific_javascripts do diff --git a/app/views/projects/merge_requests/edit.html.haml b/app/views/projects/merge_requests/edit.html.haml index 03159f123f3..7c3ac6652ee 100644 --- a/app/views/projects/merge_requests/edit.html.haml +++ b/app/views/projects/merge_requests/edit.html.haml @@ -1,4 +1,4 @@ -- page_title "Edit", "#{@merge_request.title} (#{@merge_request.to_reference}", "Merge Requests" +- page_title "Edit", "#{@merge_request.to_reference} #{@merge_request.title}", "Merge Requests" %h3.page-title Edit Merge Request #{@merge_request.to_reference} -- cgit v1.2.1 From 40fa1b6e6fa13c859351a6c8b6978d7ca3a4cf58 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Sun, 9 Oct 2016 20:31:28 +0500 Subject: Use user from let instead recreate in before --- spec/controllers/projects/blob_controller_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb index 9444a50b1ce..52d13fb6f9e 100644 --- a/spec/controllers/projects/blob_controller_spec.rb +++ b/spec/controllers/projects/blob_controller_spec.rb @@ -5,7 +5,6 @@ describe Projects::BlobController do let(:user) { create(:user) } before do - user = create(:user) project.team << [user, :master] sign_in(user) -- cgit v1.2.1 From 2de8fc3e13b64dfc50e872486aad0c33b35df8a6 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Mon, 19 Sep 2016 12:31:51 +0100 Subject: removes inconsistency regarding tagging immediately as merged once you create a branch using new branch button and adds changelog entry --- CHANGELOG | 2 ++ app/models/repository.rb | 6 +++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index caa84707cfb..841861293c8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -119,6 +119,8 @@ v 8.12.1 - Fix issue with search filter labels not displaying v 8.12.0 +v 8.12.0 (unreleased) + - Removes inconsistency regarding tagging immediatelly as merged once you create a new branch. !6408 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable - Bump fog-aws to v0.11.0 to support ap-south-1 region diff --git a/app/models/repository.rb b/app/models/repository.rb index bf59b74495b..1bf6e58b9db 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1013,13 +1013,17 @@ class Repository branch_commit = commit(branch_name) root_ref_commit = commit(root_ref) - if branch_commit + if branch_commit && !same_head?(branch_commit.id, root_ref_commit.id) is_ancestor?(branch_commit.id, root_ref_commit.id) else nil end end + def same_head?(first_commit_id, second_commit_id) + first_commit_id == second_commit_id + end + def merge_base(first_commit_id, second_commit_id) first_commit_id = commit(first_commit_id).try(:id) || first_commit_id second_commit_id = commit(second_commit_id).try(:id) || second_commit_id -- cgit v1.2.1 From ff076d88df70a70f6534faefefdca92b059318bf Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Tue, 27 Sep 2016 16:39:29 +0100 Subject: writes tests to verify the issue is solved and fixes breaking issues. --- spec/models/repository_spec.rb | 16 +++++++++++----- spec/support/test_env.rb | 4 +++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 98c64c079b9..6dd3f91be17 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -115,10 +115,16 @@ describe Repository, models: true do describe '#merged_to_root_ref?' do context 'merged branch' do - subject { repository.merged_to_root_ref?('improve/awesome') } + subject { repository.merged_to_root_ref?('branch-merged') } it { is_expected.to be_truthy } end + + context 'not merged branch' do + subject { repository.merged_to_root_ref?('not-merged-branch') } + + it { is_expected.to be_falsey } + end end describe '#can_be_merged?' do @@ -316,7 +322,7 @@ describe Repository, models: true do subject { results.first } it { is_expected.to be_an String } - it { expect(subject.lines[2]).to eq("master:CHANGELOG:188: - Feature: Replace teams with group membership\n") } + it { expect(subject.lines[2]).to eq("master:CHANGELOG:190: - Feature: Replace teams with group membership\n") } end end @@ -960,10 +966,10 @@ describe Repository, models: true do context 'cherry-picking a merge commit' do it 'cherry-picks the changes' do - expect(repository.blob_at_branch('master', 'foo/bar/.gitkeep')).to be_nil + expect(repository.blob_at_branch('improve/awesome', 'foo/bar/.gitkeep')).to be_nil - repository.cherry_pick(user, pickable_merge, 'master') - expect(repository.blob_at_branch('master', 'foo/bar/.gitkeep')).not_to be_nil + repository.cherry_pick(user, pickable_merge, 'improve/awesome') + expect(repository.blob_at_branch('improve/awesome', 'foo/bar/.gitkeep')).not_to be_nil end end end diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index 0097dbf8fad..d56274d0979 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -5,6 +5,8 @@ module TestEnv # When developing the seed repository, comment out the branch you will modify. BRANCH_SHA = { + 'not-merged-branch' => 'b83d6e3', + 'branch-merged' => '498214d', 'empty-branch' => '7efb185', 'ends-with.json' => '98b0d8b', 'flatten-dir' => 'e56497b', @@ -14,7 +16,7 @@ module TestEnv 'improve/awesome' => '5937ac0', 'markdown' => '0ed8c6c', 'lfs' => 'be93687', - 'master' => '5937ac0', + 'master' => 'b83d6e3', "'test'" => 'e56497b', 'orphaned-branch' => '45127a9', 'binary-encoding' => '7b1cf43', -- cgit v1.2.1 From 1cf2b9a6b8ff2359f7fad099dcda5632289854a6 Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Mon, 10 Oct 2016 10:11:46 +0200 Subject: Make searching for commits case insensitive. Fixes #21800. --- CHANGELOG | 1 + app/models/repository.rb | 6 ++++-- spec/models/repository_spec.rb | 18 +++++++++++++----- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index caa84707cfb..38677024a50 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -64,6 +64,7 @@ v 8.13.0 (unreleased) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Add organization field to user profile - Fix deploy status responsiveness error !6633 + - Make searching for commits case insensitive - Fix resolved discussion display in side-by-side diff view !6575 - Optimize GitHub importing for speed and memory - API: expose pipeline data in builds API (!6502, Guilherme Salazar) diff --git a/app/models/repository.rb b/app/models/repository.rb index bf59b74495b..4da1933c189 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -111,8 +111,10 @@ class Repository def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0) ref ||= root_ref - # Limited to 1000 commits for now, could be parameterized? - args = %W(#{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset} --max-count #{limit} --grep=#{query}) + args = %W( + #{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset} + --max-count #{limit} --grep=#{query} --regexp-ignore-case + ) args = args.concat(%W(-- #{path})) if path.present? git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 98c64c079b9..4641f297465 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -97,12 +97,20 @@ describe Repository, models: true do end describe '#find_commits_by_message' do - subject { repository.find_commits_by_message('submodule').map{ |k| k.id } } + it 'returns commits with messages containing a given string' do + commit_ids = repository.find_commits_by_message('submodule').map(&:id) - it { is_expected.to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } - it { is_expected.to include('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } - it { is_expected.to include('cfe32cf61b73a0d5e9f13e774abde7ff789b1660') } - it { is_expected.not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e') } + expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + expect(commit_ids).to include('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') + expect(commit_ids).to include('cfe32cf61b73a0d5e9f13e774abde7ff789b1660') + expect(commit_ids).not_to include('913c66a37b4a45b9769037c55c2d238bd0942d2e') + end + + it 'is case insensitive' do + commit_ids = repository.find_commits_by_message('SUBMODULE').map(&:id) + + expect(commit_ids).to include('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + end end describe '#blob_at' do -- cgit v1.2.1 From f3c55164d21b64de13a75a9004b48005cc7c901a Mon Sep 17 00:00:00 2001 From: Brennan Roberts Date: Sun, 2 Oct 2016 15:11:08 -0700 Subject: Prevent conflict b/w search field and its dropdown MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Stop the global search form's default "action" from fighting with dropdown items when using the keyboard to navigate the dropdown. `e.preventDefault()` is now called on the enter key when a dropdown item is already selected. Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + app/assets/javascripts/gl_dropdown.js | 1 + spec/javascripts/search_autocomplete_spec.js | 21 ++++++++++++++++++++- 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index caa84707cfb..11c75f064eb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -63,6 +63,7 @@ v 8.13.0 (unreleased) - Replace bootstrap caret with fontawesome caret (ClemMakesApps) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Add organization field to user profile + - Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts) - Fix deploy status responsiveness error !6633 - Fix resolved discussion display in side-by-side diff view !6575 - Optimize GitHub importing for speed and memory diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index d4403375643..e034ca68645 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -738,6 +738,7 @@ return false; } if (currentKeyCode === 13 && currentIndex !== -1) { + e.preventDefault(); _this.selectRowAtIndex(); } }; diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 4470fbcb099..333128782a2 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -5,6 +5,8 @@ /*= require lib/utils/common_utils */ /*= require lib/utils/type_utility */ /*= require fuzzaldrin-plus */ +/*= require turbolinks */ +/*= require jquery.turbolinks */ (function() { var addBodyAttributes, assertLinks, dashboardIssuesPath, dashboardMRsPath, groupIssuesPath, groupMRsPath, groupName, mockDashboardOptions, mockGroupOptions, mockProjectOptions, projectIssuesPath, projectMRsPath, projectName, userId, widget; @@ -138,7 +140,7 @@ list = widget.wrap.find('.dropdown-menu').find('ul'); return assertLinks(list, projectIssuesPath, projectMRsPath); }); - return it('should not show category related menu if there is text in the input', function() { + it('should not show category related menu if there is text in the input', function() { var link, list; addBodyAttributes('project'); mockProjectOptions(); @@ -148,6 +150,23 @@ link = "a[href='" + projectIssuesPath + "/?assignee_id=" + userId + "']"; return expect(list.find(link).length).toBe(0); }); + return it('should not submit the search form when selecting an autocomplete row with the keyboard', function() { + var ENTER = 13; + var DOWN = 40; + addBodyAttributes(); + mockDashboardOptions(true); + var submitSpy = spyOnEvent('form', 'submit'); + widget.searchInput.focus(); + widget.wrap.trigger($.Event('keydown', { which: DOWN })); + var enterKeyEvent = $.Event('keydown', { which: ENTER }); + widget.searchInput.trigger(enterKeyEvent); + // This does not currently catch failing behavior. For security reasons, + // browsers will not trigger default behavior (form submit, in this + // example) on JavaScript-created keypresses. + expect(submitSpy).not.toHaveBeenTriggered(); + // Does a worse job at capturing the intent of the test, but works. + expect(enterKeyEvent.isDefaultPrevented()).toBe(true); + }); }); }).call(this); -- cgit v1.2.1 From ed24cf05fa5a3f1d6e4a2aa2740c25ee029e50a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 10 Oct 2016 10:41:52 +0200 Subject: Fix wrong icon in CI build detail sidebar: right-arrow => arrow-right MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/views/projects/builds/_sidebar.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/builds/_sidebar.html.haml b/app/views/projects/builds/_sidebar.html.haml index f5344091cae..966633f1f89 100644 --- a/app/views/projects/builds/_sidebar.html.haml +++ b/app/views/projects/builds/_sidebar.html.haml @@ -128,7 +128,7 @@ - builds.select{|build| build.status == build_status}.each do |build| .build-job{class: ('active' if build == @build), data: {stage: build.stage}} = link_to namespace_project_build_path(@project.namespace, @project, build) do - = icon('right-arrow') + = icon('arrow-right') = ci_icon_for_status(build.status) %span - if build.name -- cgit v1.2.1 From 8592e992a0da4b966f9407ffc91f88ecd7fd750c Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 10 Oct 2016 09:59:35 +0100 Subject: Added copy file path button to diffs Closes #23108 --- CHANGELOG | 1 + app/helpers/button_helper.rb | 3 ++- app/views/projects/diffs/_file.html.haml | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index caa84707cfb..45a554c3749 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -26,6 +26,7 @@ v 8.13.0 (unreleased) - Revert "Label list shows all issues (opened or closed) with that label" - Expose expires_at field when sharing project on API - Fix VueJS template tags being rendered in code comments + - Added copy file path button to merge request diff files - Fix issue with page scrolling to top when closing or pinning sidebar (lukehowell) - Add Issue Board API support (andrebsguedes) - Allow the Koding integration to be configured through the API diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index b478580978b..a695aceea76 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -15,10 +15,11 @@ module ButtonHelper # # See http://clipboardjs.com/#usage def clipboard_button(data = {}) + css_class = data[:class] || 'btn-clipboard' data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) content_tag :button, icon('clipboard'), - class: "btn btn-clipboard", + class: "btn #{css_class}", data: data, type: :button, title: "Copy to Clipboard" diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index d07de45fdde..257e0a855bd 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -8,7 +8,7 @@ = link_to '#', class: 'js-toggle-diff-comments btn active has-tooltip btn-file-option', title: "Toggle comments for this file", disabled: @diff_notes_disabled do = icon('comment') \ - + = clipboard_button(clipboard_text: diff_file.new_path, class: 'btn-file-option') - if editable_diff?(diff_file) - link_opts = @merge_request.id ? { from_merge_request_id: @merge_request.id } : {} = edit_blob_link(@merge_request.source_project, @merge_request.source_branch, diff_file.new_path, -- cgit v1.2.1 From 237c8f66e6608420629503280aaea555ee980022 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Fri, 7 Oct 2016 15:24:09 +0200 Subject: Precalculate trending projects This commit introduces a Sidekiq worker that precalculates the list of trending projects on a daily basis. The resulting set is stored in a database table that is then queried by Project.trending. This setup means that Unicorn workers no longer _may_ have to calculate the list of trending projects. Furthermore it supports filtering without any complex caching mechanisms. The data in the "trending_projects" table is inserted in the same order as the project ranking. This means that getting the projects in the correct order is simply a matter of: SELECT projects.* FROM projects INNER JOIN trending_projects ON trending_projects.project_id = projects.id ORDER BY trending_projects.id ASC; Such a query will only take a few milliseconds at most (as measured on GitLab.com), opposed to a few seconds for the query used for calculating the project ranks. The migration in this commit does not require downtime and takes care of populating an initial list of trending projects. --- app/controllers/explore/projects_controller.rb | 3 +- app/finders/trending_projects_finder.rb | 16 ------- app/models/project.rb | 16 ++----- app/models/trending_project.rb | 35 ++++++++++++++ app/workers/trending_projects_worker.rb | 11 +++++ config/initializers/1_settings.rb | 4 ++ ...0161007133303_precalculate_trending_projects.rb | 38 +++++++++++++++ db/schema.rb | 9 +++- spec/finders/trending_projects_finder_spec.rb | 48 ------------------- spec/models/project_spec.rb | 26 ++-------- spec/models/trending_project_spec.rb | 56 ++++++++++++++++++++++ spec/workers/trending_projects_worker_spec.rb | 11 +++++ 12 files changed, 171 insertions(+), 102 deletions(-) delete mode 100644 app/finders/trending_projects_finder.rb create mode 100644 app/models/trending_project.rb create mode 100644 app/workers/trending_projects_worker.rb create mode 100644 db/migrate/20161007133303_precalculate_trending_projects.rb delete mode 100644 spec/finders/trending_projects_finder_spec.rb create mode 100644 spec/models/trending_project_spec.rb create mode 100644 spec/workers/trending_projects_worker_spec.rb diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index 38e5943eb76..a62c6211372 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -21,8 +21,7 @@ class Explore::ProjectsController < Explore::ApplicationController end def trending - @projects = TrendingProjectsFinder.new.execute - @projects = filter_projects(@projects) + @projects = filter_projects(Project.trending) @projects = @projects.page(params[:page]) respond_to do |format| diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb deleted file mode 100644 index c1e434d9926..00000000000 --- a/app/finders/trending_projects_finder.rb +++ /dev/null @@ -1,16 +0,0 @@ -# Finder for retrieving public trending projects in a given time range. -class TrendingProjectsFinder - # current_user - The currently logged in User, if any. - # last_months - The number of months to limit the trending data to. - def execute(months_limit = 1) - Rails.cache.fetch(cache_key_for(months_limit), expires_in: 1.day) do - Project.public_only.trending(months_limit.months.ago) - end - end - - private - - def cache_key_for(months) - "trending_projects/#{months}" - end -end diff --git a/app/models/project.rb b/app/models/project.rb index 88e4bd14860..74d54e69648 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -375,19 +375,9 @@ class Project < ActiveRecord::Base %r{(?#{name_pattern}/#{name_pattern})} end - def trending(since = 1.month.ago) - # By counting in the JOIN we don't expose the GROUP BY to the outer query. - # This means that calls such as "any?" and "count" just return a number of - # the total count, instead of the counts grouped per project as a Hash. - join_body = "INNER JOIN ( - SELECT project_id, COUNT(*) AS amount - FROM notes - WHERE created_at >= #{sanitize(since)} - AND system IS FALSE - GROUP BY project_id - ) join_note_counts ON projects.id = join_note_counts.project_id" - - joins(join_body).reorder('join_note_counts.amount DESC') + def trending + joins('INNER JOIN trending_projects ON projects.id = trending_projects.project_id'). + reorder('trending_projects.id ASC') end def cached_count diff --git a/app/models/trending_project.rb b/app/models/trending_project.rb new file mode 100644 index 00000000000..27e3732da17 --- /dev/null +++ b/app/models/trending_project.rb @@ -0,0 +1,35 @@ +class TrendingProject < ActiveRecord::Base + belongs_to :project + + # The number of months to include in the trending calculation. + MONTHS_TO_INCLUDE = 1 + + # The maximum number of projects to include in the trending set. + PROJECTS_LIMIT = 100 + + # Populates the trending projects table with the current list of trending + # projects. + def self.refresh! + # The calculation **must** run in a transaction. If the removal of data and + # insertion of new data were to run separately a user might end up with an + # empty list of trending projects for a short period of time. + transaction do + delete_all + + timestamp = connection.quote(MONTHS_TO_INCLUDE.months.ago) + + connection.execute <<-EOF.strip_heredoc + INSERT INTO #{table_name} (project_id) + SELECT project_id + FROM notes + INNER JOIN projects ON projects.id = notes.project_id + WHERE notes.created_at >= #{timestamp} + AND notes.system IS FALSE + AND projects.visibility_level = #{Gitlab::VisibilityLevel::PUBLIC} + GROUP BY project_id + ORDER BY count(*) DESC + LIMIT #{PROJECTS_LIMIT}; + EOF + end + end +end diff --git a/app/workers/trending_projects_worker.rb b/app/workers/trending_projects_worker.rb new file mode 100644 index 00000000000..df4c4a6628b --- /dev/null +++ b/app/workers/trending_projects_worker.rb @@ -0,0 +1,11 @@ +class TrendingProjectsWorker + include Sidekiq::Worker + + sidekiq_options queue: :trending_projects + + def perform + Rails.logger.info('Refreshing trending projects') + + TrendingProject.refresh! + end +end diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index c5ed2162c92..efe0ac9c965 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -304,6 +304,10 @@ Settings.cron_jobs['prune_old_events_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['prune_old_events_worker']['cron'] ||= '* */6 * * *' Settings.cron_jobs['prune_old_events_worker']['job_class'] = 'PruneOldEventsWorker' +Settings.cron_jobs['trending_projects_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['trending_projects_worker']['cron'] = '0 1 * * *' +Settings.cron_jobs['trending_projects_worker']['job_class'] = 'TrendingProjectsWorker' + # # GitLab Shell # diff --git a/db/migrate/20161007133303_precalculate_trending_projects.rb b/db/migrate/20161007133303_precalculate_trending_projects.rb new file mode 100644 index 00000000000..b324cd94268 --- /dev/null +++ b/db/migrate/20161007133303_precalculate_trending_projects.rb @@ -0,0 +1,38 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class PrecalculateTrendingProjects < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + create_table :trending_projects do |t| + t.references :project, index: true, foreign_key: { on_delete: :cascade }, null: false + end + + timestamp = connection.quote(1.month.ago) + + # We're hardcoding the visibility level (public) here so that if it ever + # changes this query doesn't suddenly use the new value (which may break + # later migrations). + visibility = 20 + + execute <<-EOF.strip_heredoc + INSERT INTO trending_projects (project_id) + SELECT project_id + FROM notes + INNER JOIN projects ON projects.id = notes.project_id + WHERE notes.created_at >= #{timestamp} + AND notes.system IS FALSE + AND projects.visibility_level = #{visibility} + GROUP BY project_id + ORDER BY count(*) DESC + LIMIT 100; + EOF + end + + def down + drop_table :trending_projects + end +end diff --git a/db/schema.rb b/db/schema.rb index 56da70b3c02..c5ddf9eee32 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: 20160926145521) do +ActiveRecord::Schema.define(version: 20161007133303) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1070,6 +1070,12 @@ ActiveRecord::Schema.define(version: 20160926145521) do add_index "todos", ["target_type", "target_id"], name: "index_todos_on_target_type_and_target_id", using: :btree add_index "todos", ["user_id"], name: "index_todos_on_user_id", using: :btree + create_table "trending_projects", force: :cascade do |t| + t.integer "project_id", null: false + end + + add_index "trending_projects", ["project_id"], name: "index_trending_projects_on_project_id", using: :btree + create_table "u2f_registrations", force: :cascade do |t| t.text "certificate" t.string "key_handle" @@ -1212,5 +1218,6 @@ ActiveRecord::Schema.define(version: 20160926145521) do 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 "trending_projects", "projects", on_delete: :cascade add_foreign_key "u2f_registrations", "users" end diff --git a/spec/finders/trending_projects_finder_spec.rb b/spec/finders/trending_projects_finder_spec.rb deleted file mode 100644 index cfe15b9defa..00000000000 --- a/spec/finders/trending_projects_finder_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -require 'spec_helper' - -describe TrendingProjectsFinder do - let(:user) { create(:user) } - let(:public_project1) { create(:empty_project, :public) } - let(:public_project2) { create(:empty_project, :public) } - let(:private_project) { create(:empty_project, :private) } - let(:internal_project) { create(:empty_project, :internal) } - - before do - 3.times do - create(:note_on_commit, project: public_project1) - end - - 2.times do - create(:note_on_commit, project: public_project2, created_at: 5.weeks.ago) - end - - create(:note_on_commit, project: private_project) - create(:note_on_commit, project: internal_project) - end - - describe '#execute', caching: true do - context 'without an explicit time range' do - it 'returns public trending projects' do - projects = described_class.new.execute - - expect(projects).to eq([public_project1]) - end - end - - context 'with an explicit time range' do - it 'returns public trending projects' do - projects = described_class.new.execute(2) - - expect(projects).to eq([public_project1, public_project2]) - end - end - - it 'caches the list of projects' do - projects = described_class.new - - expect(Project).to receive(:trending).once - - 2.times { projects.execute } - end - end -end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 8aadfcb439b..dae546a0cdc 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -800,32 +800,14 @@ describe Project, models: true do end create(:note_on_commit, project: project2) - end - - describe 'without an explicit start date' do - subject { described_class.trending.to_a } - it 'sorts Projects by the amount of notes in descending order' do - expect(subject).to eq([project1, project2]) - end + TrendingProject.refresh! end - describe 'with an explicit start date' do - let(:date) { 2.months.ago } + subject { described_class.trending.to_a } - subject { described_class.trending(date).to_a } - - before do - 2.times do - # Little fix for special issue related to Fractional Seconds support for MySQL. - # See: https://github.com/rails/rails/pull/14359/files - create(:note_on_commit, project: project2, created_at: date + 1) - end - end - - it 'sorts Projects by the amount of notes in descending order' do - expect(subject).to eq([project2, project1]) - end + it 'sorts projects by the amount of notes in descending order' do + expect(subject).to eq([project1, project2]) end it 'does not take system notes into account' do diff --git a/spec/models/trending_project_spec.rb b/spec/models/trending_project_spec.rb new file mode 100644 index 00000000000..cc28c6d4004 --- /dev/null +++ b/spec/models/trending_project_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe TrendingProject do + let(:user) { create(:user) } + let(:public_project1) { create(:empty_project, :public) } + let(:public_project2) { create(:empty_project, :public) } + let(:public_project3) { create(:empty_project, :public) } + let(:private_project) { create(:empty_project, :private) } + let(:internal_project) { create(:empty_project, :internal) } + + before do + 3.times do + create(:note_on_commit, project: public_project1) + end + + 2.times do + create(:note_on_commit, project: public_project2) + end + + create(:note_on_commit, project: public_project3, created_at: 5.weeks.ago) + create(:note_on_commit, project: private_project) + create(:note_on_commit, project: internal_project) + end + + describe '.refresh!' do + before do + described_class.refresh! + end + + it 'populates the trending projects table' do + expect(described_class.count).to eq(2) + end + + it 'removes existing rows before populating the table' do + described_class.refresh! + + expect(described_class.count).to eq(2) + end + + it 'stores the project IDs for every trending project' do + rows = described_class.order(id: :asc).all + + expect(rows[0].project_id).to eq(public_project1.id) + expect(rows[1].project_id).to eq(public_project2.id) + end + + it 'does not store projects that fall out of the trending time range' do + expect(described_class.where(project_id: public_project3).any?).to eq(false) + end + + it 'stores only public projects' do + expect(described_class.where(project_id: [public_project1.id, public_project2.id]).count).to eq(2) + expect(described_class.where(project_id: [private_project.id, internal_project.id]).count).to eq(0) + end + end +end diff --git a/spec/workers/trending_projects_worker_spec.rb b/spec/workers/trending_projects_worker_spec.rb new file mode 100644 index 00000000000..c3c6fdcf2d5 --- /dev/null +++ b/spec/workers/trending_projects_worker_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe TrendingProjectsWorker do + describe '#perform' do + it 'refreshes the trending projects' do + expect(TrendingProject).to receive(:refresh!) + + described_class.new.perform + end + end +end -- cgit v1.2.1 From 33ce1976451ee8fc0275e0ae012755053d50ec34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 10 Oct 2016 13:35:26 +0200 Subject: API: New /users/:id/events endpoint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + doc/api/users.md | 146 ++++++++++++++++++++++++++++++++++++++++ lib/api/users.rb | 20 ++++++ spec/requests/api/users_spec.rb | 55 +++++++++++++++ 4 files changed, 222 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e6cdc09b9c6..4c257b36175 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -59,6 +59,7 @@ v 8.13.0 (unreleased) - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created - Add broadcast messages and alerts below sub-nav - Better empty state for Groups view + - API: New /users/:id/events endpoint - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) - Replace bootstrap caret with fontawesome caret (ClemMakesApps) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 diff --git a/doc/api/users.md b/doc/api/users.md index 9be4f2e6ec3..15202010b7b 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -627,3 +627,149 @@ Parameters: Will return `200 OK` on success, `404 User Not Found` is user cannot be found or `403 Forbidden` when trying to unblock a user blocked by LDAP synchronization. + +### Get user contribution events + +Get the contribution events for the specified user, sorted from newest to latest. + +``` +GET /users/:id/events +``` + +Parameters: + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer | yes | The ID of the user | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/user/:id/events +``` + +Example response: + +```json +[ + { + "title": null, + "project_id": 15, + "action_name": "closed", + "target_id": 830, + "target_type": "Issue", + "author_id": 1, + "data": null, + "target_title": "Public project search field", + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/u/root" + }, + "author_username": "root" + }, + { + "title": null, + "project_id": 15, + "action_name": "opened", + "target_id": null, + "target_type": null, + "author_id": 1, + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/u/root" + }, + "author_username": "john", + "data": { + "before": "50d4420237a9de7be1304607147aec22e4a14af7", + "after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428", + "ref": "refs/heads/master", + "user_id": 1, + "user_name": "Dmitriy Zaporozhets", + "repository": { + "name": "gitlabhq", + "url": "git@dev.gitlab.org:gitlab/gitlabhq.git", + "description": "GitLab: self hosted Git management software. \r\nDistributed under the MIT License.", + "homepage": "https://dev.gitlab.org/gitlab/gitlabhq" + }, + "commits": [ + { + "id": "c5feabde2d8cd023215af4d2ceeb7a64839fc428", + "message": "Add simple search to projects in public area", + "timestamp": "2013-05-13T18:18:08+00:00", + "url": "https://dev.gitlab.org/gitlab/gitlabhq/commit/c5feabde2d8cd023215af4d2ceeb7a64839fc428", + "author": { + "name": "Dmitriy Zaporozhets", + "email": "dmitriy.zaporozhets@gmail.com" + } + } + ], + "total_commits_count": 1 + }, + "target_title": null + }, + { + "title": null, + "project_id": 15, + "action_name": "closed", + "target_id": 840, + "target_type": "Issue", + "author_id": 1, + "data": null, + "target_title": "Finish & merge Code search PR", + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/u/root" + }, + "author_username": "root" + }, + { + "title": null, + "project_id": 15, + "action_name": "commented on", + "target_id": 1312, + "target_type": "Note", + "author_id": 1, + "data": null, + "target_title": null, + "created_at": "2015-12-04T10:33:58.089Z", + "note": { + "id": 1312, + "body": "What an awesome day!", + "attachment": null, + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/u/root" + }, + "created_at": "2015-12-04T10:33:56.698Z", + "system": false, + "upvote": false, + "downvote": false, + "noteable_id": 377, + "noteable_type": "Issue" + }, + "author": { + "name": "Dmitriy Zaporozhets", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://localhost:3000/uploads/user/avatar/1/fox_avatar.png", + "web_url": "http://localhost:3000/u/root" + }, + "author_username": "root" + } +] +``` diff --git a/lib/api/users.rb b/lib/api/users.rb index 18c4cad09ae..e868f628404 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -321,6 +321,26 @@ module API user.activate end end + + desc 'Get contribution events of a specified user' do + detail 'This feature was introduced in GitLab 8.13.' + success Entities::Event + end + params do + requires :id, type: String, desc: 'The user ID' + end + get ':id/events' do + user = User.find_by(id: declared(params).id) + not_found!('User') unless user + + events = user.recent_events. + merge(ProjectsFinder.new.execute(current_user)). + references(:project). + with_associations. + page(params[:page]) + + present paginate(events), with: Entities::Event + end end resource :user do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f4ea3bebb4c..56b2ec6271e 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -895,4 +895,59 @@ describe API::API, api: true do expect{put api("/users/ASDF/block", admin) }.to raise_error(ActionController::RoutingError) end end + + describe 'GET /user/:id/events' do + let(:user) { create(:user) } + let(:lambda_user) { create(:user) } + let(:project) { create(:empty_project) } + let(:note) { create(:note_on_issue, note: 'What an awesome day!', project: project) } + + before do + project.add_user(user, :developer) + EventCreateService.new.leave_note(note, user) + end + + context "as a user than cannot see the event's project" do + it 'returns no events' do + get api("/users/#{user.id}/events", lambda_user) + + expect(response).to have_http_status(200) + expect(json_response).to be_empty + end + end + + context "as a user than can see the event's project" do + it_behaves_like 'a paginated resources' do + let(:request) { get api("/users/#{user.id}/events", user) } + end + + context 'joined event' do + it 'returns the "joined" event' do + get api("/users/#{user.id}/events", user) + + first_event = json_response.first + + expect(first_event['action_name']).to eq('commented on') + expect(first_event['project_id'].to_i).to eq(project.id) + expect(first_event['author_username']).to eq(user.username) + expect(first_event['note']['id']).to eq(note.id) + expect(first_event['note']['body']).to eq('What an awesome day!') + + last_event = json_response.last + + expect(last_event['action_name']).to eq('joined') + expect(last_event['project_id'].to_i).to eq(project.id) + expect(last_event['author_username']).to eq(user.username) + expect(last_event['author']['name']).to eq(user.name) + end + end + end + + it 'returns a 404 error if not found' do + get api('/users/42/events', user) + + expect(response).to have_http_status(404) + expect(json_response['message']).to eq('404 User Not Found') + end + end end -- cgit v1.2.1 From 7336e73b69040ecb6e8f474d029c8b741090efb2 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Mon, 10 Oct 2016 15:22:09 +0200 Subject: Add link to test coverage report to README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8236f986b56..a6b30aff5a0 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # GitLab -[![build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) -[![coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) +[![Build status](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/build.svg)](https://gitlab.com/gitlab-org/gitlab-ce/commits/master) +[![CE coverage report](https://gitlab.com/gitlab-org/gitlab-ce/badges/master/coverage.svg?job=coverage)](http://gitlab-org.gitlab.io/gitlab-ce/coverage-ruby) [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) [![Core Infrastructure Initiative Best Practices](https://bestpractices.coreinfrastructure.org/projects/42/badge)](https://bestpractices.coreinfrastructure.org/projects/42) -- cgit v1.2.1 From 66c32cab1af621caa6ae3cb24b82b344d43512a5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 14:17:06 +0300 Subject: Remove NamespacesController The main purpose of this controller was redirect to group or user page when URL like https://gitlab.com/gitlab-org was used. Now this functionality is handled by contrainers and take user to correct controller right from the start Signed-off-by: Dmitriy Zaporozhets --- app/controllers/namespaces_controller.rb | 25 ------ config/routes.rb | 2 - spec/controllers/namespaces_controller_spec.rb | 118 ------------------------- 3 files changed, 145 deletions(-) delete mode 100644 app/controllers/namespaces_controller.rb delete mode 100644 spec/controllers/namespaces_controller_spec.rb diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb deleted file mode 100644 index 83eec1bf4a2..00000000000 --- a/app/controllers/namespaces_controller.rb +++ /dev/null @@ -1,25 +0,0 @@ -class NamespacesController < ApplicationController - skip_before_action :authenticate_user! - - def show - namespace = Namespace.find_by(path: params[:id]) - - if namespace - if namespace.is_a?(Group) - group = namespace - else - user = namespace.owner - end - end - - if user - redirect_to user_path(user) - elsif group && can?(current_user, :read_group, group) - redirect_to group_path(group) - elsif current_user.nil? - authenticate_user! - else - render_404 - end - end -end diff --git a/config/routes.rb b/config/routes.rb index bf7c5b76128..83c3a42c19f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -87,7 +87,5 @@ Rails.application.routes.draw do # Get all keys of user get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } - get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } - root to: "root#index" end diff --git a/spec/controllers/namespaces_controller_spec.rb b/spec/controllers/namespaces_controller_spec.rb deleted file mode 100644 index 2b334ed1172..00000000000 --- a/spec/controllers/namespaces_controller_spec.rb +++ /dev/null @@ -1,118 +0,0 @@ -require 'spec_helper' - -describe NamespacesController do - let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } - - describe "GET show" do - context "when the namespace belongs to a user" do - let!(:other_user) { create(:user) } - - it "redirects to the user's page" do - get :show, id: other_user.username - - expect(response).to redirect_to(user_path(other_user)) - end - end - - context "when the namespace belongs to a group" do - let!(:group) { create(:group) } - - context "when the group is public" do - context "when not signed in" do - it "redirects to the group's page" do - get :show, id: group.path - - expect(response).to redirect_to(group_path(group)) - end - end - - context "when signed in" do - before do - sign_in(user) - end - - it "redirects to the group's page" do - get :show, id: group.path - - expect(response).to redirect_to(group_path(group)) - end - end - end - - context "when the group is private" do - before do - group.update_attribute(:visibility_level, Group::PRIVATE) - end - - context "when not signed in" do - it "redirects to the sign in page" do - get :show, id: group.path - expect(response).to redirect_to(new_user_session_path) - end - end - - context "when signed in" do - before do - sign_in(user) - end - - context "when the user has access to the group" do - before do - group.add_developer(user) - end - - context "when the user is blocked" do - before do - user.block - end - - it "redirects to the sign in page" do - get :show, id: group.path - - expect(response).to redirect_to(new_user_session_path) - end - end - - context "when the user isn't blocked" do - it "redirects to the group's page" do - get :show, id: group.path - - expect(response).to redirect_to(group_path(group)) - end - end - end - - context "when the user doesn't have access to the group" do - it "responds with status 404" do - get :show, id: group.path - - expect(response).to have_http_status(404) - end - end - end - end - end - - context "when the namespace doesn't exist" do - context "when signed in" do - before do - sign_in(user) - end - - it "responds with status 404" do - get :show, id: "doesntexist" - - expect(response).to have_http_status(404) - end - end - - context "when not signed in" do - it "redirects to the sign in page" do - get :show, id: "doesntexist" - - expect(response).to redirect_to(new_user_session_path) - end - end - end - end -end -- cgit v1.2.1 From 68ab7047dae98172a0bd8b92956f2ee51b9167a0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 14:53:49 +0300 Subject: Update git over http test to match new routing Signed-off-by: Dmitriy Zaporozhets --- spec/requests/git_http_spec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index c0c1e62e910..413d06715b3 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -412,10 +412,9 @@ describe 'Git HTTP requests', lib: true do context "when the params are anything else" do let(:params) { { service: 'git-implode-pack' } } - before { get path, params } - it "redirects to the sign-in page" do - expect(response).to redirect_to(new_user_session_path) + it "fails to find a route" do + expect { get(path, params) }.to raise_error(ActionController::RoutingError) end end end -- cgit v1.2.1 From d6cfc0042ed2ce9a33f31a6c44661c136e861b98 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 16:39:57 +0300 Subject: Catch any undefined API routing and return 400 Bad Request Signed-off-by: Dmitriy Zaporozhets --- lib/api/api.rb | 4 ++++ spec/requests/api/users_spec.rb | 4 ++-- spec/routing/routing_spec.rb | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/api/api.rb b/lib/api/api.rb index 0bbf73a1b63..95a64f14ac7 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -73,5 +73,9 @@ module API mount ::API::Triggers mount ::API::Users mount ::API::Variables + + route :any, '*path' do + error!('400 Bad Request', 400) + end end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f4ea3bebb4c..c040000e8bb 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -90,9 +90,9 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns a 404 if invalid ID" do + it "returns a 400 if invalid ID" do get api("/users/1ASDF", user) - expect(response).to have_http_status(404) + expect(response).to have_http_status(400) end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 0dd00af878d..0ee1c811dfb 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -266,7 +266,9 @@ describe "Groups", "routing" do end it "also display group#show on the short path" do - expect(get('/1')).to route_to('namespaces#show', id: '1') + allow(Group).to receive(:find_by_path).and_return(true) + + expect(get('/1')).to route_to('groups#show', id: '1') end end -- cgit v1.2.1 From fdfc93679d1ca91d4666095ba2ca732fdb273947 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 18:39:04 +0300 Subject: Fix API specs behaviour for invalid routing Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + doc/api/README.md | 13 +++++++++ spec/requests/api/project_hooks_spec.rb | 5 ++-- spec/requests/api/users_spec.rb | 52 +++++++++++++++++++++------------ 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 72e146f4f3f..aec47dd66f2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -82,6 +82,7 @@ v 8.13.0 (unreleased) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container - Fix a typo in doc/api/labels.md + - API: all unknown routing will be handled with 400 Bad Request v 8.12.5 (unreleased) diff --git a/doc/api/README.md b/doc/api/README.md index bbd5bcfb386..8004a00659c 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -355,6 +355,19 @@ follows: } ``` +## Bad request + +When you try to access API URL that does not exist you will receive 400 Bad Request. + +``` +HTTP/1.1 400 Bad Request +Content-Type: application/json +{ + "error": "400 Bad Request" +} +``` + + ## Clients There are many unofficial GitLab API Clients for most of the popular diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 765dc8a8f66..5d739802095 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -163,9 +163,10 @@ describe API::API, 'ProjectHooks', api: true do expect(response).to have_http_status(404) end - it "returns a 405 error if hook id not given" do + it "returns a 400 error if hook id not given" do delete api("/projects/#{project.id}/hooks", user) - expect(response).to have_http_status(405) + + expect(response).to have_http_status(400) end it "returns a 404 if a user attempts to delete project hooks he/she does not own" do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index c040000e8bb..9537b0ec83d 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -92,6 +92,7 @@ describe API::API, api: true do it "returns a 400 if invalid ID" do get api("/users/1ASDF", user) + expect(response).to have_http_status(400) end end @@ -340,8 +341,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "raises error for invalid ID" do - expect{put api("/users/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 if invalid ID" do + put api("/users/ASDF", admin) + + expect(response).to have_http_status(400) end it 'returns 400 error if user does not validate' do @@ -525,9 +528,10 @@ describe API::API, api: true do expect(json_response.first['email']).to eq(email.email) end - it "raises error for invalid ID" do + it "returns a 400 for invalid ID" do put api("/users/ASDF/emails", admin) - expect(response).to have_http_status(405) + + expect(response).to have_http_status(400) end end end @@ -566,8 +570,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Email Not Found') end - it "raises error for invalid ID" do - expect{delete api("/users/ASDF/emails/bar", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 for invalid ID" do + delete api("/users/ASDF/emails/bar", admin) + + expect(response).to have_http_status(400) end end end @@ -600,8 +606,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 User Not Found') end - it "raises error for invalid ID" do - expect{delete api("/users/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 for invalid ID" do + delete api("/users/ASDF", admin) + + expect(response).to have_http_status(400) end end @@ -667,9 +675,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns 404 for invalid ID" do + it "returns 400 for invalid ID" do get api("/users/keys/ASDF", admin) - expect(response).to have_http_status(404) + + expect(response).to have_http_status(400) end end @@ -727,8 +736,10 @@ describe API::API, api: true do expect(response).to have_http_status(401) end - it "raises error for invalid ID" do - expect{delete api("/users/keys/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 for invalid ID" do + delete api("/users/keys/ASDF", admin) + + expect(response).to have_http_status(400) end end @@ -776,9 +787,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns 404 for invalid ID" do + it "returns 400 for invalid ID" do get api("/users/emails/ASDF", admin) - expect(response).to have_http_status(404) + + expect(response).to have_http_status(400) end end @@ -825,8 +837,10 @@ describe API::API, api: true do expect(response).to have_http_status(401) end - it "raises error for invalid ID" do - expect{delete api("/users/emails/ASDF", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 for invalid ID" do + delete api("/users/emails/ASDF", admin) + + expect(response).to have_http_status(400) end end @@ -891,8 +905,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 User Not Found') end - it "raises error for invalid ID" do - expect{put api("/users/ASDF/block", admin) }.to raise_error(ActionController::RoutingError) + it "returns a 400 for invalid ID" do + put api("/users/ASDF/block", admin) + + expect(response).to have_http_status(400) end end end -- cgit v1.2.1 From 137ebcfb3cb013174f2885776a47264cffd193a6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 20:18:02 +0300 Subject: Replace undefined Grape routing code from 400 to 404 Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + doc/api/README.md | 6 ++-- lib/api/api.rb | 2 +- spec/requests/api/project_hooks_spec.rb | 4 +-- spec/requests/api/users_spec.rb | 50 +++++++++++++++++---------------- 5 files changed, 33 insertions(+), 30 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index aec47dd66f2..fc5e02cfd73 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -83,6 +83,7 @@ v 8.13.0 (unreleased) - Grouped pipeline dropdown is a scrollable container - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 400 Bad Request + - API: all unknown routing will be handled with 404 Not Found v 8.12.5 (unreleased) diff --git a/doc/api/README.md b/doc/api/README.md index 8004a00659c..d47e537debc 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -357,13 +357,13 @@ follows: ## Bad request -When you try to access API URL that does not exist you will receive 400 Bad Request. +When you try to access API URL that does not exist you will receive 404 Not Found. ``` -HTTP/1.1 400 Bad Request +HTTP/1.1 404 Not Found Content-Type: application/json { - "error": "400 Bad Request" + "error": "404 Not Found" } ``` diff --git a/lib/api/api.rb b/lib/api/api.rb index 95a64f14ac7..99722a0a65c 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -75,7 +75,7 @@ module API mount ::API::Variables route :any, '*path' do - error!('400 Bad Request', 400) + error!('404 Not Found', 404) end end end diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index 5d739802095..cfcdcad74cd 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -163,10 +163,10 @@ describe API::API, 'ProjectHooks', api: true do expect(response).to have_http_status(404) end - it "returns a 400 error if hook id not given" do + it "returns a 404 error if hook id not given" do delete api("/projects/#{project.id}/hooks", user) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end it "returns a 404 if a user attempts to delete project hooks he/she does not own" do diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 9537b0ec83d..f0dd0592adb 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -90,10 +90,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns a 400 if invalid ID" do + it "returns a 404 for invalid ID" do get api("/users/1ASDF", user) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -341,10 +341,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns a 400 if invalid ID" do + it "returns a 404 if invalid ID" do put api("/users/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end it 'returns 400 error if user does not validate' do @@ -410,9 +410,9 @@ describe API::API, api: true do end.to change{ user.keys.count }.by(1) end - it "returns 400 for invalid ID" do + it "returns 404 for invalid ID" do post api("/users/999999/keys", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -496,9 +496,10 @@ describe API::API, api: true do end.to change{ user.emails.count }.by(1) end - it "raises error for invalid ID" do + it "returns a 404 for invalid ID" do post api("/users/999999/emails", admin) - expect(response).to have_http_status(400) + + expect(response).to have_http_status(404) end end @@ -528,10 +529,10 @@ describe API::API, api: true do expect(json_response.first['email']).to eq(email.email) end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do put api("/users/ASDF/emails", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end end @@ -570,10 +571,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Email Not Found') end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do delete api("/users/ASDF/emails/bar", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end end @@ -606,10 +607,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 User Not Found') end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do delete api("/users/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -662,6 +663,7 @@ describe API::API, api: true do it "returns 404 Not Found within invalid ID" do get api("/user/keys/42", user) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Not found') end @@ -675,10 +677,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns 400 for invalid ID" do + it "returns 404 for invalid ID" do get api("/users/keys/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -736,10 +738,10 @@ describe API::API, api: true do expect(response).to have_http_status(401) end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do delete api("/users/keys/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -787,10 +789,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 Not found') end - it "returns 400 for invalid ID" do + it "returns 404 for invalid ID" do get api("/users/emails/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -837,10 +839,10 @@ describe API::API, api: true do expect(response).to have_http_status(401) end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do delete api("/users/emails/ASDF", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end @@ -905,10 +907,10 @@ describe API::API, api: true do expect(json_response['message']).to eq('404 User Not Found') end - it "returns a 400 for invalid ID" do + it "returns a 404 for invalid ID" do put api("/users/ASDF/block", admin) - expect(response).to have_http_status(400) + expect(response).to have_http_status(404) end end end -- cgit v1.2.1 From 3e49123dd5f8cb3c04184ce53e95f598e82a9f88 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 7 Oct 2016 21:09:41 +0300 Subject: Fix api users spec for post request with invalid id Signed-off-by: Dmitriy Zaporozhets --- spec/requests/api/users_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index f0dd0592adb..b002949b41b 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -410,9 +410,9 @@ describe API::API, api: true do end.to change{ user.keys.count }.by(1) end - it "returns 404 for invalid ID" do + it "returns 400 for invalid ID" do post api("/users/999999/keys", admin) - expect(response).to have_http_status(404) + expect(response).to have_http_status(400) end end @@ -496,10 +496,10 @@ describe API::API, api: true do end.to change{ user.emails.count }.by(1) end - it "returns a 404 for invalid ID" do + it "returns a 400 for invalid ID" do post api("/users/999999/emails", admin) - expect(response).to have_http_status(404) + expect(response).to have_http_status(400) end end -- cgit v1.2.1 From dc37ef6139ba96fe0b90f7c0188161b298269fef Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 10 Oct 2016 16:04:05 +0300 Subject: Better wording in API readme Signed-off-by: Dmitriy Zaporozhets --- doc/api/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/api/README.md b/doc/api/README.md index d47e537debc..9e907689c80 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -355,9 +355,9 @@ follows: } ``` -## Bad request +## Unknown route -When you try to access API URL that does not exist you will receive 404 Not Found. +When you try to access an API URL that does not exist you will receive 404 Not Found. ``` HTTP/1.1 404 Not Found -- cgit v1.2.1 From 42a6f5c1acb22f75c231a8f82019044e19235782 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 10 Oct 2016 03:32:46 +0100 Subject: Changed placeholder to 'Commit hash' Changed 'Commit hash' to 'Git revision' --- app/views/projects/network/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index b2ece44d966..29df1bab04e 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -8,7 +8,7 @@ .project-network .controls = form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f| - = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: 'search-input form-control input-mx-250 search-sha' + = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Git revision", class: 'search-input form-control input-mx-250 search-sha' = button_tag class: 'btn btn-success' do = icon('search') .inline.prepend-left-20 -- cgit v1.2.1 From 4fff9d27d24ef1d959784cfa64d9a3189351c6ce Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 10 Oct 2016 14:44:00 +0100 Subject: Changed 'Compare branches, tags or commit ranges' to 'Compare Git revisions' --- app/views/projects/compare/index.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index e9ff8e90dd5..45be6581cfc 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -4,7 +4,7 @@ %div{ class: container_class } .sub-header-block - Compare branches, tags or commit ranges. + Compare Git revisions. %br Fill input field with commit id like %code.label-branch 4eedf23 -- cgit v1.2.1 From ee2d392255ceb4669f87ef0589535f0e61f2430f Mon Sep 17 00:00:00 2001 From: Gennady Trafimenkov Date: Fri, 5 Aug 2016 09:31:45 +0300 Subject: Clarify which token should be used to delete a runner --- CHANGELOG | 1 + doc/api/ci/runners.md | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bee5bc769ca..26ec441c27f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -483,6 +483,7 @@ v 8.11.0 - Add pipeline events hook - Bump gitlab_git to speedup DiffCollection iterations - Rewrite description of a blocked user in admin settings. (Elias Werberich) + - Clarify documentation for Runners API (Gennady Trafimenkov) - 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) diff --git a/doc/api/ci/runners.md b/doc/api/ci/runners.md index ecec53fde03..16028d1f124 100644 --- a/doc/api/ci/runners.md +++ b/doc/api/ci/runners.md @@ -12,7 +12,9 @@ communication channel. For the consumer API see the This API uses two types of authentication: 1. Unique Runner's token, which is the token assigned to the Runner after it - has been registered. + has been registered. This token can be found on the Runner's edit page (go to + **Project > Runners**, select one of the Runners listed under **Runners activated for + this project**). 2. Using Runners' registration token. This is a token that can be found in project's settings. @@ -48,7 +50,7 @@ DELETE /ci/api/v1/runners/delete | Attribute | Type | Required | Description | | --------- | ------- | --------- | ----------- | -| `token` | string | yes | Runner's registration token | +| `token` | string | yes | Unique Runner's token | Example request: -- cgit v1.2.1 From 09ccf6cce3e191f5971c96f1ce8b227a8f3682af Mon Sep 17 00:00:00 2001 From: Georg G Date: Mon, 10 Oct 2016 16:18:26 +0200 Subject: Use Linguist::Language[] instead of creating a hash --- CHANGELOG | 1 + app/controllers/projects/graphs_controller.rb | 6 +----- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 392a356c79a..e81df2fbf11 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -36,6 +36,7 @@ v 8.13.0 (unreleased) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Fix that manual jobs would no longer block jobs in the next stage. !6604 - Add configurable email subject suffix (Fu Xu) + - Use defined colour for a language when available !6748 (nilsding) - Added tooltip to fork count on project show page. (Justin DiPierro) - Use a ConnectionPool for Rails.cache on Sidekiq servers - Replace `alias_method_chain` with `Module#prepend` diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index f4f2e5841a0..923e7340e69 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -35,14 +35,10 @@ class Projects::GraphsController < Projects::ApplicationController def languages @languages = Linguist::Repository.new(@repository.rugged, @repository.rugged.head.target_id).languages total = @languages.map(&:last).sum - colors = Linguist::Language.colors. - select { |lang| @languages.include? lang.name }. - map { |lang| [lang.name, lang.color] }. - to_h @languages = @languages.map do |language| name, share = language - color = colors[name] || "##{Digest::SHA256.hexdigest(name)[0...6]}" + color = Linguist::Language[name].color || "##{Digest::SHA256.hexdigest(name)[0...6]}" { value: (share.to_f * 100 / total).round(2), label: name, -- cgit v1.2.1 From c69b81839430500de49d089df2049e778c8b7ef8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 10 Oct 2016 17:37:08 +0300 Subject: Fix duplicate entry in CHANGELOG Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index fc5e02cfd73..54f73419571 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -82,7 +82,6 @@ v 8.13.0 (unreleased) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container - Fix a typo in doc/api/labels.md - - API: all unknown routing will be handled with 400 Bad Request - API: all unknown routing will be handled with 404 Not Found v 8.12.5 (unreleased) -- cgit v1.2.1 From aac2af5bc42fbca355d7d5f5ab03ef662b876219 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Mon, 10 Oct 2016 15:46:26 +0100 Subject: HTMLEntityFilter -> HtmlEntityFilter --- lib/banzai/filter/html_entity_filter.rb | 2 +- lib/banzai/pipeline/single_line_pipeline.rb | 2 +- spec/lib/banzai/filter/html_entity_filter_spec.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/banzai/filter/html_entity_filter.rb b/lib/banzai/filter/html_entity_filter.rb index 4ef8b3b6dcf..e008fd428b0 100644 --- a/lib/banzai/filter/html_entity_filter.rb +++ b/lib/banzai/filter/html_entity_filter.rb @@ -3,7 +3,7 @@ require 'erb' module Banzai module Filter # Text filter that escapes these HTML entities: & " < > - class HTMLEntityFilter < HTML::Pipeline::TextFilter + class HtmlEntityFilter < HTML::Pipeline::TextFilter def call ERB::Util.html_escape(text) end diff --git a/lib/banzai/pipeline/single_line_pipeline.rb b/lib/banzai/pipeline/single_line_pipeline.rb index 30bc035d085..1929099931b 100644 --- a/lib/banzai/pipeline/single_line_pipeline.rb +++ b/lib/banzai/pipeline/single_line_pipeline.rb @@ -3,7 +3,7 @@ module Banzai class SingleLinePipeline < GfmPipeline def self.filters @filters ||= FilterArray[ - Filter::HTMLEntityFilter, + Filter::HtmlEntityFilter, Filter::SanitizationFilter, Filter::EmojiFilter, diff --git a/spec/lib/banzai/filter/html_entity_filter_spec.rb b/spec/lib/banzai/filter/html_entity_filter_spec.rb index 6dc4a970071..4c68ce6d6e4 100644 --- a/spec/lib/banzai/filter/html_entity_filter_spec.rb +++ b/spec/lib/banzai/filter/html_entity_filter_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Banzai::Filter::HTMLEntityFilter, lib: true do +describe Banzai::Filter::HtmlEntityFilter, lib: true do include FilterSpecHelper let(:unescaped) { 'foo &&&' } -- cgit v1.2.1 From 36bf0b67e56af452b6a4ba9b03a1ee684cab2a78 Mon Sep 17 00:00:00 2001 From: Takuya Noguchi Date: Fri, 7 Oct 2016 01:01:32 +0900 Subject: Remove Ci::ApplicationController MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + app/controllers/ci/application_controller.rb | 7 ------- app/controllers/ci/lints_controller.rb | 2 +- app/controllers/ci/projects_controller.rb | 2 +- 4 files changed, 3 insertions(+), 9 deletions(-) delete mode 100644 app/controllers/ci/application_controller.rb diff --git a/CHANGELOG b/CHANGELOG index 26ec441c27f..7081dfacace 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -82,6 +82,7 @@ v 8.13.0 (unreleased) - Retouch environments list and deployments list - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Grouped pipeline dropdown is a scrollable container + - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) - Fix a typo in doc/api/labels.md v 8.12.5 (unreleased) diff --git a/app/controllers/ci/application_controller.rb b/app/controllers/ci/application_controller.rb deleted file mode 100644 index 5bb7d499cdc..00000000000 --- a/app/controllers/ci/application_controller.rb +++ /dev/null @@ -1,7 +0,0 @@ -module Ci - class ApplicationController < ::ApplicationController - def self.railtie_helpers_paths - "app/helpers/ci" - end - end -end diff --git a/app/controllers/ci/lints_controller.rb b/app/controllers/ci/lints_controller.rb index 78012960252..3eb485de9db 100644 --- a/app/controllers/ci/lints_controller.rb +++ b/app/controllers/ci/lints_controller.rb @@ -1,5 +1,5 @@ module Ci - class LintsController < ApplicationController + class LintsController < ::ApplicationController before_action :authenticate_user! def show diff --git a/app/controllers/ci/projects_controller.rb b/app/controllers/ci/projects_controller.rb index aa894fde36b..ff297d6ff13 100644 --- a/app/controllers/ci/projects_controller.rb +++ b/app/controllers/ci/projects_controller.rb @@ -1,5 +1,5 @@ module Ci - class ProjectsController < Ci::ApplicationController + class ProjectsController < ::ApplicationController before_action :project before_action :no_cache, only: [:badge] before_action :authorize_read_project!, except: [:badge, :index] -- cgit v1.2.1 From ee9b75fd37dd29e062cedeafefe1e02fa1b65914 Mon Sep 17 00:00:00 2001 From: Jerdog Date: Mon, 19 Sep 2016 15:54:49 -0500 Subject: Changes to make Git basics more intuitive - updated verbiage where appropriate - changed "git config" commands to include quotes for variables to be more in line with standard practive and to avoid issues with spaces - updated CHANGELOG as part of commit --- CHANGELOG | 1 + doc/gitlab-basics/start-using-git.md | 20 +++++++++----------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bee5bc769ca..3a32622ad55 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -323,6 +323,7 @@ v 8.11.7 - Avoid conflict with admin labels when importing GitHub labels. !6158 - Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234 - Allow the Rails cookie to be used for API authentication. + - Updating verbiage on git basics to be more intuitive v 8.11.6 - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md index b61f436c1a4..4f7015f7dbc 100644 --- a/doc/gitlab-basics/start-using-git.md +++ b/doc/gitlab-basics/start-using-git.md @@ -1,11 +1,10 @@ # Start using Git on the command line -If you want to start using a Git and GitLab, make sure that you have created an -account on GitLab. +If you want to start using Git and GitLab, make sure that you have created and/or signed into an [account on GitLab](https://gitlab.com/users/sign_in). ## Open a shell -Depending on your operating system, find the shell of your preference. Here are some suggestions. +Depending on your operating system, you will need to use a shell of your preference. Here are some suggestions: - [Terminal](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line) on Mac OSX @@ -22,19 +21,19 @@ Type the following command and then press enter: git --version ``` -You should receive a message that will tell you which Git version you have in your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). +You should receive a message that will tell you which Git version you have on your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git). If Git doesn't automatically download, there's an option on the website to [download manually](https://git-scm.com/downloads). Then follow the steps on the installation window. -After you finished installing, open a new shell and type "git --version" again to verify that it was correctly installed. +After you are finished installing, open a new shell and type "git --version" again to verify that it was correctly installed. ## Add your Git username and set your email -It is important because every Git commit that you create will use this information. +It is important to configure your Git username and email address as every Git commit that you create will use this information to identify who has submitted the commit. On your shell, type the following command to add your username: ``` -git config --global user.name ADD YOUR USERNAME +git config --global user.name "your_username_as_you_want_it_to_appear" ``` Then verify that you have the correct username: @@ -44,7 +43,7 @@ git config --global user.name To set your email address, type the following command: ``` -git config --global user.email ADD YOUR EMAIL +git config --global user.email "your_email_address@domain" ``` To verify that you entered your email correctly, type: @@ -52,7 +51,7 @@ To verify that you entered your email correctly, type: git config --global user.email ``` -You'll need to do this only once because you are using the "--global" option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the "--global" option when you’re in that project. +You'll need to do this only once as you are using the "--global" option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the "--global" option when you’re in that project. ## Check your information @@ -76,7 +75,7 @@ git pull REMOTE NAME-OF-BRANCH -u (REMOTE: origin) (NAME-OF-BRANCH: could be "master" or an existing branch) ### Create a branch -Spaces won't be recognized, so you need to use a hyphen or underscore. +Spaces won't be recognized, so you will need to use a hyphen or underscore. ``` git checkout -b NAME-OF-BRANCH ``` @@ -127,4 +126,3 @@ You need to be in the master branch. git checkout master git merge NAME-OF-BRANCH ``` - -- cgit v1.2.1 From db32660450b4768e86af07388e11a72b11ebb929 Mon Sep 17 00:00:00 2001 From: Jerdog Date: Mon, 19 Sep 2016 16:18:03 -0500 Subject: Updating changes based on feedback from @connorshea --- doc/gitlab-basics/start-using-git.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md index 4f7015f7dbc..42cd8bb3e48 100644 --- a/doc/gitlab-basics/start-using-git.md +++ b/doc/gitlab-basics/start-using-git.md @@ -1,6 +1,6 @@ # Start using Git on the command line -If you want to start using Git and GitLab, make sure that you have created and/or signed into an [account on GitLab](https://gitlab.com/users/sign_in). +If you want to start using Git and GitLab, make sure that you have created and/or signed into an account on GitLab. ## Open a shell @@ -29,11 +29,11 @@ After you are finished installing, open a new shell and type "git --version" aga ## Add your Git username and set your email -It is important to configure your Git username and email address as every Git commit that you create will use this information to identify who has submitted the commit. +It is important to configure your Git username and email address as every Git commit will use this information to identify you as the author. On your shell, type the following command to add your username: ``` -git config --global user.name "your_username_as_you_want_it_to_appear" +git config --global user.name "YOUR_USERNAME" ``` Then verify that you have the correct username: @@ -43,7 +43,7 @@ git config --global user.name To set your email address, type the following command: ``` -git config --global user.email "your_email_address@domain" +git config --global user.email "your_email_address@example.com" ``` To verify that you entered your email correctly, type: @@ -51,7 +51,7 @@ To verify that you entered your email correctly, type: git config --global user.email ``` -You'll need to do this only once as you are using the "--global" option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the "--global" option when you’re in that project. +You'll need to do this only once as you are using the `--global` option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the `--global` option when you’re in that project. ## Check your information -- cgit v1.2.1 From 4917bbd7ffc9e7e67d93a08650d95d02a0a67081 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Mon, 10 Oct 2016 17:03:34 +0200 Subject: Speed up specs for GET /projects/:id/events MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit From 8.15s to 4.55s by grouping expectations Signed-off-by: Rémy Coutable --- spec/requests/api/projects_spec.rb | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5f19638b460..85717d274aa 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -588,37 +588,39 @@ describe API::API, api: true do before do note = create(:note_on_issue, note: 'What an awesome day!', project: project) EventCreateService.new.leave_note(note, note.author) - get api("/projects/#{project.id}/events", user) end - it { expect(response).to have_http_status(200) } + it 'returns all events' do + get api("/projects/#{project.id}/events", user) - context 'joined event' do - let(:json_event) { json_response[1] } + expect(response).to have_http_status(200) - it { expect(json_event['action_name']).to eq('joined') } - it { expect(json_event['project_id'].to_i).to eq(project.id) } - it { expect(json_event['author_username']).to eq(user3.username) } - it { expect(json_event['author']['name']).to eq(user3.name) } - end + first_event = json_response.first - context 'comment event' do - let(:json_event) { json_response.first } + expect(first_event['action_name']).to eq('commented on') + expect(first_event['note']['body']).to eq('What an awesome day!') - it { expect(json_event['action_name']).to eq('commented on') } - it { expect(json_event['note']['body']).to eq('What an awesome day!') } + last_event = json_response.last + + expect(last_event['action_name']).to eq('joined') + expect(last_event['project_id'].to_i).to eq(project.id) + expect(last_event['author_username']).to eq(user3.username) + expect(last_event['author']['name']).to eq(user3.name) end end it 'returns a 404 error if not found' do get api('/projects/42/events', user) + expect(response).to have_http_status(404) expect(json_response['message']).to eq('404 Project Not Found') end it 'returns a 404 error if user is not a member' do other_user = create(:user) + get api("/projects/#{project.id}/events", other_user) + expect(response).to have_http_status(404) end end -- cgit v1.2.1 From 6606642f8f352267d9f645778a789b79d98a6ca8 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Mon, 10 Oct 2016 16:19:46 +0100 Subject: fixup! Added link to bulk assign issues to MR author. (Issue #18876) --- spec/controllers/projects/merge_requests_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 2a68e5a2c9b..84298f8bef4 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -742,7 +742,7 @@ describe Projects::MergeRequestsController do it 'calls MergeRequests::AssignIssuesService' do expect(MergeRequests::AssignIssuesService).to receive(:new). with(project, user, merge_request: merge_request). - and_return(double(execute: {count: 1})) + and_return(double(execute: { count: 1 })) post_assign_issues end -- cgit v1.2.1 From 7aa9675fbd7ca2e45403adb444fc03680f0e5d7a Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 10 Oct 2016 17:35:57 +0200 Subject: Add registry to skipped data in backup raketask docs [ci skip] --- doc/raketasks/backup_restore.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 91c2df7d464..26baffdf792 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -32,15 +32,17 @@ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` You can specify that portions of the application data be skipped using the -environment variable `SKIP`. You can skip: -- `db` +environment variable `SKIP`. You can skip: + +- `db` (database) - `uploads` (attachments) -- `repositories` +- `repositories` (Git repositories data) - `builds` (CI build output logs) - `artifacts` (CI build artifacts) - `lfs` (LFS objects) +- `registry` (Container Registry images) -Separate multiple data types to skip using a comma. For example: +Separate multiple data types to skip using a comma. For example: ``` sudo gitlab-rake gitlab:backup:create SKIP=db,uploads -- cgit v1.2.1 From cc8ad60fc42e9a115da63f0e0eca71c4b2567c35 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 12 Sep 2016 12:19:20 +0200 Subject: Update pipeline graph styles to match mockup --- app/assets/javascripts/pipeline.js.es6 | 9 ++++++++ app/assets/stylesheets/pages/pipelines.scss | 36 +++++++++++++++++------------ 2 files changed, 30 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index 8813bb5dfef..d3ed9757afe 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -1,4 +1,12 @@ (function() { + + function addMarginToBuild () { + const $secondChild = $('.build:nth-child(2)'); + if ($secondChild.length) { + $secondChild.closest('.stage-column').addClass('left-margin'); + } + } + function toggleGraph() { const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); @@ -21,4 +29,5 @@ } $(document).on('click', '.toggle-pipeline-btn', toggleGraph); + $(document).on('ready', addMarginToBuild); })(); diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index a2779704eff..e7e1a2a9b18 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -303,7 +303,13 @@ .stage-column { display: inline-block; vertical-align: top; - margin-right: 65px; + margin-right: 48px; + + &.left-margin { + &:not(:first-child) { + margin-left: 48px; + } + } li { list-style: none; @@ -321,9 +327,9 @@ .build { border: 1px solid $border-color; position: relative; - padding: 6px 10px; + padding: 8px 10px; border-radius: 30px; - width: 150px; + width: 186px; margin-bottom: 10px; &.playable { @@ -443,9 +449,9 @@ content: ''; position: absolute; top: 50%; - right: -69px; + right: -48px; border-top: 2px solid $border-color; - width: 69px; + width: 48px; height: 1px; } } @@ -457,22 +463,22 @@ top: -47px; position: absolute; border-bottom: 2px solid $border-color; - width: 20px; + width: 25px; height: 65px; } // Right connecting curves &::after { - right: -20px; + right: -25px; border-right: 2px solid $border-color; - border-radius: 0 0 15px; + border-radius: 0 0 20px; } // Left connecting curves &::before { - left: -20px; + left: -25px; border-left: 2px solid $border-color; - border-radius: 0 0 0 15px; + border-radius: 0 0 0 20px; } } @@ -538,20 +544,20 @@ width: 21px; height: 25px; position: absolute; - top: -29px; + top: -30px; border-top: 2px solid $border-color; } &::after { - left: -39px; + left: -44px; border-right: 2px solid $border-color; - border-radius: 0 15px; + border-radius: 0 20px; } &::before { - right: -39px; + right: -44px; border-left: 2px solid $border-color; - border-radius: 15px 0 0; + border-radius: 20px 0 0; } } } -- cgit v1.2.1 From ff0f70c0fe75773d060ecf5cf75d6fc5f879283b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Wed, 14 Sep 2016 13:51:36 +0200 Subject: Change length of connecting lines based on number of builds --- app/assets/javascripts/pipeline.js.es6 | 9 ++++++--- app/assets/stylesheets/pages/pipelines.scss | 24 +++++++++++++++++++++--- 2 files changed, 27 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index d3ed9757afe..f501761b1ec 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -1,9 +1,12 @@ (function() { function addMarginToBuild () { - const $secondChild = $('.build:nth-child(2)'); - if ($secondChild.length) { - $secondChild.closest('.stage-column').addClass('left-margin'); + const $secondChildBuildNode = $('.build:nth-child(2)'); + const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); + // const $previousBuildColumn = $secondChildBuildNode.closest('.stage-column').prev('.stage-column'); + if ($secondChildBuildNode.length) { + $secondChildBuildNode.closest('.stage-column').addClass('left-margin'); + $firstChildBuildNode.addClass('left-connector'); } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index e7e1a2a9b18..37df702ea13 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -303,11 +303,26 @@ .stage-column { display: inline-block; vertical-align: top; - margin-right: 48px; + + &:not(:last-child) { + margin-right: 44px; + } &.left-margin { &:not(:first-child) { - margin-left: 48px; + margin-left: 44px; + + .left-connector { + &::before { + content: ''; + position: absolute; + top: 50%; + left: -48px; + border-top: 2px solid $border-color; + width: 48px; + height: 1px; + } + } } } @@ -348,7 +363,10 @@ } .build-content { - width: 130px; + width: 164px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; .ci-status-text { width: 110px; -- cgit v1.2.1 From 65e482e7e9b7385e6d8ee72c102eca6e79ee97fa Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 16 Sep 2016 09:44:00 +0200 Subject: Change size of pipeline status icons and dropdowns --- app/assets/stylesheets/pages/pipelines.scss | 47 ++++++++++++++-------- .../projects/ci/builds/_build_pipeline.html.haml | 6 ++- .../commit/_pipeline_status_group.html.haml | 3 +- .../_generic_commit_status_pipeline.html.haml | 6 ++- 4 files changed, 41 insertions(+), 21 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 37df702ea13..547b9742dff 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -331,9 +331,9 @@ } .stage-name { - margin-bottom: 15px; + margin: 0 0 15px 10px; font-weight: bold; - width: 150px; + width: 176px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -364,12 +364,17 @@ .build-content { width: 164px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + + .ci-status-icon { + + svg { + height: 20px; + width: 20px; + } + } .ci-status-text { - width: 110px; + width: 135px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; @@ -397,27 +402,37 @@ color: $layout-link-gray; .ci-status-text { - width: 80px; + width: 112px; } } .grouped-pipeline-dropdown { padding: 8px 0; - width: 200px; + width: 168px; left: auto; - right: -214px; + right: -180px; top: -9px; max-height: 245px; overflow-y: scroll; - a:hover { - .ci-status-text { - text-decoration: none; + a { + padding: 7px 8px; + margin: 0 8px; + + &:hover { + .ci-status-text { + text-decoration: none; + } } } + svg { + width: 14px; + height: 14px; + } + .ci-status-text { - width: 145px; + width: 112px; } .arrow { @@ -482,7 +497,7 @@ position: absolute; border-bottom: 2px solid $border-color; width: 25px; - height: 65px; + height: 69px; } // Right connecting curves @@ -504,7 +519,7 @@ &:nth-child(2) { &::after, &::before { height: 29px; - top: -10px; + top: -7px; } .curve { display: block; @@ -562,7 +577,7 @@ width: 21px; height: 25px; position: absolute; - top: -30px; + top: -31.5px; border-top: 2px solid $border-color; } diff --git a/app/views/projects/ci/builds/_build_pipeline.html.haml b/app/views/projects/ci/builds/_build_pipeline.html.haml index 547bc0c9c19..017d3ff6af2 100644 --- a/app/views/projects/ci/builds/_build_pipeline.html.haml +++ b/app/views/projects/ci/builds/_build_pipeline.html.haml @@ -5,8 +5,10 @@ .ci-status-text= subject.name - elsif can?(current_user, :read_build, @project) = link_to namespace_project_build_path(subject.project.namespace, subject.project, subject) do - = render_status_with_link('build', subject.status) + %span.ci-status-icon + = render_status_with_link('build', subject.status) .ci-status-text= subject.name - else - = render_status_with_link('build', subject.status) + %span.ci-status-icon + = render_status_with_link('build', subject.status) = ci_icon_for_status(subject.status) diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index 4e7a6f1af08..2064242ab54 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,5 +1,6 @@ - group_status = CommitStatus.where(id: subject).status -= render_status_with_link('build', group_status) +%span.ci-status-icon + = render_status_with_link('build', group_status) .dropdown.inline %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } %span.ci-status-text diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml index 409f4701e4b..0a66d60accc 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status_pipeline.html.haml @@ -1,7 +1,9 @@ - if subject.target_url = link_to subject.target_url do - = render_status_with_link('commit status', subject.status) + %span.ci-status-icon + = render_status_with_link('commit status', subject.status) %span.ci-status-text= subject.name - else - = render_status_with_link('commit status', subject.status) + %span.ci-status-icon + = render_status_with_link('commit status', subject.status) %span.ci-status-text= subject.name -- cgit v1.2.1 From eb55ac7d4d13a2c404d24c68cef10675ce29cf3c Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Wed, 21 Sep 2016 01:45:39 +0100 Subject: Added final changes from handover --- app/assets/stylesheets/framework/variables.scss | 5 +++ app/assets/stylesheets/pages/pipelines.scss | 38 +++++++++++++--------- .../projects/commit/_pipeline_stage.html.haml | 2 +- .../commit/_pipeline_status_group.html.haml | 17 +++++----- 4 files changed, 36 insertions(+), 26 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 14ec310de2d..4c34ed3ebf7 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -17,8 +17,10 @@ $white-normal: #ededed; $white-dark: #ececec; $gray-light: #fafafa; +$gray-lighter: #f9f9f9; $gray-normal: #f5f5f5; $gray-dark: #ededed; +$gray-darker: #eee; $gray-darkest: #c9c9c9; $green-light: #38ae67; @@ -33,6 +35,8 @@ $blue-medium-light: #3498cb; $blue-medium: #2f8ebf; $blue-medium-dark: #2d86b4; +$blue-light-transparent: rgba(44, 159, 216, 0.05); + $orange-light: #fc8a51; $orange-normal: #e75e40; $orange-dark: #ce5237; @@ -91,6 +95,7 @@ $table-text-gray: #8f8f8f; $gl-font-size: 15px; $gl-title-color: #333; $gl-text-color: #5c5c5c; +$gl-text-color-light: #8c8c8c; $gl-text-green: #4a2; $gl-text-red: #d12f19; $gl-text-orange: #d90; diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 547b9742dff..36c57f3ca30 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -342,11 +342,18 @@ .build { border: 1px solid $border-color; position: relative; - padding: 8px 10px; + padding: 7px 10px 8px; border-radius: 30px; width: 186px; margin-bottom: 10px; + &:hover { + background-color: $gray-lighter; + .dropdown-menu-toggle { + background-color: transparent; + } + } + &.playable { background-color: $gray-light; @@ -366,7 +373,6 @@ width: 164px; .ci-status-icon { - svg { height: 20px; width: 20px; @@ -385,41 +391,40 @@ } a { - color: $layout-link-gray; + color: $gl-text-color-light; text-decoration: none; - - &:hover { - .ci-status-text { - text-decoration: underline; - } - } } .dropdown-menu-toggle { border: none; width: auto; padding: 0; - color: $layout-link-gray; + color: $gl-text-color-light; .ci-status-text { - width: 112px; + max-width: 112px; + width: auto; } } .grouped-pipeline-dropdown { padding: 8px 0; - width: 168px; + width: 186px; left: auto; - right: -180px; + right: -197px; top: -9px; max-height: 245px; overflow-y: scroll; a { - padding: 7px 8px; + color: $gl-text-color; + padding: 7px 8px 8px; margin: 0 8px; &:hover { + background-color: $blue-light-transparent; + border-radius: 3px; + .ci-status-text { text-decoration: none; } @@ -465,9 +470,10 @@ } .badge { - background-color: $gray-dark; - color: $layout-link-gray; + background-color: $gray-darker; + color: $gl-text-color-light; font-weight: normal; + margin-left: $btn-xs-side-margin; } } diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml index 23c5c51fbc2..68d42126bf6 100644 --- a/app/views/projects/commit/_pipeline_stage.html.haml +++ b/app/views/projects/commit/_pipeline_stage.html.haml @@ -10,5 +10,5 @@ - else %li.build .curve - .build-content + .dropdown.inline.build-content{ type: 'button', data: { toggle: 'dropdown' } } = render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index 2064242ab54..bff65bff653 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,12 +1,11 @@ - group_status = CommitStatus.where(id: subject).status %span.ci-status-icon = render_status_with_link('build', group_status) -.dropdown.inline - %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } - %span.ci-status-text - = name - %span.badge= subject.size - %ul.dropdown-menu.grouped-pipeline-dropdown - .arrow - - subject.each do |status| - = render "projects/#{status.to_partial_path}_pipeline", subject: status +%button.dropdown-menu-toggle + %span.ci-status-text + = name + %span.badge= subject.size +%ul.dropdown-menu.grouped-pipeline-dropdown + .arrow + - subject.each do |status| + = render "projects/#{status.to_partial_path}_pipeline", subject: status -- cgit v1.2.1 From 5defad2d21b6481c07fb4a77f0a56ed7c19ff899 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 29 Sep 2016 22:24:37 +0100 Subject: Finished up margin JS logic --- app/assets/javascripts/dispatcher.js | 3 + app/assets/javascripts/pipeline.js.es6 | 60 ++++++++++---------- app/assets/stylesheets/pages/pipelines.scss | 4 ++ app/views/projects/commit/_pipeline.html.haml | 79 ++++++++++++++------------- 4 files changed, 79 insertions(+), 67 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 8d99b12102d..45494afe7aa 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -126,6 +126,9 @@ new TreeView(); } break; + case 'projects:pipelines:show': + new window.gl.Pipelines(); + break; case 'groups:activity': new Activities(); break; diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index f501761b1ec..6299ba2269d 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -1,36 +1,40 @@ -(function() { - - function addMarginToBuild () { - const $secondChildBuildNode = $('.build:nth-child(2)'); - const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); - // const $previousBuildColumn = $secondChildBuildNode.closest('.stage-column').prev('.stage-column'); - if ($secondChildBuildNode.length) { - $secondChildBuildNode.closest('.stage-column').addClass('left-margin'); - $firstChildBuildNode.addClass('left-connector'); +((global) => { + + class Pipelines { + constructor() { + $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); + $(document).off('ready.addMarginToBuildColumns').on('ready.addMarginToBuildColumns', this.addMarginToBuildColumns); } - } - function toggleGraph() { - const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); - const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); - const $btnText = $(this).find('.toggle-btn-text'); - const $icon = $(this).find('.fa'); + toggleGraph() { + const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); + const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); + const $btnText = $(this).find('.toggle-btn-text'); - $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); + $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); - const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); - const expandIcon = 'fa-caret-down'; - const hideIcon = 'fa-caret-up'; + const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); - if(graphCollapsed) { - $btnText.text('Expand'); - $icon.removeClass(hideIcon).addClass(expandIcon); - } else { - $btnText.text('Hide'); - $icon.removeClass(expandIcon).addClass(hideIcon); + graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') + } + + addMarginToBuildColumns() { + const $secondChildBuildNode = $('.build:nth-child(2)'); + if ($secondChildBuildNode.length) { + const $firstChildBuildNode = $secondChildBuildNode.prev('.build'); + const $multiBuildColumn = $secondChildBuildNode.closest('.stage-column'); + const $previousColumn = $multiBuildColumn.prev('.stage-column'); + $multiBuildColumn.addClass('left-margin'); + $firstChildBuildNode.addClass('left-connector'); + $previousColumn.each(function() { + $this = $(this); + if ($('.build', $this).length === 1) $this.addClass('no-margin'); + }); + } + $('.pipeline-graph-container').removeClass('hidden'); } } - $(document).on('click', '.toggle-pipeline-btn', toggleGraph); - $(document).on('ready', addMarginToBuild); -})(); + global.Pipelines = Pipelines; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 36c57f3ca30..f4211ea3b2d 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -326,6 +326,10 @@ } } + &.no-margin { + margin: 0; + } + li { list-style: none; } diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index da5b9832ba5..bdf3c0a2aba 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,45 +1,46 @@ -.row-content-block.build-content.middle-block.pipeline-actions - .pull-right - .btn.btn-grouped.btn-white.toggle-pipeline-btn - %span.toggle-btn-text Hide - %span pipeline graph - = icon('caret-up') - - if can?(current_user, :update_pipeline, pipeline.project) - - if pipeline.builds.latest.failed.any?(&:retryable?) - = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post +.pipeline-graph-container.hidden + .row-content-block.build-content.middle-block.pipeline-actions + .pull-right + .btn.btn-grouped.btn-white.toggle-pipeline-btn + %span.toggle-btn-text Hide + %span pipeline graph + %span.caret + - if can?(current_user, :update_pipeline, pipeline.project) + - if pipeline.builds.latest.failed.any?(&:retryable?) + = link_to "Retry failed", retry_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: 'btn btn-grouped btn-primary', method: :post - - if pipeline.builds.running_or_pending.any? - = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post + - if pipeline.builds.running_or_pending.any? + = link_to "Cancel running", cancel_namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), data: { confirm: 'Are you sure?' }, class: 'btn btn-grouped btn-danger', method: :post - .oneline.clearfix - - if defined?(pipeline_details) && pipeline_details - Pipeline - = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace" - with - = pluralize pipeline.statuses.count(:id), "build" - - if pipeline.ref - for - = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace" - - if defined?(link_to_commit) && link_to_commit - for commit - = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "monospace" - - if pipeline.duration - in - = time_interval_in_words pipeline.duration + .oneline.clearfix + - if defined?(pipeline_details) && pipeline_details + Pipeline + = link_to "##{pipeline.id}", namespace_project_pipeline_path(pipeline.project.namespace, pipeline.project, pipeline.id), class: "monospace" + with + = pluralize pipeline.statuses.count(:id), "build" + - if pipeline.ref + for + = link_to pipeline.ref, namespace_project_commits_path(pipeline.project.namespace, pipeline.project, pipeline.ref), class: "monospace" + - if defined?(link_to_commit) && link_to_commit + for commit + = link_to pipeline.short_sha, namespace_project_commit_path(pipeline.project.namespace, pipeline.project, pipeline.sha), class: "monospace" + - if pipeline.duration + in + = time_interval_in_words pipeline.duration -.row-content-block.build-content.middle-block.pipeline-graph - .pipeline-visualization - %ul.stage-column-list - - stages = pipeline.stages_with_latest_statuses - - stages.each do |stage, statuses| - %li.stage-column - .stage-name - %a{name: stage} - - if stage - = stage.titleize - .builds-container - %ul - = render "projects/commit/pipeline_stage", statuses: statuses + .row-content-block.build-content.middle-block.pipeline-graph + .pipeline-visualization + %ul.stage-column-list + - stages = pipeline.stages_with_latest_statuses + - stages.each do |stage, statuses| + %li.stage-column + .stage-name + %a{name: stage} + - if stage + = stage.titleize + .builds-container + %ul + = render "projects/commit/pipeline_stage", statuses: statuses - if pipeline.yaml_errors.present? -- cgit v1.2.1 From c2deaa7e0db808abad86e87bac9524784b02e602 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 4 Oct 2016 13:59:57 -0500 Subject: Move hidden class to graph itself; remove background color from play node & align icon --- app/assets/javascripts/pipeline.js.es6 | 2 +- app/assets/stylesheets/pages/pipelines.scss | 5 ++--- app/views/projects/commit/_pipeline.html.haml | 4 ++-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index 6299ba2269d..68a34dda2af 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -31,7 +31,7 @@ if ($('.build', $this).length === 1) $this.addClass('no-margin'); }); } - $('.pipeline-graph-container').removeClass('hidden'); + $('.pipeline-graph').removeClass('hidden'); } } diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index f4211ea3b2d..4e53c9765df 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -359,11 +359,10 @@ } &.playable { - background-color: $gray-light; svg { - height: 12px; - width: 12px; + height: 13px; + width: 20px; position: relative; top: 1px; diff --git a/app/views/projects/commit/_pipeline.html.haml b/app/views/projects/commit/_pipeline.html.haml index bdf3c0a2aba..288c06d9b67 100644 --- a/app/views/projects/commit/_pipeline.html.haml +++ b/app/views/projects/commit/_pipeline.html.haml @@ -1,4 +1,4 @@ -.pipeline-graph-container.hidden +.pipeline-graph-container .row-content-block.build-content.middle-block.pipeline-actions .pull-right .btn.btn-grouped.btn-white.toggle-pipeline-btn @@ -28,7 +28,7 @@ in = time_interval_in_words pipeline.duration - .row-content-block.build-content.middle-block.pipeline-graph + .row-content-block.build-content.middle-block.pipeline-graph.hidden .pipeline-visualization %ul.stage-column-list - stages = pipeline.stages_with_latest_statuses -- cgit v1.2.1 From 113050c5709a204f58e0395b08e1582b3c862d66 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 6 Oct 2016 13:35:13 -0500 Subject: Fix ul html --- app/assets/stylesheets/pages/pipelines.scss | 1 - app/views/projects/commit/_pipeline_status_group.html.haml | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 4e53c9765df..9ce5bee466f 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -422,7 +422,6 @@ a { color: $gl-text-color; padding: 7px 8px 8px; - margin: 0 8px; &:hover { background-color: $blue-light-transparent; diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index bff65bff653..b26de822450 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -6,6 +6,7 @@ = name %span.badge= subject.size %ul.dropdown-menu.grouped-pipeline-dropdown - .arrow + %li.arrow - subject.each do |status| - = render "projects/#{status.to_partial_path}_pipeline", subject: status + %li + = render "projects/#{status.to_partial_path}_pipeline", subject: status -- cgit v1.2.1 From 6a7b673035752890023e630755f27ec0d412a129 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 6 Oct 2016 18:00:06 +0100 Subject: JS review changes - Removed window from window.gl in dispatcher Added page:load event as ready isnt fired by turbolinks Fix dropdown menu link click registration --- app/assets/javascripts/dispatcher.js | 2 +- app/assets/javascripts/pipeline.js.es6 | 1 + app/assets/stylesheets/pages/pipelines.scss | 4 ++++ app/views/projects/commit/_pipeline_stage.html.haml | 2 +- app/views/projects/commit/_pipeline_status_group.html.haml | 2 +- 5 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index 45494afe7aa..adff73af79c 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -127,7 +127,7 @@ } break; case 'projects:pipelines:show': - new window.gl.Pipelines(); + new gl.Pipelines(); break; case 'groups:activity': new Activities(); diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index 68a34dda2af..1030447f74a 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -4,6 +4,7 @@ constructor() { $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); $(document).off('ready.addMarginToBuildColumns').on('ready.addMarginToBuildColumns', this.addMarginToBuildColumns); + $(document).off('page:load.addMarginToBuildColumns').on('page:load.addMarginToBuildColumns', this.addMarginToBuildColumns); } toggleGraph() { diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 9ce5bee466f..0c3c1e404af 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -373,6 +373,9 @@ } .build-content { + display: -ms-flexbox; + display: -webkit-flex; + display: flex; width: 164px; .ci-status-icon { @@ -403,6 +406,7 @@ width: auto; padding: 0; color: $gl-text-color-light; + flex-grow: 1; .ci-status-text { max-width: 112px; diff --git a/app/views/projects/commit/_pipeline_stage.html.haml b/app/views/projects/commit/_pipeline_stage.html.haml index 68d42126bf6..289aa5178b1 100644 --- a/app/views/projects/commit/_pipeline_stage.html.haml +++ b/app/views/projects/commit/_pipeline_stage.html.haml @@ -10,5 +10,5 @@ - else %li.build .curve - .dropdown.inline.build-content{ type: 'button', data: { toggle: 'dropdown' } } + .dropdown.inline.build-content = render "projects/commit/pipeline_status_group", name: group_name, subject: grouped_statuses diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index b26de822450..8582e9a8772 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,7 +1,7 @@ - group_status = CommitStatus.where(id: subject).status %span.ci-status-icon = render_status_with_link('build', group_status) -%button.dropdown-menu-toggle +%button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } %span.ci-status-text = name %span.badge= subject.size -- cgit v1.2.1 From 4e1efca14119a25c441d833268894a632eb269d5 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 6 Oct 2016 14:33:55 -0500 Subject: Fix node flex alignment --- app/views/projects/commit/_pipeline_status_group.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index 8582e9a8772..6ada719e006 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -1,7 +1,7 @@ - group_status = CommitStatus.where(id: subject).status -%span.ci-status-icon - = render_status_with_link('build', group_status) %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown' } } + %span.ci-status-icon + = render_status_with_link('build', group_status) %span.ci-status-text = name %span.badge= subject.size -- cgit v1.2.1 From a57b3fc80c50b951d3a54ce2108608d45826c5ef Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 6 Oct 2016 21:20:25 +0100 Subject: Corrected my correction for turbolinks -.-' Removed extra cell on generic pipeline --- app/assets/javascripts/pipeline.js.es6 | 3 +-- .../projects/generic_commit_statuses/_generic_commit_status.html.haml | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index 1030447f74a..a29f6ecdd59 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -3,8 +3,7 @@ class Pipelines { constructor() { $(document).off('click', '.toggle-pipeline-btn').on('click', '.toggle-pipeline-btn', this.toggleGraph); - $(document).off('ready.addMarginToBuildColumns').on('ready.addMarginToBuildColumns', this.addMarginToBuildColumns); - $(document).off('page:load.addMarginToBuildColumns').on('page:load.addMarginToBuildColumns', this.addMarginToBuildColumns); + this.addMarginToBuildColumns(); } toggleGraph() { diff --git a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml index 331dc1fcc29..80fe6be49b0 100644 --- a/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml +++ b/app/views/projects/generic_commit_statuses/_generic_commit_status.html.haml @@ -62,5 +62,3 @@ %td.coverage - if generic_commit_status.try(:coverage) #{generic_commit_status.coverage}% - - %td -- cgit v1.2.1 From 274ac4efdb94efe4d3ea4d5fff193eb2f02db507 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 7 Oct 2016 12:23:00 +0100 Subject: JS review changes and fixed conflicts --- app/assets/javascripts/pipeline.js.es6 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/pipeline.js.es6 b/app/assets/javascripts/pipeline.js.es6 index a29f6ecdd59..6bf63ee6979 100644 --- a/app/assets/javascripts/pipeline.js.es6 +++ b/app/assets/javascripts/pipeline.js.es6 @@ -10,10 +10,10 @@ const $pipelineBtn = $(this).closest('.toggle-pipeline-btn'); const $pipelineGraph = $(this).closest('.row-content-block').next('.pipeline-graph'); const $btnText = $(this).find('.toggle-btn-text'); + const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); $($pipelineBtn).add($pipelineGraph).toggleClass('graph-collapsed'); - const graphCollapsed = $pipelineGraph.hasClass('graph-collapsed'); graphCollapsed ? $btnText.text('Expand') : $btnText.text('Hide') } -- cgit v1.2.1 From 879a68a7c48c469c2646ecd89412cab9679d8860 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Fri, 7 Oct 2016 14:57:57 +0200 Subject: slight update to lines and curves positioning --- app/assets/stylesheets/pages/pipelines.scss | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 0c3c1e404af..04cce24ce7d 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -321,6 +321,7 @@ border-top: 2px solid $border-color; width: 48px; height: 1px; + margin-top: -1px; } } } @@ -498,6 +499,7 @@ border-top: 2px solid $border-color; width: 48px; height: 1px; + margin-top: -1px; } } @@ -505,7 +507,7 @@ &:not(:first-child) { &::after, &::before { content: ''; - top: -47px; + top: -49px; position: absolute; border-bottom: 2px solid $border-color; width: 25px; @@ -531,7 +533,7 @@ &:nth-child(2) { &::after, &::before { height: 29px; - top: -7px; + top: -9px; } .curve { display: block; @@ -589,7 +591,7 @@ width: 21px; height: 25px; position: absolute; - top: -31.5px; + top: -32.5px; border-top: 2px solid $border-color; } -- cgit v1.2.1 From 7a28205629962427bfe5a48610ee4890b37f5ae8 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 7 Oct 2016 08:57:30 -0500 Subject: Remove negative margins --- app/assets/stylesheets/pages/pipelines.scss | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 04cce24ce7d..21224447628 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -316,12 +316,11 @@ &::before { content: ''; position: absolute; - top: 50%; + top: 49%; left: -48px; border-top: 2px solid $border-color; width: 48px; height: 1px; - margin-top: -1px; } } } @@ -494,12 +493,11 @@ &::after { content: ''; position: absolute; - top: 50%; + top: 49%; right: -48px; border-top: 2px solid $border-color; width: 48px; height: 1px; - margin-top: -1px; } } @@ -591,7 +589,7 @@ width: 21px; height: 25px; position: absolute; - top: -32.5px; + top: -31px; border-top: 2px solid $border-color; } -- cgit v1.2.1 From 0f7cc21e2479762975c4416148105e876c85d8cd Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 10 Oct 2016 17:09:48 +0100 Subject: Formatted all app/assets/javascripts to underscore naming convention --- app/assets/javascripts/LabelManager.js.es6 | 106 ------ app/assets/javascripts/commit/image-file.js | 177 --------- app/assets/javascripts/commit/image_file.js | 177 +++++++++ app/assets/javascripts/cycle-analytics.js.es6 | 93 ----- app/assets/javascripts/cycle_analytics.js.es6 | 93 +++++ .../javascripts/issues-bulk-assignment.js.es6 | 149 -------- .../javascripts/issues_bulk_assignment.js.es6 | 149 ++++++++ app/assets/javascripts/label_manager.js.es6 | 106 ++++++ app/assets/javascripts/network/branch-graph.js | 417 --------------------- app/assets/javascripts/network/branch_graph.js | 417 +++++++++++++++++++++ 10 files changed, 942 insertions(+), 942 deletions(-) delete mode 100644 app/assets/javascripts/LabelManager.js.es6 delete mode 100644 app/assets/javascripts/commit/image-file.js create mode 100644 app/assets/javascripts/commit/image_file.js delete mode 100644 app/assets/javascripts/cycle-analytics.js.es6 create mode 100644 app/assets/javascripts/cycle_analytics.js.es6 delete mode 100644 app/assets/javascripts/issues-bulk-assignment.js.es6 create mode 100644 app/assets/javascripts/issues_bulk_assignment.js.es6 create mode 100644 app/assets/javascripts/label_manager.js.es6 delete mode 100644 app/assets/javascripts/network/branch-graph.js create mode 100644 app/assets/javascripts/network/branch_graph.js diff --git a/app/assets/javascripts/LabelManager.js.es6 b/app/assets/javascripts/LabelManager.js.es6 deleted file mode 100644 index bc68e53504f..00000000000 --- a/app/assets/javascripts/LabelManager.js.es6 +++ /dev/null @@ -1,106 +0,0 @@ -((global) => { - - class LabelManager { - constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) { - this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority'); - this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels'); - this.otherLabels = otherLabels || $('.js-other-labels'); - this.errorMessage = 'Unable to update label prioritization at this time'; - this.prioritizedLabels.sortable({ - items: 'li', - placeholder: 'list-placeholder', - axis: 'y', - update: this.onPrioritySortUpdate.bind(this) - }); - this.bindEvents(); - } - - bindEvents() { - return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); - } - - onTogglePriorityClick(e) { - e.preventDefault(); - const _this = e.data; - const $btn = $(e.currentTarget); - const $label = $(`#${$btn.data('domId')}`); - const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; - const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`); - $tooltip.tooltip('destroy'); - return _this.toggleLabelPriority($label, action); - } - - toggleLabelPriority($label, action, persistState) { - if (persistState == null) { - persistState = true; - } - let xhr; - const _this = this; - const url = $label.find('.js-toggle-priority').data('url'); - let $target = this.prioritizedLabels; - let $from = this.otherLabels; - if (action === 'remove') { - $target = this.otherLabels; - $from = this.prioritizedLabels; - } - if ($from.find('li').length === 1) { - $from.find('.empty-message').removeClass('hidden'); - } - if (!$target.find('li').length) { - $target.find('.empty-message').addClass('hidden'); - } - $label.detach().appendTo($target); - // Return if we are not persisting state - if (!persistState) { - return; - } - if (action === 'remove') { - xhr = $.ajax({ - url, - type: 'DELETE' - }); - // Restore empty message - if (!$from.find('li').length) { - $from.find('.empty-message').removeClass('hidden'); - } - } else { - xhr = this.savePrioritySort($label, action); - } - return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action)); - } - - onPrioritySortUpdate() { - const xhr = this.savePrioritySort(); - return xhr.fail(function() { - return new Flash(this.errorMessage, 'alert'); - }); - } - - savePrioritySort() { - return $.post({ - url: this.prioritizedLabels.data('url'), - data: { - label_ids: this.getSortedLabelsIds() - } - }); - } - - rollbackLabelPosition($label, originalAction) { - const action = originalAction === 'remove' ? 'add' : 'remove'; - this.toggleLabelPriority($label, action, false); - return new Flash(this.errorMessage, 'alert'); - } - - getSortedLabelsIds() { - const sortedIds = []; - this.prioritizedLabels.find('li').each(function() { - sortedIds.push($(this).data('id')); - }); - return sortedIds; - } - } - - gl.LabelManager = LabelManager; - -})(window.gl || (window.gl = {})); - diff --git a/app/assets/javascripts/commit/image-file.js b/app/assets/javascripts/commit/image-file.js deleted file mode 100644 index e893491b19b..00000000000 --- a/app/assets/javascripts/commit/image-file.js +++ /dev/null @@ -1,177 +0,0 @@ -(function() { - this.ImageFile = (function() { - var prepareFrames; - - // Width where images must fits in, for 2-up this gets divided by 2 - ImageFile.availWidth = 900; - - ImageFile.viewModes = ['two-up', 'swipe']; - - function ImageFile(file) { - this.file = file; - this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) { - // Determine if old and new file has same dimensions, if not show 'two-up' view - return function(deletedWidth, deletedHeight) { - return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) { - if (width === deletedWidth && height === deletedHeight) { - return _this.initViewModes(); - } else { - return _this.initView('two-up'); - } - }); - }; - })(this)); - } - - ImageFile.prototype.initViewModes = function() { - var viewMode; - viewMode = ImageFile.viewModes[0]; - $('.view-modes', this.file).removeClass('hide'); - $('.view-modes-menu', this.file).on('click', 'li', (function(_this) { - return function(event) { - if (!$(event.currentTarget).hasClass('active')) { - return _this.activateViewMode(event.currentTarget.className); - } - }; - })(this)); - return this.activateViewMode(viewMode); - }; - - ImageFile.prototype.activateViewMode = function(viewMode) { - $('.view-modes-menu li', this.file).removeClass('active').filter("." + viewMode).addClass('active'); - return $(".view:visible:not(." + viewMode + ")", this.file).fadeOut(200, (function(_this) { - return function() { - $(".view." + viewMode, _this.file).fadeIn(200); - return _this.initView(viewMode); - }; - })(this)); - }; - - ImageFile.prototype.initView = function(viewMode) { - return this.views[viewMode].call(this); - }; - - prepareFrames = function(view) { - var maxHeight, maxWidth; - maxWidth = 0; - maxHeight = 0; - $('.frame', view).each((function(_this) { - return function(index, frame) { - var height, width; - width = $(frame).width(); - height = $(frame).height(); - maxWidth = width > maxWidth ? width : maxWidth; - return maxHeight = height > maxHeight ? height : maxHeight; - }; - })(this)).css({ - width: maxWidth, - height: maxHeight - }); - return [maxWidth, maxHeight]; - }; - - ImageFile.prototype.views = { - 'two-up': function() { - return $('.two-up.view .wrap', this.file).each((function(_this) { - return function(index, wrap) { - $('img', wrap).each(function() { - var currentWidth; - currentWidth = $(this).width(); - if (currentWidth > ImageFile.availWidth / 2) { - return $(this).width(ImageFile.availWidth / 2); - } - }); - return _this.requestImageInfo($('img', wrap), function(width, height) { - $('.image-info .meta-width', wrap).text(width + "px"); - $('.image-info .meta-height', wrap).text(height + "px"); - return $('.image-info', wrap).removeClass('hide'); - }); - }; - })(this)); - }, - 'swipe': function() { - var maxHeight, maxWidth; - maxWidth = 0; - maxHeight = 0; - return $('.swipe.view', this.file).each((function(_this) { - return function(index, view) { - var ref; - ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; - $('.swipe-frame', view).css({ - width: maxWidth + 16, - height: maxHeight + 28 - }); - $('.swipe-wrap', view).css({ - width: maxWidth + 1, - height: maxHeight + 2 - }); - return $('.swipe-bar', view).css({ - left: 0 - }).draggable({ - axis: 'x', - containment: 'parent', - drag: function(event) { - return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left); - }, - stop: function(event) { - return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left); - } - }); - }; - })(this)); - }, - 'onion-skin': function() { - var dragTrackWidth, maxHeight, maxWidth; - maxWidth = 0; - maxHeight = 0; - dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width(); - return $('.onion-skin.view', this.file).each((function(_this) { - return function(index, view) { - var ref; - ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; - $('.onion-skin-frame', view).css({ - width: maxWidth + 16, - height: maxHeight + 28 - }); - $('.swipe-wrap', view).css({ - width: maxWidth + 1, - height: maxHeight + 2 - }); - return $('.dragger', view).css({ - left: dragTrackWidth - }).draggable({ - axis: 'x', - containment: 'parent', - drag: function(event) { - return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth); - }, - stop: function(event) { - return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth); - } - }); - }; - })(this)); - } - }; - - ImageFile.prototype.requestImageInfo = function(img, callback) { - var domImg; - domImg = img.get(0); - if (domImg) { - if (domImg.complete) { - return callback.call(this, domImg.naturalWidth, domImg.naturalHeight); - } else { - return img.on('load', (function(_this) { - return function() { - return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight); - }; - })(this)); - } - } - }; - - return ImageFile; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/commit/image_file.js b/app/assets/javascripts/commit/image_file.js new file mode 100644 index 00000000000..e893491b19b --- /dev/null +++ b/app/assets/javascripts/commit/image_file.js @@ -0,0 +1,177 @@ +(function() { + this.ImageFile = (function() { + var prepareFrames; + + // Width where images must fits in, for 2-up this gets divided by 2 + ImageFile.availWidth = 900; + + ImageFile.viewModes = ['two-up', 'swipe']; + + function ImageFile(file) { + this.file = file; + this.requestImageInfo($('.two-up.view .frame.deleted img', this.file), (function(_this) { + // Determine if old and new file has same dimensions, if not show 'two-up' view + return function(deletedWidth, deletedHeight) { + return _this.requestImageInfo($('.two-up.view .frame.added img', _this.file), function(width, height) { + if (width === deletedWidth && height === deletedHeight) { + return _this.initViewModes(); + } else { + return _this.initView('two-up'); + } + }); + }; + })(this)); + } + + ImageFile.prototype.initViewModes = function() { + var viewMode; + viewMode = ImageFile.viewModes[0]; + $('.view-modes', this.file).removeClass('hide'); + $('.view-modes-menu', this.file).on('click', 'li', (function(_this) { + return function(event) { + if (!$(event.currentTarget).hasClass('active')) { + return _this.activateViewMode(event.currentTarget.className); + } + }; + })(this)); + return this.activateViewMode(viewMode); + }; + + ImageFile.prototype.activateViewMode = function(viewMode) { + $('.view-modes-menu li', this.file).removeClass('active').filter("." + viewMode).addClass('active'); + return $(".view:visible:not(." + viewMode + ")", this.file).fadeOut(200, (function(_this) { + return function() { + $(".view." + viewMode, _this.file).fadeIn(200); + return _this.initView(viewMode); + }; + })(this)); + }; + + ImageFile.prototype.initView = function(viewMode) { + return this.views[viewMode].call(this); + }; + + prepareFrames = function(view) { + var maxHeight, maxWidth; + maxWidth = 0; + maxHeight = 0; + $('.frame', view).each((function(_this) { + return function(index, frame) { + var height, width; + width = $(frame).width(); + height = $(frame).height(); + maxWidth = width > maxWidth ? width : maxWidth; + return maxHeight = height > maxHeight ? height : maxHeight; + }; + })(this)).css({ + width: maxWidth, + height: maxHeight + }); + return [maxWidth, maxHeight]; + }; + + ImageFile.prototype.views = { + 'two-up': function() { + return $('.two-up.view .wrap', this.file).each((function(_this) { + return function(index, wrap) { + $('img', wrap).each(function() { + var currentWidth; + currentWidth = $(this).width(); + if (currentWidth > ImageFile.availWidth / 2) { + return $(this).width(ImageFile.availWidth / 2); + } + }); + return _this.requestImageInfo($('img', wrap), function(width, height) { + $('.image-info .meta-width', wrap).text(width + "px"); + $('.image-info .meta-height', wrap).text(height + "px"); + return $('.image-info', wrap).removeClass('hide'); + }); + }; + })(this)); + }, + 'swipe': function() { + var maxHeight, maxWidth; + maxWidth = 0; + maxHeight = 0; + return $('.swipe.view', this.file).each((function(_this) { + return function(index, view) { + var ref; + ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; + $('.swipe-frame', view).css({ + width: maxWidth + 16, + height: maxHeight + 28 + }); + $('.swipe-wrap', view).css({ + width: maxWidth + 1, + height: maxHeight + 2 + }); + return $('.swipe-bar', view).css({ + left: 0 + }).draggable({ + axis: 'x', + containment: 'parent', + drag: function(event) { + return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left); + }, + stop: function(event) { + return $('.swipe-wrap', view).width((maxWidth + 1) - $(this).position().left); + } + }); + }; + })(this)); + }, + 'onion-skin': function() { + var dragTrackWidth, maxHeight, maxWidth; + maxWidth = 0; + maxHeight = 0; + dragTrackWidth = $('.drag-track', this.file).width() - $('.dragger', this.file).width(); + return $('.onion-skin.view', this.file).each((function(_this) { + return function(index, view) { + var ref; + ref = prepareFrames(view), maxWidth = ref[0], maxHeight = ref[1]; + $('.onion-skin-frame', view).css({ + width: maxWidth + 16, + height: maxHeight + 28 + }); + $('.swipe-wrap', view).css({ + width: maxWidth + 1, + height: maxHeight + 2 + }); + return $('.dragger', view).css({ + left: dragTrackWidth + }).draggable({ + axis: 'x', + containment: 'parent', + drag: function(event) { + return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth); + }, + stop: function(event) { + return $('.frame.added', view).css('opacity', $(this).position().left / dragTrackWidth); + } + }); + }; + })(this)); + } + }; + + ImageFile.prototype.requestImageInfo = function(img, callback) { + var domImg; + domImg = img.get(0); + if (domImg) { + if (domImg.complete) { + return callback.call(this, domImg.naturalWidth, domImg.naturalHeight); + } else { + return img.on('load', (function(_this) { + return function() { + return callback.call(_this, domImg.naturalWidth, domImg.naturalHeight); + }; + })(this)); + } + } + }; + + return ImageFile; + + })(); + +}).call(this); diff --git a/app/assets/javascripts/cycle-analytics.js.es6 b/app/assets/javascripts/cycle-analytics.js.es6 deleted file mode 100644 index cd9886ba58d..00000000000 --- a/app/assets/javascripts/cycle-analytics.js.es6 +++ /dev/null @@ -1,93 +0,0 @@ -((global) => { - - const COOKIE_NAME = 'cycle_analytics_help_dismissed'; - const store = gl.cycleAnalyticsStore = { - isLoading: true, - hasError: false, - isHelpDismissed: $.cookie(COOKIE_NAME), - analytics: {} - }; - - gl.CycleAnalytics = class CycleAnalytics { - constructor() { - const that = this; - - this.vue = new Vue({ - el: '#cycle-analytics', - name: 'CycleAnalytics', - created: this.fetchData(), - data: store, - methods: { - dismissLanding() { - that.dismissLanding(); - } - } - }); - } - - fetchData(options) { - store.isLoading = true; - options = options || { startDate: 30 }; - - $.ajax({ - url: $('#cycle-analytics').data('request-path'), - method: 'GET', - dataType: 'json', - contentType: 'application/json', - data: { start_date: options.startDate } - }).done((data) => { - this.decorateData(data); - this.initDropdown(); - }) - .error((data) => { - this.handleError(data); - }) - .always(() => { - store.isLoading = false; - }) - } - - decorateData(data) { - data.summary = data.summary || []; - data.stats = data.stats || []; - - data.summary.forEach((item) => { - item.value = item.value || '-'; - }); - - data.stats.forEach((item) => { - item.value = item.value || '- - -'; - }); - - store.analytics = data; - } - - handleError(data) { - store.hasError = true; - new Flash('There was an error while fetching cycle analytics data.', 'alert'); - } - - dismissLanding() { - store.isHelpDismissed = true; - $.cookie(COOKIE_NAME, true, { - path: gon.relative_url_root || '/' - }); - } - - initDropdown() { - const $dropdown = $('.js-ca-dropdown'); - const $label = $dropdown.find('.dropdown-label'); - - $dropdown.find('li a').off('click').on('click', (e) => { - e.preventDefault(); - const $target = $(e.currentTarget); - const value = $target.data('value'); - - $label.text($target.text().trim()); - this.fetchData({ startDate: value }); - }) - } - - } - -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/cycle_analytics.js.es6 b/app/assets/javascripts/cycle_analytics.js.es6 new file mode 100644 index 00000000000..cd9886ba58d --- /dev/null +++ b/app/assets/javascripts/cycle_analytics.js.es6 @@ -0,0 +1,93 @@ +((global) => { + + const COOKIE_NAME = 'cycle_analytics_help_dismissed'; + const store = gl.cycleAnalyticsStore = { + isLoading: true, + hasError: false, + isHelpDismissed: $.cookie(COOKIE_NAME), + analytics: {} + }; + + gl.CycleAnalytics = class CycleAnalytics { + constructor() { + const that = this; + + this.vue = new Vue({ + el: '#cycle-analytics', + name: 'CycleAnalytics', + created: this.fetchData(), + data: store, + methods: { + dismissLanding() { + that.dismissLanding(); + } + } + }); + } + + fetchData(options) { + store.isLoading = true; + options = options || { startDate: 30 }; + + $.ajax({ + url: $('#cycle-analytics').data('request-path'), + method: 'GET', + dataType: 'json', + contentType: 'application/json', + data: { start_date: options.startDate } + }).done((data) => { + this.decorateData(data); + this.initDropdown(); + }) + .error((data) => { + this.handleError(data); + }) + .always(() => { + store.isLoading = false; + }) + } + + decorateData(data) { + data.summary = data.summary || []; + data.stats = data.stats || []; + + data.summary.forEach((item) => { + item.value = item.value || '-'; + }); + + data.stats.forEach((item) => { + item.value = item.value || '- - -'; + }); + + store.analytics = data; + } + + handleError(data) { + store.hasError = true; + new Flash('There was an error while fetching cycle analytics data.', 'alert'); + } + + dismissLanding() { + store.isHelpDismissed = true; + $.cookie(COOKIE_NAME, true, { + path: gon.relative_url_root || '/' + }); + } + + initDropdown() { + const $dropdown = $('.js-ca-dropdown'); + const $label = $dropdown.find('.dropdown-label'); + + $dropdown.find('li a').off('click').on('click', (e) => { + e.preventDefault(); + const $target = $(e.currentTarget); + const value = $target.data('value'); + + $label.text($target.text().trim()); + this.fetchData({ startDate: value }); + }) + } + + } + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/issues-bulk-assignment.js.es6 b/app/assets/javascripts/issues-bulk-assignment.js.es6 deleted file mode 100644 index 0808f538f01..00000000000 --- a/app/assets/javascripts/issues-bulk-assignment.js.es6 +++ /dev/null @@ -1,149 +0,0 @@ -((global) => { - - class IssuableBulkActions { - constructor({ container, form, issues } = {}) { - this.container = container || $('.content'), - this.form = form || this.getElement('.bulk-update'); - this.issues = issues || this.getElement('.issues-list .issue'); - this.form.data('bulkActions', this); - this.willUpdateLabels = false; - this.bindEvents(); - // Fixes bulk-assign not working when navigating through pages - Issuable.initChecks(); - } - - getElement(selector) { - return this.container.find(selector); - } - - bindEvents() { - return this.form.off('submit').on('submit', this.onFormSubmit.bind(this)); - } - - onFormSubmit(e) { - e.preventDefault(); - return this.submit(); - } - - submit() { - const _this = this; - const xhr = $.ajax({ - url: this.form.attr('action'), - method: this.form.attr('method'), - dataType: 'JSON', - data: this.getFormDataAsObject() - }); - xhr.done(() => window.location.reload()); - xhr.fail(() => new Flash("Issue update failed")); - return xhr.always(this.onFormSubmitAlways.bind(this)); - } - - onFormSubmitAlways() { - return this.form.find('[type="submit"]').enable(); - } - - getSelectedIssues() { - return this.issues.has('.selected_issue:checked'); - } - - getLabelsFromSelection() { - const labels = []; - this.getSelectedIssues().map(function() { - const labelsData = $(this).data('labels'); - if (labelsData) { - return labelsData.map(function(labelId) { - if (labels.indexOf(labelId) === -1) { - return labels.push(labelId); - } - }); - } - }); - return labels; - } - - - /** - * Will return only labels that were marked previously and the user has unmarked - * @return {Array} Label IDs - */ - - getUnmarkedIndeterminedLabels() { - const result = []; - const labelsToKeep = []; - - this.getElement('.labels-filter .is-indeterminate') - .each((i, el) => labelsToKeep.push($(el).data('labelId'))); - - this.getLabelsFromSelection().forEach((id) => { - if (labelsToKeep.indexOf(id) === -1) { - result.push(id); - } - }); - - return result; - } - - - /** - * Simple form serialization, it will return just what we need - * Returns key/value pairs from form data - */ - - getFormDataAsObject() { - const formData = { - update: { - state_event: this.form.find('input[name="update[state_event]"]').val(), - assignee_id: this.form.find('input[name="update[assignee_id]"]').val(), - milestone_id: this.form.find('input[name="update[milestone_id]"]').val(), - issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(), - subscription_event: this.form.find('input[name="update[subscription_event]"]').val(), - add_label_ids: [], - remove_label_ids: [] - } - }; - if (this.willUpdateLabels) { - this.getLabelsToApply().map(function(id) { - return formData.update.add_label_ids.push(id); - }); - this.getLabelsToRemove().map(function(id) { - return formData.update.remove_label_ids.push(id); - }); - } - return formData; - } - - getLabelsToApply() { - const labelIds = []; - const $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]'); - $labels.each(function(k, label) { - if (label) { - return labelIds.push(parseInt($(label).val())); - } - }); - return labelIds; - } - - - /** - * Returns Label IDs that will be removed from issue selection - * @return {Array} Array of labels IDs - */ - - getLabelsToRemove() { - const result = []; - const indeterminatedLabels = this.getUnmarkedIndeterminedLabels(); - const labelsToApply = this.getLabelsToApply(); - indeterminatedLabels.map(function(id) { - // We need to exclude label IDs that will be applied - // By not doing this will cause issues from selection to not add labels at all - if (labelsToApply.indexOf(id) === -1) { - return result.push(id); - } - }); - return result; - } - } - - global.IssuableBulkActions = IssuableBulkActions; - -})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/issues_bulk_assignment.js.es6 b/app/assets/javascripts/issues_bulk_assignment.js.es6 new file mode 100644 index 00000000000..0808f538f01 --- /dev/null +++ b/app/assets/javascripts/issues_bulk_assignment.js.es6 @@ -0,0 +1,149 @@ +((global) => { + + class IssuableBulkActions { + constructor({ container, form, issues } = {}) { + this.container = container || $('.content'), + this.form = form || this.getElement('.bulk-update'); + this.issues = issues || this.getElement('.issues-list .issue'); + this.form.data('bulkActions', this); + this.willUpdateLabels = false; + this.bindEvents(); + // Fixes bulk-assign not working when navigating through pages + Issuable.initChecks(); + } + + getElement(selector) { + return this.container.find(selector); + } + + bindEvents() { + return this.form.off('submit').on('submit', this.onFormSubmit.bind(this)); + } + + onFormSubmit(e) { + e.preventDefault(); + return this.submit(); + } + + submit() { + const _this = this; + const xhr = $.ajax({ + url: this.form.attr('action'), + method: this.form.attr('method'), + dataType: 'JSON', + data: this.getFormDataAsObject() + }); + xhr.done(() => window.location.reload()); + xhr.fail(() => new Flash("Issue update failed")); + return xhr.always(this.onFormSubmitAlways.bind(this)); + } + + onFormSubmitAlways() { + return this.form.find('[type="submit"]').enable(); + } + + getSelectedIssues() { + return this.issues.has('.selected_issue:checked'); + } + + getLabelsFromSelection() { + const labels = []; + this.getSelectedIssues().map(function() { + const labelsData = $(this).data('labels'); + if (labelsData) { + return labelsData.map(function(labelId) { + if (labels.indexOf(labelId) === -1) { + return labels.push(labelId); + } + }); + } + }); + return labels; + } + + + /** + * Will return only labels that were marked previously and the user has unmarked + * @return {Array} Label IDs + */ + + getUnmarkedIndeterminedLabels() { + const result = []; + const labelsToKeep = []; + + this.getElement('.labels-filter .is-indeterminate') + .each((i, el) => labelsToKeep.push($(el).data('labelId'))); + + this.getLabelsFromSelection().forEach((id) => { + if (labelsToKeep.indexOf(id) === -1) { + result.push(id); + } + }); + + return result; + } + + + /** + * Simple form serialization, it will return just what we need + * Returns key/value pairs from form data + */ + + getFormDataAsObject() { + const formData = { + update: { + state_event: this.form.find('input[name="update[state_event]"]').val(), + assignee_id: this.form.find('input[name="update[assignee_id]"]').val(), + milestone_id: this.form.find('input[name="update[milestone_id]"]').val(), + issuable_ids: this.form.find('input[name="update[issuable_ids]"]').val(), + subscription_event: this.form.find('input[name="update[subscription_event]"]').val(), + add_label_ids: [], + remove_label_ids: [] + } + }; + if (this.willUpdateLabels) { + this.getLabelsToApply().map(function(id) { + return formData.update.add_label_ids.push(id); + }); + this.getLabelsToRemove().map(function(id) { + return formData.update.remove_label_ids.push(id); + }); + } + return formData; + } + + getLabelsToApply() { + const labelIds = []; + const $labels = this.form.find('.labels-filter input[name="update[label_ids][]"]'); + $labels.each(function(k, label) { + if (label) { + return labelIds.push(parseInt($(label).val())); + } + }); + return labelIds; + } + + + /** + * Returns Label IDs that will be removed from issue selection + * @return {Array} Array of labels IDs + */ + + getLabelsToRemove() { + const result = []; + const indeterminatedLabels = this.getUnmarkedIndeterminedLabels(); + const labelsToApply = this.getLabelsToApply(); + indeterminatedLabels.map(function(id) { + // We need to exclude label IDs that will be applied + // By not doing this will cause issues from selection to not add labels at all + if (labelsToApply.indexOf(id) === -1) { + return result.push(id); + } + }); + return result; + } + } + + global.IssuableBulkActions = IssuableBulkActions; + +})(window.gl || (window.gl = {})); diff --git a/app/assets/javascripts/label_manager.js.es6 b/app/assets/javascripts/label_manager.js.es6 new file mode 100644 index 00000000000..bc68e53504f --- /dev/null +++ b/app/assets/javascripts/label_manager.js.es6 @@ -0,0 +1,106 @@ +((global) => { + + class LabelManager { + constructor({ togglePriorityButton, prioritizedLabels, otherLabels } = {}) { + this.togglePriorityButton = togglePriorityButton || $('.js-toggle-priority'); + this.prioritizedLabels = prioritizedLabels || $('.js-prioritized-labels'); + this.otherLabels = otherLabels || $('.js-other-labels'); + this.errorMessage = 'Unable to update label prioritization at this time'; + this.prioritizedLabels.sortable({ + items: 'li', + placeholder: 'list-placeholder', + axis: 'y', + update: this.onPrioritySortUpdate.bind(this) + }); + this.bindEvents(); + } + + bindEvents() { + return this.togglePriorityButton.on('click', this, this.onTogglePriorityClick); + } + + onTogglePriorityClick(e) { + e.preventDefault(); + const _this = e.data; + const $btn = $(e.currentTarget); + const $label = $(`#${$btn.data('domId')}`); + const action = $btn.parents('.js-prioritized-labels').length ? 'remove' : 'add'; + const $tooltip = $(`#${$btn.find('.has-tooltip:visible').attr('aria-describedby')}`); + $tooltip.tooltip('destroy'); + return _this.toggleLabelPriority($label, action); + } + + toggleLabelPriority($label, action, persistState) { + if (persistState == null) { + persistState = true; + } + let xhr; + const _this = this; + const url = $label.find('.js-toggle-priority').data('url'); + let $target = this.prioritizedLabels; + let $from = this.otherLabels; + if (action === 'remove') { + $target = this.otherLabels; + $from = this.prioritizedLabels; + } + if ($from.find('li').length === 1) { + $from.find('.empty-message').removeClass('hidden'); + } + if (!$target.find('li').length) { + $target.find('.empty-message').addClass('hidden'); + } + $label.detach().appendTo($target); + // Return if we are not persisting state + if (!persistState) { + return; + } + if (action === 'remove') { + xhr = $.ajax({ + url, + type: 'DELETE' + }); + // Restore empty message + if (!$from.find('li').length) { + $from.find('.empty-message').removeClass('hidden'); + } + } else { + xhr = this.savePrioritySort($label, action); + } + return xhr.fail(this.rollbackLabelPosition.bind(this, $label, action)); + } + + onPrioritySortUpdate() { + const xhr = this.savePrioritySort(); + return xhr.fail(function() { + return new Flash(this.errorMessage, 'alert'); + }); + } + + savePrioritySort() { + return $.post({ + url: this.prioritizedLabels.data('url'), + data: { + label_ids: this.getSortedLabelsIds() + } + }); + } + + rollbackLabelPosition($label, originalAction) { + const action = originalAction === 'remove' ? 'add' : 'remove'; + this.toggleLabelPriority($label, action, false); + return new Flash(this.errorMessage, 'alert'); + } + + getSortedLabelsIds() { + const sortedIds = []; + this.prioritizedLabels.find('li').each(function() { + sortedIds.push($(this).data('id')); + }); + return sortedIds; + } + } + + gl.LabelManager = LabelManager; + +})(window.gl || (window.gl = {})); + diff --git a/app/assets/javascripts/network/branch-graph.js b/app/assets/javascripts/network/branch-graph.js deleted file mode 100644 index 91132af273a..00000000000 --- a/app/assets/javascripts/network/branch-graph.js +++ /dev/null @@ -1,417 +0,0 @@ -(function() { - var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; - - this.BranchGraph = (function() { - function BranchGraph(element1, options1) { - this.element = element1; - this.options = options1; - this.scrollTop = bind(this.scrollTop, this); - this.scrollBottom = bind(this.scrollBottom, this); - this.scrollRight = bind(this.scrollRight, this); - this.scrollLeft = bind(this.scrollLeft, this); - this.scrollUp = bind(this.scrollUp, this); - this.scrollDown = bind(this.scrollDown, this); - this.preparedCommits = {}; - this.mtime = 0; - this.mspace = 0; - this.parents = {}; - this.colors = ["#000"]; - this.offsetX = 150; - this.offsetY = 20; - this.unitTime = 30; - this.unitSpace = 10; - this.prev_start = -1; - this.load(); - } - - BranchGraph.prototype.load = function() { - return $.ajax({ - url: this.options.url, - method: "get", - dataType: "json", - success: $.proxy(function(data) { - $(".loading", this.element).hide(); - this.prepareData(data.days, data.commits); - return this.buildGraph(); - }, this) - }); - }; - - BranchGraph.prototype.prepareData = function(days, commits) { - var c, ch, cw, j, len, ref; - this.days = days; - this.commits = commits; - this.collectParents(); - this.graphHeight = $(this.element).height(); - this.graphWidth = $(this.element).width(); - ch = Math.max(this.graphHeight, this.offsetY + this.unitTime * this.mtime + 150); - cw = Math.max(this.graphWidth, this.offsetX + this.unitSpace * this.mspace + 300); - this.r = Raphael(this.element.get(0), cw, ch); - this.top = this.r.set(); - this.barHeight = Math.max(this.graphHeight, this.unitTime * this.days.length + 320); - ref = this.commits; - for (j = 0, len = ref.length; j < len; j++) { - c = ref[j]; - if (c.id in this.parents) { - c.isParent = true; - } - this.preparedCommits[c.id] = c; - this.markCommit(c); - } - return this.collectColors(); - }; - - BranchGraph.prototype.collectParents = function() { - var c, j, len, p, ref, results; - ref = this.commits; - results = []; - for (j = 0, len = ref.length; j < len; j++) { - c = ref[j]; - this.mtime = Math.max(this.mtime, c.time); - this.mspace = Math.max(this.mspace, c.space); - results.push((function() { - var l, len1, ref1, results1; - ref1 = c.parents; - results1 = []; - for (l = 0, len1 = ref1.length; l < len1; l++) { - p = ref1[l]; - this.parents[p[0]] = true; - results1.push(this.mspace = Math.max(this.mspace, p[1])); - } - return results1; - }).call(this)); - } - return results; - }; - - BranchGraph.prototype.collectColors = function() { - var k, results; - k = 0; - results = []; - while (k < this.mspace) { - this.colors.push(Raphael.getColor(.8)); - // Skipping a few colors in the spectrum to get more contrast between colors - Raphael.getColor(); - Raphael.getColor(); - results.push(k++); - } - return results; - }; - - BranchGraph.prototype.buildGraph = function() { - var cuday, cumonth, day, j, len, mm, r, ref; - r = this.r; - cuday = 0; - cumonth = ""; - r.rect(0, 0, 40, this.barHeight).attr({ - fill: "#222" - }); - r.rect(40, 0, 30, this.barHeight).attr({ - fill: "#444" - }); - ref = this.days; - for (mm = j = 0, len = ref.length; j < len; mm = ++j) { - day = ref[mm]; - if (cuday !== day[0] || cumonth !== day[1]) { - // Dates - r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({ - font: "12px Monaco, monospace", - fill: "#BBB" - }); - cuday = day[0]; - } - if (cumonth !== day[1]) { - // Months - r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({ - font: "12px Monaco, monospace", - fill: "#EEE" - }); - cumonth = day[1]; - } - } - this.renderPartialGraph(); - return this.bindEvents(); - }; - - BranchGraph.prototype.renderPartialGraph = function() { - var commit, end, i, isGraphEdge, start, x, y; - start = Math.floor((this.element.scrollTop() - this.offsetY) / this.unitTime) - 10; - if (start < 0) { - isGraphEdge = true; - start = 0; - } - end = start + 40; - if (this.commits.length < end) { - isGraphEdge = true; - end = this.commits.length; - } - if (this.prev_start === -1 || Math.abs(this.prev_start - start) > 10 || isGraphEdge) { - i = start; - this.prev_start = start; - while (i < end) { - commit = this.commits[i]; - i += 1; - if (commit.hasDrawn !== true) { - x = this.offsetX + this.unitSpace * (this.mspace - commit.space); - y = this.offsetY + this.unitTime * commit.time; - this.drawDot(x, y, commit); - this.drawLines(x, y, commit); - this.appendLabel(x, y, commit); - this.appendAnchor(x, y, commit); - commit.hasDrawn = true; - } - } - return this.top.toFront(); - } - }; - - BranchGraph.prototype.bindEvents = function() { - var element; - element = this.element; - return $(element).scroll((function(_this) { - return function(event) { - return _this.renderPartialGraph(); - }; - })(this)); - }; - - BranchGraph.prototype.scrollDown = function() { - this.element.scrollTop(this.element.scrollTop() + 50); - return this.renderPartialGraph(); - }; - - BranchGraph.prototype.scrollUp = function() { - this.element.scrollTop(this.element.scrollTop() - 50); - return this.renderPartialGraph(); - }; - - BranchGraph.prototype.scrollLeft = function() { - this.element.scrollLeft(this.element.scrollLeft() - 50); - return this.renderPartialGraph(); - }; - - BranchGraph.prototype.scrollRight = function() { - this.element.scrollLeft(this.element.scrollLeft() + 50); - return this.renderPartialGraph(); - }; - - BranchGraph.prototype.scrollBottom = function() { - return this.element.scrollTop(this.element.find('svg').height()); - }; - - BranchGraph.prototype.scrollTop = function() { - return this.element.scrollTop(0); - }; - - BranchGraph.prototype.appendLabel = function(x, y, commit) { - var label, r, rect, shortrefs, text, textbox, triangle; - if (!commit.refs) { - return; - } - r = this.r; - shortrefs = commit.refs; - // Truncate if longer than 15 chars - if (shortrefs.length > 17) { - shortrefs = shortrefs.substr(0, 15) + "…"; - } - text = r.text(x + 4, y, shortrefs).attr({ - "text-anchor": "start", - font: "10px Monaco, monospace", - fill: "#FFF", - title: commit.refs - }); - textbox = text.getBBox(); - // Create rectangle based on the size of the textbox - rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({ - fill: "#000", - "fill-opacity": .5, - stroke: "none" - }); - triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr({ - fill: "#000", - "fill-opacity": .5, - stroke: "none" - }); - label = r.set(rect, text); - label.transform(["t", -rect.getBBox().width - 15, 0]); - // Set text to front - return text.toFront(); - }; - - BranchGraph.prototype.appendAnchor = function(x, y, commit) { - var anchor, options, r, top; - r = this.r; - top = this.top; - options = this.options; - anchor = r.circle(x, y, 10).attr({ - fill: "#000", - opacity: 0, - cursor: "pointer" - }).click(function() { - return window.open(options.commit_url.replace("%s", commit.id), "_blank"); - }).hover(function() { - this.tooltip = r.commitTooltip(x + 5, y, commit); - return top.push(this.tooltip.insertBefore(this)); - }, function() { - return this.tooltip && this.tooltip.remove() && delete this.tooltip; - }); - return top.push(anchor); - }; - - BranchGraph.prototype.drawDot = function(x, y, commit) { - var avatar_box_x, avatar_box_y, r; - r = this.r; - r.circle(x, y, 3).attr({ - fill: this.colors[commit.space], - stroke: "none" - }); - avatar_box_x = this.offsetX + this.unitSpace * this.mspace + 10; - avatar_box_y = y - 10; - r.rect(avatar_box_x, avatar_box_y, 20, 20).attr({ - stroke: this.colors[commit.space], - "stroke-width": 2 - }); - r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20); - return r.text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split("\n")[0]).attr({ - "text-anchor": "start", - font: "14px Monaco, monospace" - }); - }; - - BranchGraph.prototype.drawLines = function(x, y, commit) { - var arrow, color, i, j, len, offset, parent, parentCommit, parentX1, parentX2, parentY, r, ref, results, route; - r = this.r; - ref = commit.parents; - results = []; - for (i = j = 0, len = ref.length; j < len; i = ++j) { - parent = ref[i]; - parentCommit = this.preparedCommits[parent[0]]; - parentY = this.offsetY + this.unitTime * parentCommit.time; - parentX1 = this.offsetX + this.unitSpace * (this.mspace - parentCommit.space); - parentX2 = this.offsetX + this.unitSpace * (this.mspace - parent[1]); - // Set line color - if (parentCommit.space <= commit.space) { - color = this.colors[commit.space]; - } else { - color = this.colors[parentCommit.space]; - } - // Build line shape - if (parent[1] === commit.space) { - offset = [0, 5]; - arrow = "l-2,5,4,0,-2,-5,0,5"; - } else if (parent[1] < commit.space) { - offset = [3, 3]; - arrow = "l5,0,-2,4,-3,-4,4,2"; - } else { - offset = [-3, 3]; - arrow = "l-5,0,2,4,3,-4,-4,2"; - } - // Start point - route = ["M", x + offset[0], y + offset[1]]; - // Add arrow if not first parent - if (i > 0) { - route.push(arrow); - } - // Circumvent if overlap - if (commit.space !== parentCommit.space || commit.space !== parent[1]) { - route.push("L", parentX2, y + 10, "L", parentX2, parentY - 5); - } - // End point - route.push("L", parentX1, parentY); - results.push(r.path(route).attr({ - stroke: color, - "stroke-width": 2 - })); - } - return results; - }; - - BranchGraph.prototype.markCommit = function(commit) { - var r, x, y; - if (commit.id === this.options.commit_id) { - r = this.r; - x = this.offsetX + this.unitSpace * (this.mspace - commit.space); - y = this.offsetY + this.unitTime * commit.time; - r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr({ - fill: "#000", - "fill-opacity": .5, - stroke: "none" - }); - // Displayed in the center - return this.element.scrollTop(y - this.graphHeight / 2); - } - }; - - return BranchGraph; - - })(); - - Raphael.prototype.commitTooltip = function(x, y, commit) { - var boxHeight, boxWidth, icon, idText, messageText, nameText, rect, textSet, tooltip; - boxWidth = 300; - boxHeight = 200; - icon = this.image(gon.relative_url_root + commit.author.icon, x, y, 20, 20); - nameText = this.text(x + 25, y + 10, commit.author.name); - idText = this.text(x, y + 35, commit.id); - messageText = this.text(x, y + 50, commit.message); - textSet = this.set(icon, nameText, idText, messageText).attr({ - "text-anchor": "start", - font: "12px Monaco, monospace" - }); - nameText.attr({ - font: "14px Arial", - "font-weight": "bold" - }); - idText.attr({ - fill: "#AAA" - }); - this.textWrap(messageText, boxWidth - 50); - rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({ - fill: "#FFF", - stroke: "#000", - "stroke-linecap": "round", - "stroke-width": 2 - }); - tooltip = this.set(rect, textSet); - rect.attr({ - height: tooltip.getBBox().height + 10, - width: tooltip.getBBox().width + 10 - }); - tooltip.transform(["t", 20, 20]); - return tooltip; - }; - - Raphael.prototype.textWrap = function(t, width) { - var abc, b, content, h, j, len, letterWidth, s, word, words, x; - content = t.attr("text"); - abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; - t.attr({ - text: abc - }); - letterWidth = t.getBBox().width / abc.length; - t.attr({ - text: content - }); - words = content.split(" "); - x = 0; - s = []; - for (j = 0, len = words.length; j < len; j++) { - word = words[j]; - if (x + (word.length * letterWidth) > width) { - s.push("\n"); - x = 0; - } - x += word.length * letterWidth; - s.push(word + " "); - } - t.attr({ - text: s.join("") - }); - b = t.getBBox(); - h = Math.abs(b.y2) - Math.abs(b.y) + 1; - return t.attr({ - y: b.y + h - }); - }; - -}).call(this); diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js new file mode 100644 index 00000000000..91132af273a --- /dev/null +++ b/app/assets/javascripts/network/branch_graph.js @@ -0,0 +1,417 @@ +(function() { + var bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }; + + this.BranchGraph = (function() { + function BranchGraph(element1, options1) { + this.element = element1; + this.options = options1; + this.scrollTop = bind(this.scrollTop, this); + this.scrollBottom = bind(this.scrollBottom, this); + this.scrollRight = bind(this.scrollRight, this); + this.scrollLeft = bind(this.scrollLeft, this); + this.scrollUp = bind(this.scrollUp, this); + this.scrollDown = bind(this.scrollDown, this); + this.preparedCommits = {}; + this.mtime = 0; + this.mspace = 0; + this.parents = {}; + this.colors = ["#000"]; + this.offsetX = 150; + this.offsetY = 20; + this.unitTime = 30; + this.unitSpace = 10; + this.prev_start = -1; + this.load(); + } + + BranchGraph.prototype.load = function() { + return $.ajax({ + url: this.options.url, + method: "get", + dataType: "json", + success: $.proxy(function(data) { + $(".loading", this.element).hide(); + this.prepareData(data.days, data.commits); + return this.buildGraph(); + }, this) + }); + }; + + BranchGraph.prototype.prepareData = function(days, commits) { + var c, ch, cw, j, len, ref; + this.days = days; + this.commits = commits; + this.collectParents(); + this.graphHeight = $(this.element).height(); + this.graphWidth = $(this.element).width(); + ch = Math.max(this.graphHeight, this.offsetY + this.unitTime * this.mtime + 150); + cw = Math.max(this.graphWidth, this.offsetX + this.unitSpace * this.mspace + 300); + this.r = Raphael(this.element.get(0), cw, ch); + this.top = this.r.set(); + this.barHeight = Math.max(this.graphHeight, this.unitTime * this.days.length + 320); + ref = this.commits; + for (j = 0, len = ref.length; j < len; j++) { + c = ref[j]; + if (c.id in this.parents) { + c.isParent = true; + } + this.preparedCommits[c.id] = c; + this.markCommit(c); + } + return this.collectColors(); + }; + + BranchGraph.prototype.collectParents = function() { + var c, j, len, p, ref, results; + ref = this.commits; + results = []; + for (j = 0, len = ref.length; j < len; j++) { + c = ref[j]; + this.mtime = Math.max(this.mtime, c.time); + this.mspace = Math.max(this.mspace, c.space); + results.push((function() { + var l, len1, ref1, results1; + ref1 = c.parents; + results1 = []; + for (l = 0, len1 = ref1.length; l < len1; l++) { + p = ref1[l]; + this.parents[p[0]] = true; + results1.push(this.mspace = Math.max(this.mspace, p[1])); + } + return results1; + }).call(this)); + } + return results; + }; + + BranchGraph.prototype.collectColors = function() { + var k, results; + k = 0; + results = []; + while (k < this.mspace) { + this.colors.push(Raphael.getColor(.8)); + // Skipping a few colors in the spectrum to get more contrast between colors + Raphael.getColor(); + Raphael.getColor(); + results.push(k++); + } + return results; + }; + + BranchGraph.prototype.buildGraph = function() { + var cuday, cumonth, day, j, len, mm, r, ref; + r = this.r; + cuday = 0; + cumonth = ""; + r.rect(0, 0, 40, this.barHeight).attr({ + fill: "#222" + }); + r.rect(40, 0, 30, this.barHeight).attr({ + fill: "#444" + }); + ref = this.days; + for (mm = j = 0, len = ref.length; j < len; mm = ++j) { + day = ref[mm]; + if (cuday !== day[0] || cumonth !== day[1]) { + // Dates + r.text(55, this.offsetY + this.unitTime * mm, day[0]).attr({ + font: "12px Monaco, monospace", + fill: "#BBB" + }); + cuday = day[0]; + } + if (cumonth !== day[1]) { + // Months + r.text(20, this.offsetY + this.unitTime * mm, day[1]).attr({ + font: "12px Monaco, monospace", + fill: "#EEE" + }); + cumonth = day[1]; + } + } + this.renderPartialGraph(); + return this.bindEvents(); + }; + + BranchGraph.prototype.renderPartialGraph = function() { + var commit, end, i, isGraphEdge, start, x, y; + start = Math.floor((this.element.scrollTop() - this.offsetY) / this.unitTime) - 10; + if (start < 0) { + isGraphEdge = true; + start = 0; + } + end = start + 40; + if (this.commits.length < end) { + isGraphEdge = true; + end = this.commits.length; + } + if (this.prev_start === -1 || Math.abs(this.prev_start - start) > 10 || isGraphEdge) { + i = start; + this.prev_start = start; + while (i < end) { + commit = this.commits[i]; + i += 1; + if (commit.hasDrawn !== true) { + x = this.offsetX + this.unitSpace * (this.mspace - commit.space); + y = this.offsetY + this.unitTime * commit.time; + this.drawDot(x, y, commit); + this.drawLines(x, y, commit); + this.appendLabel(x, y, commit); + this.appendAnchor(x, y, commit); + commit.hasDrawn = true; + } + } + return this.top.toFront(); + } + }; + + BranchGraph.prototype.bindEvents = function() { + var element; + element = this.element; + return $(element).scroll((function(_this) { + return function(event) { + return _this.renderPartialGraph(); + }; + })(this)); + }; + + BranchGraph.prototype.scrollDown = function() { + this.element.scrollTop(this.element.scrollTop() + 50); + return this.renderPartialGraph(); + }; + + BranchGraph.prototype.scrollUp = function() { + this.element.scrollTop(this.element.scrollTop() - 50); + return this.renderPartialGraph(); + }; + + BranchGraph.prototype.scrollLeft = function() { + this.element.scrollLeft(this.element.scrollLeft() - 50); + return this.renderPartialGraph(); + }; + + BranchGraph.prototype.scrollRight = function() { + this.element.scrollLeft(this.element.scrollLeft() + 50); + return this.renderPartialGraph(); + }; + + BranchGraph.prototype.scrollBottom = function() { + return this.element.scrollTop(this.element.find('svg').height()); + }; + + BranchGraph.prototype.scrollTop = function() { + return this.element.scrollTop(0); + }; + + BranchGraph.prototype.appendLabel = function(x, y, commit) { + var label, r, rect, shortrefs, text, textbox, triangle; + if (!commit.refs) { + return; + } + r = this.r; + shortrefs = commit.refs; + // Truncate if longer than 15 chars + if (shortrefs.length > 17) { + shortrefs = shortrefs.substr(0, 15) + "…"; + } + text = r.text(x + 4, y, shortrefs).attr({ + "text-anchor": "start", + font: "10px Monaco, monospace", + fill: "#FFF", + title: commit.refs + }); + textbox = text.getBBox(); + // Create rectangle based on the size of the textbox + rect = r.rect(x, y - 7, textbox.width + 5, textbox.height + 5, 4).attr({ + fill: "#000", + "fill-opacity": .5, + stroke: "none" + }); + triangle = r.path(["M", x - 5, y, "L", x - 15, y - 4, "L", x - 15, y + 4, "Z"]).attr({ + fill: "#000", + "fill-opacity": .5, + stroke: "none" + }); + label = r.set(rect, text); + label.transform(["t", -rect.getBBox().width - 15, 0]); + // Set text to front + return text.toFront(); + }; + + BranchGraph.prototype.appendAnchor = function(x, y, commit) { + var anchor, options, r, top; + r = this.r; + top = this.top; + options = this.options; + anchor = r.circle(x, y, 10).attr({ + fill: "#000", + opacity: 0, + cursor: "pointer" + }).click(function() { + return window.open(options.commit_url.replace("%s", commit.id), "_blank"); + }).hover(function() { + this.tooltip = r.commitTooltip(x + 5, y, commit); + return top.push(this.tooltip.insertBefore(this)); + }, function() { + return this.tooltip && this.tooltip.remove() && delete this.tooltip; + }); + return top.push(anchor); + }; + + BranchGraph.prototype.drawDot = function(x, y, commit) { + var avatar_box_x, avatar_box_y, r; + r = this.r; + r.circle(x, y, 3).attr({ + fill: this.colors[commit.space], + stroke: "none" + }); + avatar_box_x = this.offsetX + this.unitSpace * this.mspace + 10; + avatar_box_y = y - 10; + r.rect(avatar_box_x, avatar_box_y, 20, 20).attr({ + stroke: this.colors[commit.space], + "stroke-width": 2 + }); + r.image(commit.author.icon, avatar_box_x, avatar_box_y, 20, 20); + return r.text(this.offsetX + this.unitSpace * this.mspace + 35, y, commit.message.split("\n")[0]).attr({ + "text-anchor": "start", + font: "14px Monaco, monospace" + }); + }; + + BranchGraph.prototype.drawLines = function(x, y, commit) { + var arrow, color, i, j, len, offset, parent, parentCommit, parentX1, parentX2, parentY, r, ref, results, route; + r = this.r; + ref = commit.parents; + results = []; + for (i = j = 0, len = ref.length; j < len; i = ++j) { + parent = ref[i]; + parentCommit = this.preparedCommits[parent[0]]; + parentY = this.offsetY + this.unitTime * parentCommit.time; + parentX1 = this.offsetX + this.unitSpace * (this.mspace - parentCommit.space); + parentX2 = this.offsetX + this.unitSpace * (this.mspace - parent[1]); + // Set line color + if (parentCommit.space <= commit.space) { + color = this.colors[commit.space]; + } else { + color = this.colors[parentCommit.space]; + } + // Build line shape + if (parent[1] === commit.space) { + offset = [0, 5]; + arrow = "l-2,5,4,0,-2,-5,0,5"; + } else if (parent[1] < commit.space) { + offset = [3, 3]; + arrow = "l5,0,-2,4,-3,-4,4,2"; + } else { + offset = [-3, 3]; + arrow = "l-5,0,2,4,3,-4,-4,2"; + } + // Start point + route = ["M", x + offset[0], y + offset[1]]; + // Add arrow if not first parent + if (i > 0) { + route.push(arrow); + } + // Circumvent if overlap + if (commit.space !== parentCommit.space || commit.space !== parent[1]) { + route.push("L", parentX2, y + 10, "L", parentX2, parentY - 5); + } + // End point + route.push("L", parentX1, parentY); + results.push(r.path(route).attr({ + stroke: color, + "stroke-width": 2 + })); + } + return results; + }; + + BranchGraph.prototype.markCommit = function(commit) { + var r, x, y; + if (commit.id === this.options.commit_id) { + r = this.r; + x = this.offsetX + this.unitSpace * (this.mspace - commit.space); + y = this.offsetY + this.unitTime * commit.time; + r.path(["M", x + 5, y, "L", x + 15, y + 4, "L", x + 15, y - 4, "Z"]).attr({ + fill: "#000", + "fill-opacity": .5, + stroke: "none" + }); + // Displayed in the center + return this.element.scrollTop(y - this.graphHeight / 2); + } + }; + + return BranchGraph; + + })(); + + Raphael.prototype.commitTooltip = function(x, y, commit) { + var boxHeight, boxWidth, icon, idText, messageText, nameText, rect, textSet, tooltip; + boxWidth = 300; + boxHeight = 200; + icon = this.image(gon.relative_url_root + commit.author.icon, x, y, 20, 20); + nameText = this.text(x + 25, y + 10, commit.author.name); + idText = this.text(x, y + 35, commit.id); + messageText = this.text(x, y + 50, commit.message); + textSet = this.set(icon, nameText, idText, messageText).attr({ + "text-anchor": "start", + font: "12px Monaco, monospace" + }); + nameText.attr({ + font: "14px Arial", + "font-weight": "bold" + }); + idText.attr({ + fill: "#AAA" + }); + this.textWrap(messageText, boxWidth - 50); + rect = this.rect(x - 10, y - 10, boxWidth, 100, 4).attr({ + fill: "#FFF", + stroke: "#000", + "stroke-linecap": "round", + "stroke-width": 2 + }); + tooltip = this.set(rect, textSet); + rect.attr({ + height: tooltip.getBBox().height + 10, + width: tooltip.getBBox().width + 10 + }); + tooltip.transform(["t", 20, 20]); + return tooltip; + }; + + Raphael.prototype.textWrap = function(t, width) { + var abc, b, content, h, j, len, letterWidth, s, word, words, x; + content = t.attr("text"); + abc = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + t.attr({ + text: abc + }); + letterWidth = t.getBBox().width / abc.length; + t.attr({ + text: content + }); + words = content.split(" "); + x = 0; + s = []; + for (j = 0, len = words.length; j < len; j++) { + word = words[j]; + if (x + (word.length * letterWidth) > width) { + s.push("\n"); + x = 0; + } + x += word.length * letterWidth; + s.push(word + " "); + } + t.attr({ + text: s.join("") + }); + b = t.getBBox(); + h = Math.abs(b.y2) - Math.abs(b.y) + 1; + return t.attr({ + y: b.y + h + }); + }; + +}).call(this); -- cgit v1.2.1 From f60e0b92b8cba1d70c40094511a87fc0ed62977d Mon Sep 17 00:00:00 2001 From: TMate Software Support Date: Mon, 10 Oct 2016 18:14:29 +0000 Subject: Update migrating_from_svn.md document on migration from SVN to GitLab as suggested by @axil in MR 6549. --- doc/workflow/importing/migrating_from_svn.md | 52 ++++++++++++++++------------ 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md index 76839315c53..fc27a38f735 100644 --- a/doc/workflow/importing/migrating_from_svn.md +++ b/doc/workflow/importing/migrating_from_svn.md @@ -8,21 +8,16 @@ between the two, for more information consult your favorite search engine. There are two approaches to SVN to Git migration: -#### [Git/SVN Mirror](#mirror) +1. [Git/SVN Mirror](#smooth-migration-with-a-gitsvn-mirror-using-subgit) which: + - Makes the GitLab repository to mirror the SVN project. + - Git and SVN repositories are kept in sync; you can use either one. + - Smoothens the migration process and allows to manage migration risks. - Make GitLab repository mirror SVN project. - - Git and SVN project are kept in sync; use either one or another. - - Smoothens migration process and allows to manage migration risks. +1. [Cut over migration](#cut-over-migration-with-svn2git) which: + - Translates and imports the existing data and history from SVN to Git. + - Is a fire and forget approach, good for smaller teams. -#### [Cut over migration](#cutover) - - Translate existing data and history from SVN to Git. - - Fire and forget approach, good for smaller teams. - -## Smooth migration with a Git/SVN mirror using SubGit +## Smooth migration with a Git/SVN mirror using SubGit #### Prerequisites @@ -37,20 +32,32 @@ at `/opt/subgit-VERSION/bin/subgit` #### Configuration In GitLab create new empty repository. In filesystem it will be located at -`/var/opt/gitlab/git-data/repositories/USER/REPOS.git` path. +`/var/opt/gitlab/git-data/repositories/USER/REPOS.git` path by default. +For convenice, assign this path to a variable: + +``` +GIT_REPOS_PATH=/var/opt/gitlab/git-data/repositories/USER/REPOS.git +``` + +SubGit will keep this repository will be kept in sync with a remote SVN project. +For convenience, assign remote SVN project URL to a variable: + +``` +SVN_PROJECT_URL=http://svn.company.com/repos/project +``` Run SubGit to set up a Git/SVN mirror. Make sure `subgit` command is ran -on behalf of the same user that runs GitLab. +on behalf of the same user that keeps ownership of GitLab Git repositories (`git` by default): ``` -subgit configure --layout auto SVN_PROJECT_URL GIT_REPOS_PATH +subgit configure --layout auto $SVN_PROJECT_URL $GIT_REPOS_PATH ``` Adjust authors and branches mappings, if necessary: ``` -edit GIT_REPOS_PATH/subgit/authors.txt -edit GIT_REPOS_PATH/subgit/config +edit $GIT_REPOS_PATH/subgit/authors.txt +edit $GIT_REPOS_PATH/subgit/config ``` For more information regarding SubGit configuration options, refer to @@ -62,7 +69,7 @@ Run `subgit` to perform initial translation of existing SVN revisions into Git repository: ``` -subgit install GIT_REPOS_PATH +subgit install $GIT_REPOS_PATH ``` After initial translation is completed, GitLab Git repository and SVN project @@ -74,7 +81,7 @@ Would you prefer to perform one-time cut over migration with `subgit` use `import` command in place of `install`: ``` -subgit import GIT_REPOS_PATH +subgit import $GIT_REPOS_PATH ``` #### Licensing @@ -87,10 +94,9 @@ progress at [this issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/990). #### Support -For any questions related to SVN to GitLab migration with SubGit contact us at [support@subgit.com](mailto:support@subgit.com). We support -all our users. +For any questions related to SVN to GitLab migration with SubGit you can contact SubGit team at [support@subgit.com](mailto:support@subgit.com). -## Cut over migration with svn2git +## Cut over migration with svn2git If you are currently using an SVN repository, you can migrate the repository to Git and GitLab. We recommend a hard cut over - run the migration command once -- cgit v1.2.1 From eecccf5e206ffdac6d29f757804165654324ea31 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 10 Oct 2016 21:36:30 +0200 Subject: Change img dir name --- doc/gitlab-basics/add-file.md | 10 +++++----- doc/gitlab-basics/add-merge-request.md | 16 ++++++++-------- .../basicsimages/add_new_merge_request.png | Bin 9003 -> 0 bytes doc/gitlab-basics/basicsimages/add_sshkey.png | Bin 1394 -> 0 bytes doc/gitlab-basics/basicsimages/branch_info.png | Bin 7572 -> 0 bytes doc/gitlab-basics/basicsimages/branch_name.png | Bin 2137 -> 0 bytes doc/gitlab-basics/basicsimages/branches.png | Bin 3548 -> 0 bytes doc/gitlab-basics/basicsimages/button-create-mr.png | Bin 5927 -> 0 bytes doc/gitlab-basics/basicsimages/click-on-new-group.png | Bin 1957 -> 0 bytes doc/gitlab-basics/basicsimages/commit_changes.png | Bin 4941 -> 0 bytes doc/gitlab-basics/basicsimages/commit_message.png | Bin 5103 -> 0 bytes doc/gitlab-basics/basicsimages/commits.png | Bin 4112 -> 0 bytes doc/gitlab-basics/basicsimages/compare_branches.png | Bin 1520 -> 0 bytes doc/gitlab-basics/basicsimages/create_file.png | Bin 2451 -> 0 bytes doc/gitlab-basics/basicsimages/create_group.png | Bin 3184 -> 0 bytes doc/gitlab-basics/basicsimages/edit_file.png | Bin 2221 -> 0 bytes doc/gitlab-basics/basicsimages/file_located.png | Bin 3078 -> 0 bytes doc/gitlab-basics/basicsimages/file_name.png | Bin 2412 -> 0 bytes doc/gitlab-basics/basicsimages/find_file.png | Bin 8426 -> 0 bytes doc/gitlab-basics/basicsimages/find_group.png | Bin 5897 -> 0 bytes doc/gitlab-basics/basicsimages/fork.png | Bin 896 -> 0 bytes doc/gitlab-basics/basicsimages/group_info.png | Bin 15479 -> 0 bytes doc/gitlab-basics/basicsimages/groups.png | Bin 4752 -> 0 bytes doc/gitlab-basics/basicsimages/https.png | Bin 2822 -> 0 bytes doc/gitlab-basics/basicsimages/image_file.png | Bin 2796 -> 0 bytes doc/gitlab-basics/basicsimages/issue_title.png | Bin 8311 -> 0 bytes doc/gitlab-basics/basicsimages/issues.png | Bin 4153 -> 0 bytes doc/gitlab-basics/basicsimages/key.png | Bin 1177 -> 0 bytes doc/gitlab-basics/basicsimages/merge_requests.png | Bin 4213 -> 0 bytes doc/gitlab-basics/basicsimages/new_issue.png | Bin 2974 -> 0 bytes doc/gitlab-basics/basicsimages/new_merge_request.png | Bin 3162 -> 0 bytes doc/gitlab-basics/basicsimages/new_project.png | Bin 2234 -> 0 bytes doc/gitlab-basics/basicsimages/newbranch.png | Bin 1244 -> 0 bytes doc/gitlab-basics/basicsimages/paste_sshkey.png | Bin 7699 -> 0 bytes doc/gitlab-basics/basicsimages/profile_settings.png | Bin 1101 -> 0 bytes doc/gitlab-basics/basicsimages/project_info.png | Bin 21041 -> 0 bytes doc/gitlab-basics/basicsimages/public_file_link.png | Bin 3023 -> 0 bytes doc/gitlab-basics/basicsimages/select-group.png | Bin 6034 -> 0 bytes doc/gitlab-basics/basicsimages/select-group2.png | Bin 5040 -> 0 bytes doc/gitlab-basics/basicsimages/select_branch.png | Bin 11207 -> 0 bytes doc/gitlab-basics/basicsimages/select_project.png | Bin 16176 -> 0 bytes doc/gitlab-basics/basicsimages/settings.png | Bin 4149 -> 0 bytes doc/gitlab-basics/basicsimages/shh_keys.png | Bin 4782 -> 0 bytes doc/gitlab-basics/basicsimages/submit_new_issue.png | Bin 8644 -> 0 bytes doc/gitlab-basics/basicsimages/title_description_mr.png | Bin 11919 -> 0 bytes doc/gitlab-basics/basicsimages/white_space.png | Bin 2192 -> 0 bytes doc/gitlab-basics/command-line-commands.md | 4 ++-- doc/gitlab-basics/create-branch.md | 12 ++++++------ doc/gitlab-basics/create-group.md | 10 +++++----- doc/gitlab-basics/create-issue.md | 10 +++++----- doc/gitlab-basics/create-project.md | 4 ++-- doc/gitlab-basics/create-your-ssh-keys.md | 8 ++++---- doc/gitlab-basics/fork-project.md | 4 ++-- doc/gitlab-basics/img/add_new_merge_request.png | Bin 0 -> 9003 bytes doc/gitlab-basics/img/add_sshkey.png | Bin 0 -> 1394 bytes doc/gitlab-basics/img/branch_info.png | Bin 0 -> 7572 bytes doc/gitlab-basics/img/branch_name.png | Bin 0 -> 2137 bytes doc/gitlab-basics/img/branches.png | Bin 0 -> 3548 bytes doc/gitlab-basics/img/button-create-mr.png | Bin 0 -> 5927 bytes doc/gitlab-basics/img/click-on-new-group.png | Bin 0 -> 1957 bytes doc/gitlab-basics/img/commit_changes.png | Bin 0 -> 4941 bytes doc/gitlab-basics/img/commit_message.png | Bin 0 -> 5103 bytes doc/gitlab-basics/img/commits.png | Bin 0 -> 4112 bytes doc/gitlab-basics/img/compare_branches.png | Bin 0 -> 1520 bytes doc/gitlab-basics/img/create_file.png | Bin 0 -> 2451 bytes doc/gitlab-basics/img/create_group.png | Bin 0 -> 3184 bytes doc/gitlab-basics/img/edit_file.png | Bin 0 -> 2221 bytes doc/gitlab-basics/img/file_located.png | Bin 0 -> 3078 bytes doc/gitlab-basics/img/file_name.png | Bin 0 -> 2412 bytes doc/gitlab-basics/img/find_file.png | Bin 0 -> 8426 bytes doc/gitlab-basics/img/find_group.png | Bin 0 -> 5897 bytes doc/gitlab-basics/img/fork.png | Bin 0 -> 896 bytes doc/gitlab-basics/img/group_info.png | Bin 0 -> 15479 bytes doc/gitlab-basics/img/groups.png | Bin 0 -> 4752 bytes doc/gitlab-basics/img/https.png | Bin 0 -> 2822 bytes doc/gitlab-basics/img/image_file.png | Bin 0 -> 2796 bytes doc/gitlab-basics/img/issue_title.png | Bin 0 -> 8311 bytes doc/gitlab-basics/img/issues.png | Bin 0 -> 4153 bytes doc/gitlab-basics/img/key.png | Bin 0 -> 1177 bytes doc/gitlab-basics/img/merge_requests.png | Bin 0 -> 4213 bytes doc/gitlab-basics/img/new_issue.png | Bin 0 -> 2974 bytes doc/gitlab-basics/img/new_merge_request.png | Bin 0 -> 3162 bytes doc/gitlab-basics/img/new_project.png | Bin 0 -> 2234 bytes doc/gitlab-basics/img/newbranch.png | Bin 0 -> 1244 bytes doc/gitlab-basics/img/paste_sshkey.png | Bin 0 -> 7699 bytes doc/gitlab-basics/img/profile_settings.png | Bin 0 -> 1101 bytes doc/gitlab-basics/img/project_info.png | Bin 0 -> 21041 bytes doc/gitlab-basics/img/public_file_link.png | Bin 0 -> 3023 bytes doc/gitlab-basics/img/select-group.png | Bin 0 -> 6034 bytes doc/gitlab-basics/img/select-group2.png | Bin 0 -> 5040 bytes doc/gitlab-basics/img/select_branch.png | Bin 0 -> 11207 bytes doc/gitlab-basics/img/select_project.png | Bin 0 -> 16176 bytes doc/gitlab-basics/img/settings.png | Bin 0 -> 4149 bytes doc/gitlab-basics/img/shh_keys.png | Bin 0 -> 4782 bytes doc/gitlab-basics/img/submit_new_issue.png | Bin 0 -> 8644 bytes doc/gitlab-basics/img/title_description_mr.png | Bin 0 -> 11919 bytes doc/gitlab-basics/img/white_space.png | Bin 0 -> 2192 bytes 97 files changed, 39 insertions(+), 39 deletions(-) delete mode 100644 doc/gitlab-basics/basicsimages/add_new_merge_request.png delete mode 100644 doc/gitlab-basics/basicsimages/add_sshkey.png delete mode 100644 doc/gitlab-basics/basicsimages/branch_info.png delete mode 100644 doc/gitlab-basics/basicsimages/branch_name.png delete mode 100644 doc/gitlab-basics/basicsimages/branches.png delete mode 100644 doc/gitlab-basics/basicsimages/button-create-mr.png delete mode 100644 doc/gitlab-basics/basicsimages/click-on-new-group.png delete mode 100644 doc/gitlab-basics/basicsimages/commit_changes.png delete mode 100644 doc/gitlab-basics/basicsimages/commit_message.png delete mode 100644 doc/gitlab-basics/basicsimages/commits.png delete mode 100644 doc/gitlab-basics/basicsimages/compare_branches.png delete mode 100644 doc/gitlab-basics/basicsimages/create_file.png delete mode 100644 doc/gitlab-basics/basicsimages/create_group.png delete mode 100644 doc/gitlab-basics/basicsimages/edit_file.png delete mode 100644 doc/gitlab-basics/basicsimages/file_located.png delete mode 100644 doc/gitlab-basics/basicsimages/file_name.png delete mode 100644 doc/gitlab-basics/basicsimages/find_file.png delete mode 100644 doc/gitlab-basics/basicsimages/find_group.png delete mode 100644 doc/gitlab-basics/basicsimages/fork.png delete mode 100644 doc/gitlab-basics/basicsimages/group_info.png delete mode 100644 doc/gitlab-basics/basicsimages/groups.png delete mode 100644 doc/gitlab-basics/basicsimages/https.png delete mode 100644 doc/gitlab-basics/basicsimages/image_file.png delete mode 100644 doc/gitlab-basics/basicsimages/issue_title.png delete mode 100644 doc/gitlab-basics/basicsimages/issues.png delete mode 100644 doc/gitlab-basics/basicsimages/key.png delete mode 100644 doc/gitlab-basics/basicsimages/merge_requests.png delete mode 100644 doc/gitlab-basics/basicsimages/new_issue.png delete mode 100644 doc/gitlab-basics/basicsimages/new_merge_request.png delete mode 100644 doc/gitlab-basics/basicsimages/new_project.png delete mode 100644 doc/gitlab-basics/basicsimages/newbranch.png delete mode 100644 doc/gitlab-basics/basicsimages/paste_sshkey.png delete mode 100644 doc/gitlab-basics/basicsimages/profile_settings.png delete mode 100644 doc/gitlab-basics/basicsimages/project_info.png delete mode 100644 doc/gitlab-basics/basicsimages/public_file_link.png delete mode 100644 doc/gitlab-basics/basicsimages/select-group.png delete mode 100644 doc/gitlab-basics/basicsimages/select-group2.png delete mode 100644 doc/gitlab-basics/basicsimages/select_branch.png delete mode 100644 doc/gitlab-basics/basicsimages/select_project.png delete mode 100644 doc/gitlab-basics/basicsimages/settings.png delete mode 100644 doc/gitlab-basics/basicsimages/shh_keys.png delete mode 100644 doc/gitlab-basics/basicsimages/submit_new_issue.png delete mode 100644 doc/gitlab-basics/basicsimages/title_description_mr.png delete mode 100644 doc/gitlab-basics/basicsimages/white_space.png create mode 100644 doc/gitlab-basics/img/add_new_merge_request.png create mode 100644 doc/gitlab-basics/img/add_sshkey.png create mode 100644 doc/gitlab-basics/img/branch_info.png create mode 100644 doc/gitlab-basics/img/branch_name.png create mode 100644 doc/gitlab-basics/img/branches.png create mode 100644 doc/gitlab-basics/img/button-create-mr.png create mode 100644 doc/gitlab-basics/img/click-on-new-group.png create mode 100644 doc/gitlab-basics/img/commit_changes.png create mode 100644 doc/gitlab-basics/img/commit_message.png create mode 100644 doc/gitlab-basics/img/commits.png create mode 100644 doc/gitlab-basics/img/compare_branches.png create mode 100644 doc/gitlab-basics/img/create_file.png create mode 100644 doc/gitlab-basics/img/create_group.png create mode 100644 doc/gitlab-basics/img/edit_file.png create mode 100644 doc/gitlab-basics/img/file_located.png create mode 100644 doc/gitlab-basics/img/file_name.png create mode 100644 doc/gitlab-basics/img/find_file.png create mode 100644 doc/gitlab-basics/img/find_group.png create mode 100644 doc/gitlab-basics/img/fork.png create mode 100644 doc/gitlab-basics/img/group_info.png create mode 100644 doc/gitlab-basics/img/groups.png create mode 100644 doc/gitlab-basics/img/https.png create mode 100644 doc/gitlab-basics/img/image_file.png create mode 100644 doc/gitlab-basics/img/issue_title.png create mode 100644 doc/gitlab-basics/img/issues.png create mode 100644 doc/gitlab-basics/img/key.png create mode 100644 doc/gitlab-basics/img/merge_requests.png create mode 100644 doc/gitlab-basics/img/new_issue.png create mode 100644 doc/gitlab-basics/img/new_merge_request.png create mode 100644 doc/gitlab-basics/img/new_project.png create mode 100644 doc/gitlab-basics/img/newbranch.png create mode 100644 doc/gitlab-basics/img/paste_sshkey.png create mode 100644 doc/gitlab-basics/img/profile_settings.png create mode 100644 doc/gitlab-basics/img/project_info.png create mode 100644 doc/gitlab-basics/img/public_file_link.png create mode 100644 doc/gitlab-basics/img/select-group.png create mode 100644 doc/gitlab-basics/img/select-group2.png create mode 100644 doc/gitlab-basics/img/select_branch.png create mode 100644 doc/gitlab-basics/img/select_project.png create mode 100644 doc/gitlab-basics/img/settings.png create mode 100644 doc/gitlab-basics/img/shh_keys.png create mode 100644 doc/gitlab-basics/img/submit_new_issue.png create mode 100644 doc/gitlab-basics/img/title_description_mr.png create mode 100644 doc/gitlab-basics/img/white_space.png diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md index ff10a98e8f5..86aff749ff1 100644 --- a/doc/gitlab-basics/add-file.md +++ b/doc/gitlab-basics/add-file.md @@ -6,22 +6,22 @@ To create a file in GitLab, sign in to GitLab. Select a project on the right side of your screen: -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) It's a good idea to [create a branch](create-branch.md), but it's not necessary. Go to the directory where you'd like to add the file and click on the "+" sign next to the name of the project and directory: -![Create a file](basicsimages/create_file.png) +![Create a file](img/create_file.png) Name your file (you can't add spaces, so you can use hyphens or underscores). Don't forget to include the markup language you'd like to use : -![File name](basicsimages/file_name.png) +![File name](img/file_name.png) Add all the information that you'd like to include in your file: -![Add information](basicsimages/white_space.png) +![Add information](img/white_space.png) Add a commit message based on what you just added and then click on "commit changes": -![Commit changes](basicsimages/commit_changes.png) +![Commit changes](img/commit_changes.png) diff --git a/doc/gitlab-basics/add-merge-request.md b/doc/gitlab-basics/add-merge-request.md index 236b4248ea2..082d1408aa2 100644 --- a/doc/gitlab-basics/add-merge-request.md +++ b/doc/gitlab-basics/add-merge-request.md @@ -6,31 +6,31 @@ To create a new Merge Request, sign in to GitLab. Go to the project where you'd like to merge your changes: -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) Click on "Merge Requests" on the left side of your screen: -![Merge requests](basicsimages/merge_requests.png) +![Merge requests](img/merge_requests.png) Click on "+ new Merge Request" on the right side of the screen: -![New Merge Request](basicsimages/new_merge_request.png) +![New Merge Request](img/new_merge_request.png) Select a source branch or branch: -![Select a branch](basicsimages/select_branch.png) +![Select a branch](img/select_branch.png) Click on the "compare branches" button: -![Compare branches](basicsimages/compare_branches.png) +![Compare branches](img/compare_branches.png) Add a title and a description to your Merge Request: -![Add a title and description](basicsimages/title_description_mr.png) +![Add a title and description](img/title_description_mr.png) Select a user to review your Merge Request and to accept or close it. You may also select milestones and labels (they are optional). Then click on the "submit new Merge Request" button: -![Add a new merge request](basicsimages/add_new_merge_request.png) +![Add a new merge request](img/add_new_merge_request.png) Your Merge Request will be ready to be approved and published. @@ -39,4 +39,4 @@ Your Merge Request will be ready to be approved and published. After you created a new branch, you'll immediately find a "create a Merge Request" button at the top of your screen. You may automatically create a Merge Request from your recently created branch when clicking on this button: -![Automatic MR button](basicsimages/button-create-mr.png) +![Automatic MR button](img/button-create-mr.png) diff --git a/doc/gitlab-basics/basicsimages/add_new_merge_request.png b/doc/gitlab-basics/basicsimages/add_new_merge_request.png deleted file mode 100644 index e60992c4c6a..00000000000 Binary files a/doc/gitlab-basics/basicsimages/add_new_merge_request.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/add_sshkey.png b/doc/gitlab-basics/basicsimages/add_sshkey.png deleted file mode 100644 index 89c86018629..00000000000 Binary files a/doc/gitlab-basics/basicsimages/add_sshkey.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/branch_info.png b/doc/gitlab-basics/basicsimages/branch_info.png deleted file mode 100644 index 2264f3c5bf2..00000000000 Binary files a/doc/gitlab-basics/basicsimages/branch_info.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/branch_name.png b/doc/gitlab-basics/basicsimages/branch_name.png deleted file mode 100644 index 75fe8313611..00000000000 Binary files a/doc/gitlab-basics/basicsimages/branch_name.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/branches.png b/doc/gitlab-basics/basicsimages/branches.png deleted file mode 100644 index 8621bc05776..00000000000 Binary files a/doc/gitlab-basics/basicsimages/branches.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/button-create-mr.png b/doc/gitlab-basics/basicsimages/button-create-mr.png deleted file mode 100644 index b52ab148839..00000000000 Binary files a/doc/gitlab-basics/basicsimages/button-create-mr.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/click-on-new-group.png b/doc/gitlab-basics/basicsimages/click-on-new-group.png deleted file mode 100644 index 6450deec6fc..00000000000 Binary files a/doc/gitlab-basics/basicsimages/click-on-new-group.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/commit_changes.png b/doc/gitlab-basics/basicsimages/commit_changes.png deleted file mode 100644 index a88809c5a3f..00000000000 Binary files a/doc/gitlab-basics/basicsimages/commit_changes.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/commit_message.png b/doc/gitlab-basics/basicsimages/commit_message.png deleted file mode 100644 index 4abe4517f98..00000000000 Binary files a/doc/gitlab-basics/basicsimages/commit_message.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/commits.png b/doc/gitlab-basics/basicsimages/commits.png deleted file mode 100644 index 2bfcaf75f01..00000000000 Binary files a/doc/gitlab-basics/basicsimages/commits.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/compare_branches.png b/doc/gitlab-basics/basicsimages/compare_branches.png deleted file mode 100644 index 8a18453dd05..00000000000 Binary files a/doc/gitlab-basics/basicsimages/compare_branches.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/create_file.png b/doc/gitlab-basics/basicsimages/create_file.png deleted file mode 100644 index 5ebe1b227dd..00000000000 Binary files a/doc/gitlab-basics/basicsimages/create_file.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/create_group.png b/doc/gitlab-basics/basicsimages/create_group.png deleted file mode 100644 index 7ecc3baa990..00000000000 Binary files a/doc/gitlab-basics/basicsimages/create_group.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/edit_file.png b/doc/gitlab-basics/basicsimages/edit_file.png deleted file mode 100644 index 9d3e817d036..00000000000 Binary files a/doc/gitlab-basics/basicsimages/edit_file.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/file_located.png b/doc/gitlab-basics/basicsimages/file_located.png deleted file mode 100644 index e357cb5c6ab..00000000000 Binary files a/doc/gitlab-basics/basicsimages/file_located.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/file_name.png b/doc/gitlab-basics/basicsimages/file_name.png deleted file mode 100644 index 01639c77d0d..00000000000 Binary files a/doc/gitlab-basics/basicsimages/file_name.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/find_file.png b/doc/gitlab-basics/basicsimages/find_file.png deleted file mode 100644 index 6f26d26ae18..00000000000 Binary files a/doc/gitlab-basics/basicsimages/find_file.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/find_group.png b/doc/gitlab-basics/basicsimages/find_group.png deleted file mode 100644 index 1211510aae9..00000000000 Binary files a/doc/gitlab-basics/basicsimages/find_group.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/fork.png b/doc/gitlab-basics/basicsimages/fork.png deleted file mode 100644 index 13ff8345616..00000000000 Binary files a/doc/gitlab-basics/basicsimages/fork.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/group_info.png b/doc/gitlab-basics/basicsimages/group_info.png deleted file mode 100644 index 2507d6c295b..00000000000 Binary files a/doc/gitlab-basics/basicsimages/group_info.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/groups.png b/doc/gitlab-basics/basicsimages/groups.png deleted file mode 100644 index ef3dca60cc8..00000000000 Binary files a/doc/gitlab-basics/basicsimages/groups.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/https.png b/doc/gitlab-basics/basicsimages/https.png deleted file mode 100644 index e74dbc13f9a..00000000000 Binary files a/doc/gitlab-basics/basicsimages/https.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/image_file.png b/doc/gitlab-basics/basicsimages/image_file.png deleted file mode 100644 index 7f304b8e1f2..00000000000 Binary files a/doc/gitlab-basics/basicsimages/image_file.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/issue_title.png b/doc/gitlab-basics/basicsimages/issue_title.png deleted file mode 100644 index 60a6f7973be..00000000000 Binary files a/doc/gitlab-basics/basicsimages/issue_title.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/issues.png b/doc/gitlab-basics/basicsimages/issues.png deleted file mode 100644 index 14e9cdb64e1..00000000000 Binary files a/doc/gitlab-basics/basicsimages/issues.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/key.png b/doc/gitlab-basics/basicsimages/key.png deleted file mode 100644 index 04400173ce8..00000000000 Binary files a/doc/gitlab-basics/basicsimages/key.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/merge_requests.png b/doc/gitlab-basics/basicsimages/merge_requests.png deleted file mode 100644 index 570164df18b..00000000000 Binary files a/doc/gitlab-basics/basicsimages/merge_requests.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/new_issue.png b/doc/gitlab-basics/basicsimages/new_issue.png deleted file mode 100644 index 94e7503dd8b..00000000000 Binary files a/doc/gitlab-basics/basicsimages/new_issue.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/new_merge_request.png b/doc/gitlab-basics/basicsimages/new_merge_request.png deleted file mode 100644 index 842f5ebed74..00000000000 Binary files a/doc/gitlab-basics/basicsimages/new_merge_request.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/new_project.png b/doc/gitlab-basics/basicsimages/new_project.png deleted file mode 100644 index 421e8bc247b..00000000000 Binary files a/doc/gitlab-basics/basicsimages/new_project.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/newbranch.png b/doc/gitlab-basics/basicsimages/newbranch.png deleted file mode 100644 index d5fcf33c4ea..00000000000 Binary files a/doc/gitlab-basics/basicsimages/newbranch.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/paste_sshkey.png b/doc/gitlab-basics/basicsimages/paste_sshkey.png deleted file mode 100644 index 578ebee4440..00000000000 Binary files a/doc/gitlab-basics/basicsimages/paste_sshkey.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/profile_settings.png b/doc/gitlab-basics/basicsimages/profile_settings.png deleted file mode 100644 index cb3f79f1879..00000000000 Binary files a/doc/gitlab-basics/basicsimages/profile_settings.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/project_info.png b/doc/gitlab-basics/basicsimages/project_info.png deleted file mode 100644 index e1adb8d48c2..00000000000 Binary files a/doc/gitlab-basics/basicsimages/project_info.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/public_file_link.png b/doc/gitlab-basics/basicsimages/public_file_link.png deleted file mode 100644 index f60df6807f4..00000000000 Binary files a/doc/gitlab-basics/basicsimages/public_file_link.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/select-group.png b/doc/gitlab-basics/basicsimages/select-group.png deleted file mode 100644 index 33b978dd899..00000000000 Binary files a/doc/gitlab-basics/basicsimages/select-group.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/select-group2.png b/doc/gitlab-basics/basicsimages/select-group2.png deleted file mode 100644 index aee22c638db..00000000000 Binary files a/doc/gitlab-basics/basicsimages/select-group2.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/select_branch.png b/doc/gitlab-basics/basicsimages/select_branch.png deleted file mode 100644 index f72a3ffb57f..00000000000 Binary files a/doc/gitlab-basics/basicsimages/select_branch.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/select_project.png b/doc/gitlab-basics/basicsimages/select_project.png deleted file mode 100644 index 3bb832ea8d0..00000000000 Binary files a/doc/gitlab-basics/basicsimages/select_project.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/settings.png b/doc/gitlab-basics/basicsimages/settings.png deleted file mode 100644 index 78637013d9b..00000000000 Binary files a/doc/gitlab-basics/basicsimages/settings.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/shh_keys.png b/doc/gitlab-basics/basicsimages/shh_keys.png deleted file mode 100644 index c87f11a9d3d..00000000000 Binary files a/doc/gitlab-basics/basicsimages/shh_keys.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/submit_new_issue.png b/doc/gitlab-basics/basicsimages/submit_new_issue.png deleted file mode 100644 index 78b854c8903..00000000000 Binary files a/doc/gitlab-basics/basicsimages/submit_new_issue.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/title_description_mr.png b/doc/gitlab-basics/basicsimages/title_description_mr.png deleted file mode 100644 index c31d61ec336..00000000000 Binary files a/doc/gitlab-basics/basicsimages/title_description_mr.png and /dev/null differ diff --git a/doc/gitlab-basics/basicsimages/white_space.png b/doc/gitlab-basics/basicsimages/white_space.png deleted file mode 100644 index eaa969bdcf4..00000000000 Binary files a/doc/gitlab-basics/basicsimages/white_space.png and /dev/null differ diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md index addd3b6b6eb..253edc8a7e8 100644 --- a/doc/gitlab-basics/command-line-commands.md +++ b/doc/gitlab-basics/command-line-commands.md @@ -6,11 +6,11 @@ In Git, when you copy a project you say you "clone" it. To work on a git project When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen. -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) To work in the project, you can copy a link to the Git repository through a SSH or a HTTPS protocol. SSH is easier to use after it's been [setup](create-your-ssh-keys.md). When you're in the project, click on the HTTPS or SSH button at the right side of your screen. Then copy the link (you'll have to paste it on your shell in the next step). -![Copy the HTTPS or SSH](basicsimages/https.png) +![Copy the HTTPS or SSH](img/https.png) ## On the command line diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md index 7556b0f663e..8f078c7692f 100644 --- a/doc/gitlab-basics/create-branch.md +++ b/doc/gitlab-basics/create-branch.md @@ -8,19 +8,19 @@ To add changes to your GitLab project, you should create a branch. You can do it To create a new branch in GitLab, sign in and then select a project on the right side of your screen: -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) Click on "commits" on the menu on the left side of your screen: -![Commits](basicsimages/commits.png) +![Commits](img/commits.png) Click on the "branches" tab: -![Branches](basicsimages/branches.png) +![Branches](img/branches.png) Click on the "new branch" button on the right side of the screen: -![New branch](basicsimages/newbranch.png) +![New branch](img/newbranch.png) Fill out the information required: @@ -30,10 +30,10 @@ Fill out the information required: 1. Click on the button "create branch" -![Branch info](basicsimages/branch_info.png) +![Branch info](img/branch_info.png) ### Note: You will be able to find and select the name of your branch in the white box next to a project's name: -![Branch name](basicsimages/branch_name.png) +![Branch name](img/branch_name.png) diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md index f80ae62e442..05497279959 100644 --- a/doc/gitlab-basics/create-group.md +++ b/doc/gitlab-basics/create-group.md @@ -12,11 +12,11 @@ Sign in to [GitLab.com](https://gitlab.com). When you are on your Dashboard, click on "Groups" on the left menu of your screen: -![Go to groups](basicsimages/select-group2.png) +![Go to groups](img/select-group2.png) Click on "New group" on the top right side of your screen: -![New group](basicsimages/click-on-new-group.png) +![New group](img/click-on-new-group.png) Fill out the information required: @@ -28,7 +28,7 @@ Fill out the information required: 1. Click on "create group" -![Group information](basicsimages/group_info.png) +![Group information](img/group_info.png) ## Add a project to a group @@ -36,8 +36,8 @@ There are 2 different ways to add a new project to a group: * Select a group and then click on "New project" on the right side of your screen. Then you can [create a project](create-project.md) -![New project](basicsimages/new_project.png) +![New project](img/new_project.png) * When you are [creating a project](create-project.md), click on "create a group" on the bottom right side of your screen -![Create a group](basicsimages/create_group.png) +![Create a group](img/create_group.png) diff --git a/doc/gitlab-basics/create-issue.md b/doc/gitlab-basics/create-issue.md index da9a165b8f5..e11c86cafa0 100644 --- a/doc/gitlab-basics/create-issue.md +++ b/doc/gitlab-basics/create-issue.md @@ -6,22 +6,22 @@ To create an Issue, sign in to GitLab. Go to the project where you'd like to create the Issue: -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) Click on "Issues" on the left side of your screen: -![Issues](basicsimages/issues.png) +![Issues](img/issues.png) Click on the "+ new issue" button on the right side of your screen: -![New issue](basicsimages/new_issue.png) +![New issue](img/new_issue.png) Add a title and a description to your issue: -![Issue title and description](basicsimages/issue_title.png) +![Issue title and description](img/issue_title.png) You may assign the Issue to a user, add a milestone and add labels (they are all optional). Then click on "submit new issue": -![Submit new issue](basicsimages/submit_new_issue.png) +![Submit new issue](img/submit_new_issue.png) Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](../user/project/issues/automatic_issue_closing.md). diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md index f737dffc024..c67e5908189 100644 --- a/doc/gitlab-basics/create-project.md +++ b/doc/gitlab-basics/create-project.md @@ -4,7 +4,7 @@ To create a new project, sign in to GitLab. Go to your Dashboard and click on "new project" on the right side of your screen. -![Create a project](basicsimages/new_project.png) +![Create a project](img/new_project.png) Fill out the required information: @@ -18,4 +18,4 @@ Fill out the required information: 1. Click on "create project" -!![Project information](basicsimages/project_info.png) +!![Project information](img/project_info.png) diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md index f31c353f2cf..50ac9c36988 100644 --- a/doc/gitlab-basics/create-your-ssh-keys.md +++ b/doc/gitlab-basics/create-your-ssh-keys.md @@ -12,15 +12,15 @@ After you confirm, go to GitLab and sign in to your account. On the left side menu, click on "profile settings" and then click on "SSH Keys": -![SSH Keys](basicsimages/shh_keys.png) +![SSH Keys](img/shh_keys.png) Then click on the green button "Add SSH Key": -![Add SSH Key](basicsimages/add_sshkey.png) +![Add SSH Key](img/add_sshkey.png) There, you should paste the SSH Key that your command line will generate for you. Below you'll find the steps to generate it: -![Paste SSH Key](basicsimages/paste_sshkey.png) +![Paste SSH Key](img/paste_sshkey.png) ## To generate an SSH Key on your command line @@ -28,6 +28,6 @@ Go to your [command line](start-using-git.md) and follow the [instructions](../s Copy the SSH Key that your command line created and paste it on the "Key" box on the GitLab page. The title will be added automatically. -![Paste SSH Key](basicsimages/key.png) +![Paste SSH Key](img/key.png) Now, you'll be able to use Git over SSH, instead of Git over HTTP. diff --git a/doc/gitlab-basics/fork-project.md b/doc/gitlab-basics/fork-project.md index 5f8b81ea919..7232fc2c1ae 100644 --- a/doc/gitlab-basics/fork-project.md +++ b/doc/gitlab-basics/fork-project.md @@ -10,10 +10,10 @@ Sign in to GitLab. Select a project on the right side of your screen: -![Select a project](basicsimages/select_project.png) +![Select a project](img/select_project.png) Click on the "fork" button on the right side of your screen: -![Fork](basicsimages/fork.png) +![Fork](img/fork.png) Click on the user or group to where you'd like to add the forked project. diff --git a/doc/gitlab-basics/img/add_new_merge_request.png b/doc/gitlab-basics/img/add_new_merge_request.png new file mode 100644 index 00000000000..e60992c4c6a Binary files /dev/null and b/doc/gitlab-basics/img/add_new_merge_request.png differ diff --git a/doc/gitlab-basics/img/add_sshkey.png b/doc/gitlab-basics/img/add_sshkey.png new file mode 100644 index 00000000000..89c86018629 Binary files /dev/null and b/doc/gitlab-basics/img/add_sshkey.png differ diff --git a/doc/gitlab-basics/img/branch_info.png b/doc/gitlab-basics/img/branch_info.png new file mode 100644 index 00000000000..2264f3c5bf2 Binary files /dev/null and b/doc/gitlab-basics/img/branch_info.png differ diff --git a/doc/gitlab-basics/img/branch_name.png b/doc/gitlab-basics/img/branch_name.png new file mode 100644 index 00000000000..75fe8313611 Binary files /dev/null and b/doc/gitlab-basics/img/branch_name.png differ diff --git a/doc/gitlab-basics/img/branches.png b/doc/gitlab-basics/img/branches.png new file mode 100644 index 00000000000..8621bc05776 Binary files /dev/null and b/doc/gitlab-basics/img/branches.png differ diff --git a/doc/gitlab-basics/img/button-create-mr.png b/doc/gitlab-basics/img/button-create-mr.png new file mode 100644 index 00000000000..b52ab148839 Binary files /dev/null and b/doc/gitlab-basics/img/button-create-mr.png differ diff --git a/doc/gitlab-basics/img/click-on-new-group.png b/doc/gitlab-basics/img/click-on-new-group.png new file mode 100644 index 00000000000..6450deec6fc Binary files /dev/null and b/doc/gitlab-basics/img/click-on-new-group.png differ diff --git a/doc/gitlab-basics/img/commit_changes.png b/doc/gitlab-basics/img/commit_changes.png new file mode 100644 index 00000000000..a88809c5a3f Binary files /dev/null and b/doc/gitlab-basics/img/commit_changes.png differ diff --git a/doc/gitlab-basics/img/commit_message.png b/doc/gitlab-basics/img/commit_message.png new file mode 100644 index 00000000000..4abe4517f98 Binary files /dev/null and b/doc/gitlab-basics/img/commit_message.png differ diff --git a/doc/gitlab-basics/img/commits.png b/doc/gitlab-basics/img/commits.png new file mode 100644 index 00000000000..2bfcaf75f01 Binary files /dev/null and b/doc/gitlab-basics/img/commits.png differ diff --git a/doc/gitlab-basics/img/compare_branches.png b/doc/gitlab-basics/img/compare_branches.png new file mode 100644 index 00000000000..8a18453dd05 Binary files /dev/null and b/doc/gitlab-basics/img/compare_branches.png differ diff --git a/doc/gitlab-basics/img/create_file.png b/doc/gitlab-basics/img/create_file.png new file mode 100644 index 00000000000..5ebe1b227dd Binary files /dev/null and b/doc/gitlab-basics/img/create_file.png differ diff --git a/doc/gitlab-basics/img/create_group.png b/doc/gitlab-basics/img/create_group.png new file mode 100644 index 00000000000..7ecc3baa990 Binary files /dev/null and b/doc/gitlab-basics/img/create_group.png differ diff --git a/doc/gitlab-basics/img/edit_file.png b/doc/gitlab-basics/img/edit_file.png new file mode 100644 index 00000000000..9d3e817d036 Binary files /dev/null and b/doc/gitlab-basics/img/edit_file.png differ diff --git a/doc/gitlab-basics/img/file_located.png b/doc/gitlab-basics/img/file_located.png new file mode 100644 index 00000000000..e357cb5c6ab Binary files /dev/null and b/doc/gitlab-basics/img/file_located.png differ diff --git a/doc/gitlab-basics/img/file_name.png b/doc/gitlab-basics/img/file_name.png new file mode 100644 index 00000000000..01639c77d0d Binary files /dev/null and b/doc/gitlab-basics/img/file_name.png differ diff --git a/doc/gitlab-basics/img/find_file.png b/doc/gitlab-basics/img/find_file.png new file mode 100644 index 00000000000..6f26d26ae18 Binary files /dev/null and b/doc/gitlab-basics/img/find_file.png differ diff --git a/doc/gitlab-basics/img/find_group.png b/doc/gitlab-basics/img/find_group.png new file mode 100644 index 00000000000..1211510aae9 Binary files /dev/null and b/doc/gitlab-basics/img/find_group.png differ diff --git a/doc/gitlab-basics/img/fork.png b/doc/gitlab-basics/img/fork.png new file mode 100644 index 00000000000..13ff8345616 Binary files /dev/null and b/doc/gitlab-basics/img/fork.png differ diff --git a/doc/gitlab-basics/img/group_info.png b/doc/gitlab-basics/img/group_info.png new file mode 100644 index 00000000000..2507d6c295b Binary files /dev/null and b/doc/gitlab-basics/img/group_info.png differ diff --git a/doc/gitlab-basics/img/groups.png b/doc/gitlab-basics/img/groups.png new file mode 100644 index 00000000000..ef3dca60cc8 Binary files /dev/null and b/doc/gitlab-basics/img/groups.png differ diff --git a/doc/gitlab-basics/img/https.png b/doc/gitlab-basics/img/https.png new file mode 100644 index 00000000000..e74dbc13f9a Binary files /dev/null and b/doc/gitlab-basics/img/https.png differ diff --git a/doc/gitlab-basics/img/image_file.png b/doc/gitlab-basics/img/image_file.png new file mode 100644 index 00000000000..7f304b8e1f2 Binary files /dev/null and b/doc/gitlab-basics/img/image_file.png differ diff --git a/doc/gitlab-basics/img/issue_title.png b/doc/gitlab-basics/img/issue_title.png new file mode 100644 index 00000000000..60a6f7973be Binary files /dev/null and b/doc/gitlab-basics/img/issue_title.png differ diff --git a/doc/gitlab-basics/img/issues.png b/doc/gitlab-basics/img/issues.png new file mode 100644 index 00000000000..14e9cdb64e1 Binary files /dev/null and b/doc/gitlab-basics/img/issues.png differ diff --git a/doc/gitlab-basics/img/key.png b/doc/gitlab-basics/img/key.png new file mode 100644 index 00000000000..04400173ce8 Binary files /dev/null and b/doc/gitlab-basics/img/key.png differ diff --git a/doc/gitlab-basics/img/merge_requests.png b/doc/gitlab-basics/img/merge_requests.png new file mode 100644 index 00000000000..570164df18b Binary files /dev/null and b/doc/gitlab-basics/img/merge_requests.png differ diff --git a/doc/gitlab-basics/img/new_issue.png b/doc/gitlab-basics/img/new_issue.png new file mode 100644 index 00000000000..94e7503dd8b Binary files /dev/null and b/doc/gitlab-basics/img/new_issue.png differ diff --git a/doc/gitlab-basics/img/new_merge_request.png b/doc/gitlab-basics/img/new_merge_request.png new file mode 100644 index 00000000000..842f5ebed74 Binary files /dev/null and b/doc/gitlab-basics/img/new_merge_request.png differ diff --git a/doc/gitlab-basics/img/new_project.png b/doc/gitlab-basics/img/new_project.png new file mode 100644 index 00000000000..421e8bc247b Binary files /dev/null and b/doc/gitlab-basics/img/new_project.png differ diff --git a/doc/gitlab-basics/img/newbranch.png b/doc/gitlab-basics/img/newbranch.png new file mode 100644 index 00000000000..d5fcf33c4ea Binary files /dev/null and b/doc/gitlab-basics/img/newbranch.png differ diff --git a/doc/gitlab-basics/img/paste_sshkey.png b/doc/gitlab-basics/img/paste_sshkey.png new file mode 100644 index 00000000000..578ebee4440 Binary files /dev/null and b/doc/gitlab-basics/img/paste_sshkey.png differ diff --git a/doc/gitlab-basics/img/profile_settings.png b/doc/gitlab-basics/img/profile_settings.png new file mode 100644 index 00000000000..cb3f79f1879 Binary files /dev/null and b/doc/gitlab-basics/img/profile_settings.png differ diff --git a/doc/gitlab-basics/img/project_info.png b/doc/gitlab-basics/img/project_info.png new file mode 100644 index 00000000000..e1adb8d48c2 Binary files /dev/null and b/doc/gitlab-basics/img/project_info.png differ diff --git a/doc/gitlab-basics/img/public_file_link.png b/doc/gitlab-basics/img/public_file_link.png new file mode 100644 index 00000000000..f60df6807f4 Binary files /dev/null and b/doc/gitlab-basics/img/public_file_link.png differ diff --git a/doc/gitlab-basics/img/select-group.png b/doc/gitlab-basics/img/select-group.png new file mode 100644 index 00000000000..33b978dd899 Binary files /dev/null and b/doc/gitlab-basics/img/select-group.png differ diff --git a/doc/gitlab-basics/img/select-group2.png b/doc/gitlab-basics/img/select-group2.png new file mode 100644 index 00000000000..aee22c638db Binary files /dev/null and b/doc/gitlab-basics/img/select-group2.png differ diff --git a/doc/gitlab-basics/img/select_branch.png b/doc/gitlab-basics/img/select_branch.png new file mode 100644 index 00000000000..f72a3ffb57f Binary files /dev/null and b/doc/gitlab-basics/img/select_branch.png differ diff --git a/doc/gitlab-basics/img/select_project.png b/doc/gitlab-basics/img/select_project.png new file mode 100644 index 00000000000..3bb832ea8d0 Binary files /dev/null and b/doc/gitlab-basics/img/select_project.png differ diff --git a/doc/gitlab-basics/img/settings.png b/doc/gitlab-basics/img/settings.png new file mode 100644 index 00000000000..78637013d9b Binary files /dev/null and b/doc/gitlab-basics/img/settings.png differ diff --git a/doc/gitlab-basics/img/shh_keys.png b/doc/gitlab-basics/img/shh_keys.png new file mode 100644 index 00000000000..c87f11a9d3d Binary files /dev/null and b/doc/gitlab-basics/img/shh_keys.png differ diff --git a/doc/gitlab-basics/img/submit_new_issue.png b/doc/gitlab-basics/img/submit_new_issue.png new file mode 100644 index 00000000000..78b854c8903 Binary files /dev/null and b/doc/gitlab-basics/img/submit_new_issue.png differ diff --git a/doc/gitlab-basics/img/title_description_mr.png b/doc/gitlab-basics/img/title_description_mr.png new file mode 100644 index 00000000000..c31d61ec336 Binary files /dev/null and b/doc/gitlab-basics/img/title_description_mr.png differ diff --git a/doc/gitlab-basics/img/white_space.png b/doc/gitlab-basics/img/white_space.png new file mode 100644 index 00000000000..eaa969bdcf4 Binary files /dev/null and b/doc/gitlab-basics/img/white_space.png differ -- cgit v1.2.1 From d4fab17d7c8c2b233248295755a6277fdee09c9f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 7 Oct 2016 22:48:23 -0700 Subject: Fix Error 500 when viewing old merge requests with bad diff data Customers running old versions of GitLab may have MergeRequestDiffs with the text ["--broken diff"] due to text generated by gitlab_git 1.0.3. To avoid the Error 500, verify that each element is a type that gitlab_git will accept before attempting to create a DiffCollection. Closes #20776 --- CHANGELOG | 1 + app/models/merge_request_diff.rb | 14 +++++++++++++- spec/models/merge_request_diff_spec.rb | 10 ++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 24f77442f1a..dfb953fabd9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 8.13.0 (unreleased) - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) + - Fix Error 500 when viewing old merge requests with bad diff data - Speed-up group milestones show page - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - Add tag shortcut from the Commit page. !6543 diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 36b8b70870b..3f7e96186a1 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -6,6 +6,9 @@ class MergeRequestDiff < ActiveRecord::Base # Prevent store of diff if commits amount more then 500 COMMITS_SAFE_SIZE = 100 + # Valid types of serialized diffs allowed by Gitlab::Git::Diff + VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta] + belongs_to :merge_request state_machine :state, initial: :empty do @@ -170,6 +173,15 @@ class MergeRequestDiff < ActiveRecord::Base private + # Old GitLab implementations may have generated diffs as ["--broken-diff"]. + # Avoid an error 500 by ignoring bad elements. See: + # https://gitlab.com/gitlab-org/gitlab-ce/issues/20776 + def valid_raw_diff?(raw) + return false unless raw.respond_to?(:each) + + raw.any? { |element| VALID_CLASSES.include?(element.class) } + end + def dump_commits(commits) commits.map(&:to_hash) end @@ -200,7 +212,7 @@ class MergeRequestDiff < ActiveRecord::Base end def load_diffs(raw, options) - if raw.respond_to?(:each) + if valid_raw_diff?(raw) if paths = options[:paths] raw = raw.select do |diff| paths.include?(diff[:old_path]) || paths.include?(diff[:new_path]) diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 530a7def553..96f1f60dbc0 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -44,6 +44,16 @@ describe MergeRequestDiff, models: true do end end + context 'when the raw diffs have invalid content' do + before { mr_diff.update_attributes(st_diffs: ["--broken-diff"]) } + + it 'returns an empty DiffCollection' do + expect(mr_diff.raw_diffs.to_a).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.raw_diffs).to be_a(Gitlab::Git::DiffCollection) -- cgit v1.2.1 From f8df2bc61f205da36b50961928509ca087e7b290 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 10 Oct 2016 22:07:17 +0200 Subject: Refactor SSH key addition in GitLab basics --- doc/gitlab-basics/create-your-ssh-keys.md | 38 ++++++++++++--------- doc/gitlab-basics/img/add_sshkey.png | Bin 1394 -> 0 bytes doc/gitlab-basics/img/key.png | Bin 1177 -> 0 bytes doc/gitlab-basics/img/paste_sshkey.png | Bin 7699 -> 0 bytes doc/gitlab-basics/img/profile_settings.png | Bin 1101 -> 5975 bytes .../img/profile_settings_ssh_keys.png | Bin 0 -> 42977 bytes .../img/profile_settings_ssh_keys_paste_pub.png | Bin 0 -> 37486 bytes .../img/profile_settings_ssh_keys_single_key.png | Bin 0 -> 18498 bytes .../img/profile_settings_ssh_keys_title.png | Bin 0 -> 2362 bytes doc/gitlab-basics/img/shh_keys.png | Bin 4782 -> 0 bytes 10 files changed, 21 insertions(+), 17 deletions(-) delete mode 100644 doc/gitlab-basics/img/add_sshkey.png delete mode 100644 doc/gitlab-basics/img/key.png delete mode 100644 doc/gitlab-basics/img/paste_sshkey.png create mode 100644 doc/gitlab-basics/img/profile_settings_ssh_keys.png create mode 100644 doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png create mode 100644 doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png create mode 100644 doc/gitlab-basics/img/profile_settings_ssh_keys_title.png delete mode 100644 doc/gitlab-basics/img/shh_keys.png diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md index 50ac9c36988..b6ebe374de3 100644 --- a/doc/gitlab-basics/create-your-ssh-keys.md +++ b/doc/gitlab-basics/create-your-ssh-keys.md @@ -1,33 +1,37 @@ # How to create your SSH Keys -You need to connect your computer to your GitLab account through SSH Keys. They are unique for every computer that you link your GitLab account with. +1. The first thing you need to do is go to your [command line](start-using-git.md) + and follow the [instructions](../ssh/README.md) to generate your SSH key pair. -## Generate your SSH Key +1. Once you do that, login to GitLab with your credentials. +1. On the upper right corner, click on your avatar and go to your **Profile settings**. -Create an account on GitLab. Sign up and check your email for your confirmation link. + ![Profile settings dropdown](img/profile_settings.png) -After you confirm, go to GitLab and sign in to your account. +1. Navigate to the **SSH keys** tab. -## Add your SSH Key + ![SSH Keys](img/profile_settings_ssh_keys.png) -On the left side menu, click on "profile settings" and then click on "SSH Keys": +3. Paste your **public** key that you generated in the first step in the 'Key' + box. -![SSH Keys](img/shh_keys.png) + ![Paste SSH public key](img/profile_settings_ssh_keys_paste_pub.png) -Then click on the green button "Add SSH Key": +1. Optionally, give it a descriptive title so that you can recognize it in the + event you add multiple keys. -![Add SSH Key](img/add_sshkey.png) + ![SSH key title](img/profile_settings_ssh_keys_title.png) -There, you should paste the SSH Key that your command line will generate for you. Below you'll find the steps to generate it: +1. Finally, click on **Add key** to add it to GitLab. You will be able to see + its fingerprint, its title and creation date. -![Paste SSH Key](img/paste_sshkey.png) + ![SSH key single page](img/profile_settings_ssh_keys_single_key.png) -## To generate an SSH Key on your command line -Go to your [command line](start-using-git.md) and follow the [instructions](../ssh/README.md) to generate it. +>**Note:** +Once you add a key, you cannot edit it, only remove it. In case the paste +didn't work, you will have to remove the offending key and re-add it. -Copy the SSH Key that your command line created and paste it on the "Key" box on the GitLab page. The title will be added automatically. +--- -![Paste SSH Key](img/key.png) - -Now, you'll be able to use Git over SSH, instead of Git over HTTP. +Congratulations! You are now ready to use Git over SSH, instead of Git over HTTP! diff --git a/doc/gitlab-basics/img/add_sshkey.png b/doc/gitlab-basics/img/add_sshkey.png deleted file mode 100644 index 89c86018629..00000000000 Binary files a/doc/gitlab-basics/img/add_sshkey.png and /dev/null differ diff --git a/doc/gitlab-basics/img/key.png b/doc/gitlab-basics/img/key.png deleted file mode 100644 index 04400173ce8..00000000000 Binary files a/doc/gitlab-basics/img/key.png and /dev/null differ diff --git a/doc/gitlab-basics/img/paste_sshkey.png b/doc/gitlab-basics/img/paste_sshkey.png deleted file mode 100644 index 578ebee4440..00000000000 Binary files a/doc/gitlab-basics/img/paste_sshkey.png and /dev/null differ diff --git a/doc/gitlab-basics/img/profile_settings.png b/doc/gitlab-basics/img/profile_settings.png index cb3f79f1879..f0abd478849 100644 Binary files a/doc/gitlab-basics/img/profile_settings.png and b/doc/gitlab-basics/img/profile_settings.png differ diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys.png b/doc/gitlab-basics/img/profile_settings_ssh_keys.png new file mode 100644 index 00000000000..2c9a42fe10c Binary files /dev/null and b/doc/gitlab-basics/img/profile_settings_ssh_keys.png differ diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png new file mode 100644 index 00000000000..cd7add6937f Binary files /dev/null and b/doc/gitlab-basics/img/profile_settings_ssh_keys_paste_pub.png differ diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png new file mode 100644 index 00000000000..095beb02be8 Binary files /dev/null and b/doc/gitlab-basics/img/profile_settings_ssh_keys_single_key.png differ diff --git a/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png b/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png new file mode 100644 index 00000000000..4b998a7f948 Binary files /dev/null and b/doc/gitlab-basics/img/profile_settings_ssh_keys_title.png differ diff --git a/doc/gitlab-basics/img/shh_keys.png b/doc/gitlab-basics/img/shh_keys.png deleted file mode 100644 index c87f11a9d3d..00000000000 Binary files a/doc/gitlab-basics/img/shh_keys.png and /dev/null differ -- cgit v1.2.1 From 6de2990c998169e165c0bc9b0a7e0ab9099048e4 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Mon, 10 Oct 2016 22:18:06 +0200 Subject: Use new image for gitlab-basics/command-line-commands.md [ci skip] --- doc/gitlab-basics/README.md | 2 +- doc/gitlab-basics/command-line-commands.md | 26 ++++++++++++++++++++------ doc/gitlab-basics/img/https.png | Bin 2822 -> 0 bytes doc/gitlab-basics/img/project_clone_url.png | Bin 0 -> 40490 bytes 4 files changed, 21 insertions(+), 7 deletions(-) delete mode 100644 doc/gitlab-basics/img/https.png create mode 100644 doc/gitlab-basics/img/project_clone_url.png diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md index 3aa83975ace..38843f36b2e 100644 --- a/doc/gitlab-basics/README.md +++ b/doc/gitlab-basics/README.md @@ -2,9 +2,9 @@ Step-by-step guides on the basics of working with Git and GitLab. +- [Command Line basics](command-line-commands.md) - [Start using Git on the command line](start-using-git.md) - [Create and add your SSH Keys](create-your-ssh-keys.md) -- [Command Line basics](command-line-commands.md) - [Create a project](create-project.md) - [Create a group](create-group.md) - [Create a branch](create-branch.md) diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md index 253edc8a7e8..3b075ff5fc0 100644 --- a/doc/gitlab-basics/command-line-commands.md +++ b/doc/gitlab-basics/command-line-commands.md @@ -4,18 +4,21 @@ In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to GitLab. -When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen. +When you are on your Dashboard, click on the project that you'd like to clone. +To work in the project, you can copy a link to the Git repository through a SSH +or a HTTPS protocol. SSH is easier to use after it's been +[setup](create-your-ssh-keys.md). While you are at the **Project** tab, select +HTTPS or SSH from the dropdown menu and copy the link using the 'Copy to clipboard' +button (you'll have to paste it on your shell in the next step). -![Select a project](img/select_project.png) - -To work in the project, you can copy a link to the Git repository through a SSH or a HTTPS protocol. SSH is easier to use after it's been [setup](create-your-ssh-keys.md). When you're in the project, click on the HTTPS or SSH button at the right side of your screen. Then copy the link (you'll have to paste it on your shell in the next step). - -![Copy the HTTPS or SSH](img/https.png) +![Copy the HTTPS or SSH](img/project_clone_url.png) ## On the command line ### Clone your project + Go to your computer's shell and type the following command: + ``` git clone PASTE HTTPS OR SSH HERE ``` @@ -23,26 +26,31 @@ git clone PASTE HTTPS OR SSH HERE A clone of the project will be created in your computer. ### Go into a project, directory or file to work in it + ``` cd NAME-OF-PROJECT-OR-FILE ``` ### Go back one directory or file + ``` cd ../ ``` ### View what’s in the directory that you are in + ``` ls ``` ### Create a directory + ``` mkdir NAME-OF-YOUR-DIRECTORY ``` ### Create a README.md or file in directory + ``` touch README.md nano README.md @@ -53,27 +61,33 @@ nano README.md ``` ### Remove a file + ``` rm NAME-OF-FILE ``` ### Remove a directory and all of its contents + ``` rm -rf NAME-OF-DIRECTORY ``` ### View history in the command line + ``` history ``` ### Carry out commands for which the account you are using lacks authority + You will be asked for an administrator’s password. + ``` sudo ``` ### Tell where you are + ``` pwd ``` diff --git a/doc/gitlab-basics/img/https.png b/doc/gitlab-basics/img/https.png deleted file mode 100644 index e74dbc13f9a..00000000000 Binary files a/doc/gitlab-basics/img/https.png and /dev/null differ diff --git a/doc/gitlab-basics/img/project_clone_url.png b/doc/gitlab-basics/img/project_clone_url.png new file mode 100644 index 00000000000..eed430e1036 Binary files /dev/null and b/doc/gitlab-basics/img/project_clone_url.png differ -- cgit v1.2.1 From ac93758a25e307573dcf79a76318f5b45aec5d07 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 10 Oct 2016 16:52:48 -0700 Subject: Bump mail_room to v0.8.1 to fix thread cleanup issue Closes #20273 --- CHANGELOG | 1 + Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 24f77442f1a..49804b10c0b 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.13.0 (unreleased) - Truncate long labels with ellipsis in labels page + - Bump mail_room to v0.8.1 to fix thread cleanup issue - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Improve issue load time performance by avoiding ORDER BY in find_by call diff --git a/Gemfile.lock b/Gemfile.lock index b98c3acf948..ccab330993a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -399,7 +399,7 @@ GEM systemu (~> 2.6.2) mail (2.6.4) mime-types (>= 1.16, < 4) - mail_room (0.8.0) + mail_room (0.8.1) method_source (0.8.2) mime-types (2.99.3) mimemagic (0.3.0) -- cgit v1.2.1 From f19766a3c2223b05e1f6de1746a44e7173387988 Mon Sep 17 00:00:00 2001 From: Mitchell Hentges Date: Thu, 22 Sep 2016 11:15:00 -0700 Subject: Ensure that whitespace doesn't case adding members to fail --- CHANGELOG | 1 + app/assets/javascripts/users_select.js | 5 +++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dc06303aecf..d94305a66d1 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.13.0 (unreleased) - Truncate long labels with ellipsis in labels page + - Adding members no longer silently fails when there is extra whitespace - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Improve issue load time performance by avoiding ORDER BY in find_by call diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index bcabda3ceb2..d966277a8b2 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -261,10 +261,11 @@ } } if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) { + var trimmed = query.term.trim(); emailUser = { name: "Invite \"" + query.term + "\"", - username: query.term, - id: query.term + username: trimmed, + id: trimmed }; data.results.unshift(emailUser); } -- cgit v1.2.1 From ef696f592fd1eed038c789379837554237ff5974 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Tue, 11 Oct 2016 02:58:26 +0100 Subject: Add `robots.txt` to the list of reserved namespaces --- app/validators/namespace_validator.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb index 4dc3b2ab9a0..2821ecf0a88 100644 --- a/app/validators/namespace_validator.rb +++ b/app/validators/namespace_validator.rb @@ -24,6 +24,7 @@ class NamespaceValidator < ActiveModel::EachValidator projects public repository + robots.txt s search services -- cgit v1.2.1 From c003c894223f76635783b0c878006bff5800f2fe Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 06:41:40 +0200 Subject: Add new images for GitLab basics "create project" --- doc/gitlab-basics/create-project.md | 27 ++++++++++++--------- .../img/create_new_project_button.png | Bin 0 -> 10050 bytes doc/gitlab-basics/img/create_new_project_info.png | Bin 0 -> 49451 bytes doc/gitlab-basics/img/new_project.png | Bin 2234 -> 0 bytes doc/gitlab-basics/img/project_info.png | Bin 21041 -> 0 bytes 5 files changed, 15 insertions(+), 12 deletions(-) create mode 100644 doc/gitlab-basics/img/create_new_project_button.png create mode 100644 doc/gitlab-basics/img/create_new_project_info.png delete mode 100644 doc/gitlab-basics/img/new_project.png delete mode 100644 doc/gitlab-basics/img/project_info.png diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md index c67e5908189..3f45a631b3a 100644 --- a/doc/gitlab-basics/create-project.md +++ b/doc/gitlab-basics/create-project.md @@ -1,21 +1,24 @@ # How to create a project in GitLab -To create a new project, sign in to GitLab. +There are two ways to create a new project in GitLab. -Go to your Dashboard and click on "new project" on the right side of your screen. +1. While in your dashboard, you can create a new project using the **New project** + green button or you can use the cross icon in the upper right corner next to + your avatar which is always visible. -![Create a project](img/new_project.png) + ![Create a project](img/create_new_project_button.png) -Fill out the required information: +1. From there you can see several options. -1. Project path or the name of your project (you can't add spaces, so you can use hyphens or underscores) + ![Project information](img/create_new_project_info.png) -1. Your project's description +1. Fill out the information: -1. Select a [visibility level](https://gitlab.com/help/public_access/public_access) + 1. "Project name" is the name of your project (you can't use spaces, but you + can use hyphens or underscores). + 1. The "Project description" is optional and will be shown in your project's + dashboard so others can briefly understand what your project is about. + 1. Select a [visibility level](../public_access/public_access.md). + 1. You can also [import your existing projects](../workflow/importing/README.md). -1. You can also [import your existing projects](http://docs.gitlab.com/ce/workflow/importing/README.html) - -1. Click on "create project" - -!![Project information](img/project_info.png) +1. Finally, click **Create project**. diff --git a/doc/gitlab-basics/img/create_new_project_button.png b/doc/gitlab-basics/img/create_new_project_button.png new file mode 100644 index 00000000000..e7c794d943f Binary files /dev/null and b/doc/gitlab-basics/img/create_new_project_button.png differ diff --git a/doc/gitlab-basics/img/create_new_project_info.png b/doc/gitlab-basics/img/create_new_project_info.png new file mode 100644 index 00000000000..16d56f0707f Binary files /dev/null and b/doc/gitlab-basics/img/create_new_project_info.png differ diff --git a/doc/gitlab-basics/img/new_project.png b/doc/gitlab-basics/img/new_project.png deleted file mode 100644 index 421e8bc247b..00000000000 Binary files a/doc/gitlab-basics/img/new_project.png and /dev/null differ diff --git a/doc/gitlab-basics/img/project_info.png b/doc/gitlab-basics/img/project_info.png deleted file mode 100644 index e1adb8d48c2..00000000000 Binary files a/doc/gitlab-basics/img/project_info.png and /dev/null differ -- cgit v1.2.1 From ec102fe8f1cce1a5b971807232550e3b1e0f9ae6 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 07:11:55 +0200 Subject: New images for GitLab basics "Create group" docs --- doc/gitlab-basics/create-group.md | 51 +++++++++++---------- doc/gitlab-basics/img/click-on-new-group.png | Bin 1957 -> 0 bytes doc/gitlab-basics/img/create_group.png | Bin 3184 -> 0 bytes doc/gitlab-basics/img/create_new_group_info.png | Bin 0 -> 53103 bytes doc/gitlab-basics/img/create_new_group_sidebar.png | Bin 0 -> 5396 bytes .../img/create_new_project_from_group.png | Bin 0 -> 6545 bytes doc/gitlab-basics/img/group_info.png | Bin 15479 -> 0 bytes doc/gitlab-basics/img/select-group2.png | Bin 5040 -> 0 bytes doc/gitlab-basics/img/select_group_dropdown.png | Bin 0 -> 8038 bytes 9 files changed, 28 insertions(+), 23 deletions(-) delete mode 100644 doc/gitlab-basics/img/click-on-new-group.png delete mode 100644 doc/gitlab-basics/img/create_group.png create mode 100644 doc/gitlab-basics/img/create_new_group_info.png create mode 100644 doc/gitlab-basics/img/create_new_group_sidebar.png create mode 100644 doc/gitlab-basics/img/create_new_project_from_group.png delete mode 100644 doc/gitlab-basics/img/group_info.png delete mode 100644 doc/gitlab-basics/img/select-group2.png create mode 100644 doc/gitlab-basics/img/select_group_dropdown.png diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md index 05497279959..64274ccd5eb 100644 --- a/doc/gitlab-basics/create-group.md +++ b/doc/gitlab-basics/create-group.md @@ -1,43 +1,48 @@ # How to create a group in GitLab -## Create a group - Your projects in GitLab can be organized in 2 different ways: -under your own namespace for single projects, such as ´your-name/project-1'; or under groups. -If you organize your projects under a group, it works like a folder. You can manage your group members' permissions and access to the projects. - -To create a group, follow the instructions below: +under your own namespace for single projects, such as `your-name/project-1` or +under groups. -Sign in to [GitLab.com](https://gitlab.com). +If you organize your projects under a group, it works like a folder. You can +manage your group members' permissions and access to the projects. -When you are on your Dashboard, click on "Groups" on the left menu of your screen: +--- -![Go to groups](img/select-group2.png) +To create a group: -Click on "New group" on the top right side of your screen: +1. Expand the left sidebar by clicking the three bars at the upper left corner + and then navigate to **Groups**. -![New group](img/click-on-new-group.png) + ![Go to groups](img/create_new_group_sidebar.png) -Fill out the information required: +1. Once in your groups dashboard, click on **New group**. -1. Add a group path or group name (you can't add spaces, so you can use hyphens or underscores) + ![Create new group information](img/create_new_group_info.png) -1. Add details or a group description +1. Fill out the needed information: -1. You can choose a group avatar if you'd like + 1. Set the "Group path" which will be the namespace under which your projects + will be hosted (path can contain only letters, digits, underscores, dashes + and dots; it cannot start with dashes or end in dot). + 1. Optionally, you can add a description so that others can briefly understand + what this group is about. + 1. Optionally, choose and avatar for your project. + 1. Choose the [visibility level](../public_access/public_access.md). -1. Click on "create group" +1. Finally, click the **Create group** button. -![Group information](img/group_info.png) - -## Add a project to a group +## Add a new project to a group There are 2 different ways to add a new project to a group: -* Select a group and then click on "New project" on the right side of your screen. Then you can [create a project](create-project.md) +- Select a group and then click on the **New project** button. + + ![New project](img/create_new_project_from_group.png) -![New project](img/new_project.png) + You can then continue on [creating a project](create-project.md). -* When you are [creating a project](create-project.md), click on "create a group" on the bottom right side of your screen +- While you are [creating a project](create-project.md), select a group namespace + you've already created from the dropdown menu. -![Create a group](img/create_group.png) + ![Select group](img/select_group_dropdown.png) diff --git a/doc/gitlab-basics/img/click-on-new-group.png b/doc/gitlab-basics/img/click-on-new-group.png deleted file mode 100644 index 6450deec6fc..00000000000 Binary files a/doc/gitlab-basics/img/click-on-new-group.png and /dev/null differ diff --git a/doc/gitlab-basics/img/create_group.png b/doc/gitlab-basics/img/create_group.png deleted file mode 100644 index 7ecc3baa990..00000000000 Binary files a/doc/gitlab-basics/img/create_group.png and /dev/null differ diff --git a/doc/gitlab-basics/img/create_new_group_info.png b/doc/gitlab-basics/img/create_new_group_info.png new file mode 100644 index 00000000000..c8eddfd1bbb Binary files /dev/null and b/doc/gitlab-basics/img/create_new_group_info.png differ diff --git a/doc/gitlab-basics/img/create_new_group_sidebar.png b/doc/gitlab-basics/img/create_new_group_sidebar.png new file mode 100644 index 00000000000..28017ee02e0 Binary files /dev/null and b/doc/gitlab-basics/img/create_new_group_sidebar.png differ diff --git a/doc/gitlab-basics/img/create_new_project_from_group.png b/doc/gitlab-basics/img/create_new_project_from_group.png new file mode 100644 index 00000000000..6d41d17f9ca Binary files /dev/null and b/doc/gitlab-basics/img/create_new_project_from_group.png differ diff --git a/doc/gitlab-basics/img/group_info.png b/doc/gitlab-basics/img/group_info.png deleted file mode 100644 index 2507d6c295b..00000000000 Binary files a/doc/gitlab-basics/img/group_info.png and /dev/null differ diff --git a/doc/gitlab-basics/img/select-group2.png b/doc/gitlab-basics/img/select-group2.png deleted file mode 100644 index aee22c638db..00000000000 Binary files a/doc/gitlab-basics/img/select-group2.png and /dev/null differ diff --git a/doc/gitlab-basics/img/select_group_dropdown.png b/doc/gitlab-basics/img/select_group_dropdown.png new file mode 100644 index 00000000000..7d8b89c2df9 Binary files /dev/null and b/doc/gitlab-basics/img/select_group_dropdown.png differ -- cgit v1.2.1 From 54642e5245df774eefc1a1bd82b9c033defb5bc1 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 07:21:47 +0200 Subject: Reference the web editor docs in GitLab basics "Create a branch" --- doc/gitlab-basics/create-branch.md | 43 ++++----------------- .../img/web_editor_new_branch_from_issue.png | Bin 0 -> 4728 bytes doc/user/project/repository/web_editor.md | 4 +- 3 files changed, 10 insertions(+), 37 deletions(-) create mode 100644 doc/user/project/repository/img/web_editor_new_branch_from_issue.png diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md index 8f078c7692f..ad94f0dad29 100644 --- a/doc/gitlab-basics/create-branch.md +++ b/doc/gitlab-basics/create-branch.md @@ -2,38 +2,11 @@ A branch is an independent line of development. -New commits are recorded in the history for the current branch, which results in taking the source from someone’s repository (the place where the history of your work is stored) at certain point in time, and apply your own changes to it in the history of the project. - -To add changes to your GitLab project, you should create a branch. You can do it in your [shell](basic-git-commands.md) or in GitLab. - -To create a new branch in GitLab, sign in and then select a project on the right side of your screen: - -![Select a project](img/select_project.png) - -Click on "commits" on the menu on the left side of your screen: - -![Commits](img/commits.png) - -Click on the "branches" tab: - -![Branches](img/branches.png) - -Click on the "new branch" button on the right side of the screen: - -![New branch](img/newbranch.png) - -Fill out the information required: - -1. Add a name for your new branch (you can't add spaces, so you can use hyphens or underscores) - -1. On the "create from" space, add the the name of the branch you want to branch off from - -1. Click on the button "create branch" - -![Branch info](img/branch_info.png) - -### Note: - -You will be able to find and select the name of your branch in the white box next to a project's name: - -![Branch name](img/branch_name.png) +New commits are recorded in the history for the current branch, which results +in taking the source from someone’s repository (the place where the history of +your work is stored) at certain point in time, and apply your own changes to it +in the history of the project. + +To add changes to your GitLab project, you should create a branch. You can do +it in your [terminal](basic-git-commands.md) or by +[using the web interface](../user/project/repository/web_editor.md#create-a-new-branch). diff --git a/doc/user/project/repository/img/web_editor_new_branch_from_issue.png b/doc/user/project/repository/img/web_editor_new_branch_from_issue.png new file mode 100644 index 00000000000..b0a63ddf0ab Binary files /dev/null and b/doc/user/project/repository/img/web_editor_new_branch_from_issue.png differ diff --git a/doc/user/project/repository/web_editor.md b/doc/user/project/repository/web_editor.md index 993c6bfb7e9..675e89e4247 100644 --- a/doc/user/project/repository/web_editor.md +++ b/doc/user/project/repository/web_editor.md @@ -97,11 +97,11 @@ There are multiple ways to create a branch from GitLab's web interface. In case your development workflow dictates to have an issue for every merge request, you can quickly create a branch right on the issue page which will be -tied with the issue itself. You can see a **New Branch** button after the issue +tied with the issue itself. You can see a **New branch** button after the issue description, unless there is already a branch with the same name or a referenced merge request. -![New Branch Button](img/new_branch_from_issue.png) +![New Branch Button](img/web_editor_new_branch_from_issue.png) Once you click it, a new branch will be created that diverges from the default branch of your project, by default `master`. The branch name will be based on -- cgit v1.2.1 From dd9e5ba16598eceb61a2d27a4519710b84d01397 Mon Sep 17 00:00:00 2001 From: Chris Wilson Date: Tue, 11 Oct 2016 05:33:55 +0000 Subject: Fix step number and token param in URL example. Fix gitlab-rails command code formatting. --- doc/administration/troubleshooting/debug.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/administration/troubleshooting/debug.md b/doc/administration/troubleshooting/debug.md index d127d7b85e5..d8dce4388e1 100644 --- a/doc/administration/troubleshooting/debug.md +++ b/doc/administration/troubleshooting/debug.md @@ -144,14 +144,14 @@ separate Rails process to debug the issue: 1. Obtain the private token for your user (Profile Settings -> Account). 1. Bring up the GitLab Rails console. For omnibus users, run: - ```` + ``` sudo gitlab-rails console ``` 1. At the Rails console, run: ```ruby - [1] pry(main)> app.get '/private_token?' + [1] pry(main)> app.get '/?private_token=' ``` For example: -- cgit v1.2.1 From 7d703a4dbbce44fda3cc0cfed7c48c27f496b6a0 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 07:41:28 +0200 Subject: New images for GitLab basics "Fork project" docs --- doc/gitlab-basics/fork-project.md | 19 ++++++++++--------- doc/gitlab-basics/img/fork.png | Bin 896 -> 0 bytes doc/gitlab-basics/img/fork_choose_namespace.png | Bin 0 -> 39253 bytes doc/gitlab-basics/img/fork_new.png | Bin 0 -> 25540 bytes doc/gitlab-basics/img/select_project.png | Bin 16176 -> 0 bytes 5 files changed, 10 insertions(+), 9 deletions(-) delete mode 100644 doc/gitlab-basics/img/fork.png create mode 100644 doc/gitlab-basics/img/fork_choose_namespace.png create mode 100644 doc/gitlab-basics/img/fork_new.png delete mode 100644 doc/gitlab-basics/img/select_project.png diff --git a/doc/gitlab-basics/fork-project.md b/doc/gitlab-basics/fork-project.md index 7232fc2c1ae..6c232fe6086 100644 --- a/doc/gitlab-basics/fork-project.md +++ b/doc/gitlab-basics/fork-project.md @@ -1,19 +1,20 @@ # How to fork a project -A fork is a copy of an original repository that you can put somewhere else -or where you can experiment and apply changes that you can later decide if +A fork is a copy of an original repository that you can put in another namespace +where you can experiment and apply changes that you can later decide if publishing or not, without affecting your original project. It takes just a few steps to fork a project in GitLab. -Sign in to GitLab. +1. Go to a project's dashboard under the **Project** tab and click on the + **Fork** button. -Select a project on the right side of your screen: + ![Click on Fork button](img/fork_new.png) -![Select a project](img/select_project.png) +1. You will be asked where to fork the repository. Click on the user or group + to where you'd like to add the forked project. -Click on the "fork" button on the right side of your screen: + ![Choose namespace](img/fork_choose_namespace.png) -![Fork](img/fork.png) - -Click on the user or group to where you'd like to add the forked project. +1. After a few moments, depending on the repository's size, the forking will + complete. diff --git a/doc/gitlab-basics/img/fork.png b/doc/gitlab-basics/img/fork.png deleted file mode 100644 index 13ff8345616..00000000000 Binary files a/doc/gitlab-basics/img/fork.png and /dev/null differ diff --git a/doc/gitlab-basics/img/fork_choose_namespace.png b/doc/gitlab-basics/img/fork_choose_namespace.png new file mode 100644 index 00000000000..82c9c3bd39e Binary files /dev/null and b/doc/gitlab-basics/img/fork_choose_namespace.png differ diff --git a/doc/gitlab-basics/img/fork_new.png b/doc/gitlab-basics/img/fork_new.png new file mode 100644 index 00000000000..41885223286 Binary files /dev/null and b/doc/gitlab-basics/img/fork_new.png differ diff --git a/doc/gitlab-basics/img/select_project.png b/doc/gitlab-basics/img/select_project.png deleted file mode 100644 index 3bb832ea8d0..00000000000 Binary files a/doc/gitlab-basics/img/select_project.png and /dev/null differ -- cgit v1.2.1 From 7374f876dcfd2c113d16367fa62ced23934130c9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 07:44:26 +0200 Subject: Reference the web editor docs in GitLab basics "Add a file" --- doc/gitlab-basics/add-file.md | 28 +++------------------------- 1 file changed, 3 insertions(+), 25 deletions(-) diff --git a/doc/gitlab-basics/add-file.md b/doc/gitlab-basics/add-file.md index 86aff749ff1..e9fbcbc23a9 100644 --- a/doc/gitlab-basics/add-file.md +++ b/doc/gitlab-basics/add-file.md @@ -1,27 +1,5 @@ # How to add a file -You can create a file in your [shell](command-line-commands.md) or in GitLab. - -To create a file in GitLab, sign in to GitLab. - -Select a project on the right side of your screen: - -![Select a project](img/select_project.png) - -It's a good idea to [create a branch](create-branch.md), but it's not necessary. - -Go to the directory where you'd like to add the file and click on the "+" sign next to the name of the project and directory: - -![Create a file](img/create_file.png) - -Name your file (you can't add spaces, so you can use hyphens or underscores). Don't forget to include the markup language you'd like to use : - -![File name](img/file_name.png) - -Add all the information that you'd like to include in your file: - -![Add information](img/white_space.png) - -Add a commit message based on what you just added and then click on "commit changes": - -![Commit changes](img/commit_changes.png) +You can create a file in your [terminal](command-line-commands.md) and push +to GitLab or you can use the +[web interface](../user/project/repository/web_editor.md#create-a-file). -- cgit v1.2.1 From 73fff8d9e33d31b0958b7ef889a6ad3ff6dbf4c5 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Tue, 11 Oct 2016 10:58:46 +0500 Subject: Build instead create in label_link model spec --- spec/models/label_link_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb index 5e6f8ca1528..c18ed8574b1 100644 --- a/spec/models/label_link_spec.rb +++ b/spec/models/label_link_spec.rb @@ -1,8 +1,7 @@ require 'spec_helper' describe LabelLink, models: true do - let(:label) { create(:label_link) } - it { expect(label).to be_valid } + it { expect(build(:label_link)).to be_valid } it { is_expected.to belong_to(:label) } it { is_expected.to belong_to(:target) } -- cgit v1.2.1 From 85df1bf02dbcd180ad7db0c0e3c609671dac1a11 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Tue, 11 Oct 2016 11:02:53 +0500 Subject: Remove empty describe block on key spec model --- spec/models/key_spec.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index fd4a2beff58..7fc6ed1dd54 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -5,9 +5,6 @@ describe Key, models: true do it { is_expected.to belong_to(:user) } end - describe "Mass assignment" do - end - describe "Validation" do it { is_expected.to validate_presence_of(:title) } it { is_expected.to validate_presence_of(:key) } -- cgit v1.2.1 From 987c21f51ea67f1bd00fe50e61941920bc1feaa4 Mon Sep 17 00:00:00 2001 From: Manthan Mallikarjun Date: Mon, 4 Jul 2016 10:20:16 -0700 Subject: Add an example for testing a phoenix application with Gitlab CI. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + doc/ci/examples/README.md | 1 + doc/ci/examples/test-phoenix-application.md | 52 +++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 doc/ci/examples/test-phoenix-application.md diff --git a/CHANGELOG b/CHANGELOG index 06af4e4d6f4..d9c282257b4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -9,6 +9,7 @@ v 8.13.0 (unreleased) - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup + - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 40f0165deef..08fbd9afa2f 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -11,6 +11,7 @@ Apart from those, here is an collection of tutorials and guides on setting up yo - [Test and deploy a Python application to Heroku](test-and-deploy-python-application-to-heroku.md) - [Test a Clojure application](test-clojure-application.md) - [Test a Scala application](test-scala-application.md) +- [Test a Phoenix application](test-phoenix-application.md) - [Using `dpl` as deployment tool](deployment/README.md) - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) - [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples) diff --git a/doc/ci/examples/test-phoenix-application.md b/doc/ci/examples/test-phoenix-application.md new file mode 100644 index 00000000000..78cab2c0aeb --- /dev/null +++ b/doc/ci/examples/test-phoenix-application.md @@ -0,0 +1,52 @@ +## Test a Phoenix application + +This example demonstrates the integration of Gitlab CI with Phoenix, elixir and +postgres. + +### Add `.gitlab-ci.yml` file to project + +The following `.gitlab-ci.yml` should be added in the root of your +repository to trigger CI: + +```yaml +image: elixir:1.3.1 + +services: + - postgres:9.5.3 + +variables: + MIX_ENV: "test" + +before_script: + # Setup phoenix dependencies + - apt-get update + - apt-get install -y postgresql-client + - mix local.hex --force + - mix deps.get --only test + - mix ecto.reset + +test: + script: + - mix test +``` + +The variables will set the Mix environment to test. The +before_script will install `psql`, and other phoenix dependencies and will also +run your migrations. + +Finally, the test script will run your tests. + +### Update the Config Settings + +In `config/test.exs`, update the database hostname: +``` +config :my_app, MyApp.Repo, + hostname: if(System.get_env("CI"), do: "postgres", else: "localhost"), +``` + +### Add the Migrations Folder + +If you do not have any migrations yet, you will need to create an empty +`.gitkeep` file in `priv/repo/migrations`. + +**Source**: https://medium.com/@nahtnam/using-phoenix-on-gitlab-ci-5a51eec81142 -- cgit v1.2.1 From 8a3f389df620570e2b51f088573b90644c53348e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 09:36:03 +0200 Subject: Improve a bit the example .gitlab-ci.yml for Phoenix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/ci/examples/test-phoenix-application.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/doc/ci/examples/test-phoenix-application.md b/doc/ci/examples/test-phoenix-application.md index 78cab2c0aeb..150698ca04b 100644 --- a/doc/ci/examples/test-phoenix-application.md +++ b/doc/ci/examples/test-phoenix-application.md @@ -1,7 +1,7 @@ ## Test a Phoenix application -This example demonstrates the integration of Gitlab CI with Phoenix, elixir and -postgres. +This example demonstrates the integration of Gitlab CI with Phoenix, Elixir and +Postgres. ### Add `.gitlab-ci.yml` file to project @@ -9,10 +9,10 @@ The following `.gitlab-ci.yml` should be added in the root of your repository to trigger CI: ```yaml -image: elixir:1.3.1 +image: elixir:1.3 services: - - postgres:9.5.3 + - postgres:9.6 variables: MIX_ENV: "test" @@ -30,16 +30,17 @@ test: - mix test ``` -The variables will set the Mix environment to test. The -before_script will install `psql`, and other phoenix dependencies and will also +The variables will set the Mix environment to "test". The +`before_script` will install `psql`, some Phoenix dependencies, and will also run your migrations. -Finally, the test script will run your tests. +Finally, the test `script` will run your tests. ### Update the Config Settings In `config/test.exs`, update the database hostname: -``` + +```elixir config :my_app, MyApp.Repo, hostname: if(System.get_env("CI"), do: "postgres", else: "localhost"), ``` @@ -49,4 +50,7 @@ config :my_app, MyApp.Repo, If you do not have any migrations yet, you will need to create an empty `.gitkeep` file in `priv/repo/migrations`. -**Source**: https://medium.com/@nahtnam/using-phoenix-on-gitlab-ci-5a51eec81142 +### Sources + +- https://medium.com/@nahtnam/using-phoenix-on-gitlab-ci-5a51eec81142 +- https://davejlong.com/ci-with-phoenix-and-gitlab/ -- cgit v1.2.1 From ebba49149395ba6fb0f14c3aa9c46a496b234dea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 7 Oct 2016 18:35:36 +0200 Subject: Add a new gitlab:users:clear_all_authentication_tokens task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 1 + doc/raketasks/user_management.md | 15 ++++++++++++++ lib/tasks/gitlab/users.rake | 11 +++++++++++ spec/tasks/gitlab/users_rake_spec.rb | 38 ++++++++++++++++++++++++++++++++++++ 4 files changed, 65 insertions(+) create mode 100644 lib/tasks/gitlab/users.rake create mode 100644 spec/tasks/gitlab/users_rake_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 06af4e4d6f4..1a04e42c803 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -76,6 +76,7 @@ v 8.13.0 (unreleased) - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Notify the Merger about merge after successful build (Dimitris Karakasilis) - Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein) + - Add a new gitlab:users:clear_all_authentication_tokens task. !6745 - Reduce queries needed to find users using their SSH keys when pushing commits - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - Fix broken repository 500 errors in project list diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md index 8a5e2d6e16b..044b104f5c2 100644 --- a/doc/raketasks/user_management.md +++ b/doc/raketasks/user_management.md @@ -70,3 +70,18 @@ sudo gitlab-rake gitlab:two_factor:disable_for_all_users # installation from source bundle exec rake gitlab:two_factor:disable_for_all_users RAILS_ENV=production ``` + +## Clear authentication tokens for all users. Important! Data loss! + +Clear authentication tokens for all users in the GitLab database. This +task is useful if your users' authentication tokens might have been exposed in +any way. All the existing tokens will become invalid, and new tokens are +automatically generated upon sign-in or user modification. + +``` +# omnibus-gitlab +sudo gitlab-rake gitlab:users:clear_all_authentication_tokens + +# installation from source +bundle exec rake gitlab:users:clear_all_authentication_tokens RAILS_ENV=production +``` diff --git a/lib/tasks/gitlab/users.rake b/lib/tasks/gitlab/users.rake new file mode 100644 index 00000000000..3a16ace60bd --- /dev/null +++ b/lib/tasks/gitlab/users.rake @@ -0,0 +1,11 @@ +namespace :gitlab do + namespace :users do + desc "GitLab | Clear the authentication token for all users" + task clear_all_authentication_tokens: :environment do |t, args| + # Do small batched updates because these updates will be slow and locking + User.select(:id).find_in_batches(batch_size: 100) do |batch| + User.where(id: batch.map(&:id)).update_all(authentication_token: nil) + end + end + end +end diff --git a/spec/tasks/gitlab/users_rake_spec.rb b/spec/tasks/gitlab/users_rake_spec.rb new file mode 100644 index 00000000000..e6ebef82b78 --- /dev/null +++ b/spec/tasks/gitlab/users_rake_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' +require 'rake' + +describe 'gitlab:users namespace rake task' do + let(:enable_registry) { true } + + before :all do + Rake.application.rake_require 'tasks/gitlab/task_helpers' + Rake.application.rake_require 'tasks/gitlab/users' + + # empty task as env is already loaded + Rake::Task.define_task :environment + end + + def run_rake_task(task_name) + Rake::Task[task_name].reenable + Rake.application.invoke_task task_name + end + + describe 'clear_all_authentication_tokens' do + before do + # avoid writing task output to spec progress + allow($stdout).to receive :write + end + + context 'gitlab version' do + it 'clears the authentication token for all users' do + create_list(:user, 2) + + expect(User.pluck(:authentication_token)).to all(be_present) + + run_rake_task('gitlab:users:clear_all_authentication_tokens') + + expect(User.pluck(:authentication_token)).to all(be_nil) + end + end + end +end -- cgit v1.2.1 From 8d8282b42e765d20532d32b0598f42ea707f31f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 10:21:22 +0200 Subject: Add a safeguard in User#set_projects_limit MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- app/models/user.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/models/user.rb b/app/models/user.rb index 892ac28d5b3..f367f4616fb 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -589,6 +589,11 @@ class User < ActiveRecord::Base end def set_projects_limit + # `User.select(:id)` raises + # `ActiveModel::MissingAttributeError: missing attribute: projects_limit` + # without this safeguard! + return unless self.has_attribute?(:projects_limit) + connection_default_value_defined = new_record? && !projects_limit_changed? return unless self.projects_limit.nil? || connection_default_value_defined -- cgit v1.2.1 From 6c1690fcc03406ad3230cb05ba8701289a25ba28 Mon Sep 17 00:00:00 2001 From: Artem Sidorenko Date: Tue, 13 Sep 2016 23:05:16 +0200 Subject: Allow empty merge requests --- CHANGELOG | 1 + app/assets/stylesheets/pages/merge_requests.scss | 12 ++++++ app/services/merge_requests/build_service.rb | 23 +++++----- .../projects/merge_requests/_new_compare.html.haml | 13 ------ .../projects/merge_requests/_new_submit.html.haml | 50 ++++++++++++---------- .../shared/icons/_illustration_no_commits.svg | 1 + spec/services/merge_requests/build_service_spec.rb | 20 ++++++++- 7 files changed, 71 insertions(+), 49 deletions(-) create mode 100644 app/views/shared/icons/_illustration_no_commits.svg diff --git a/CHANGELOG b/CHANGELOG index 72e146f4f3f..81c0bfbe3e7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -80,6 +80,7 @@ v 8.13.0 (unreleased) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Retouch environments list and deployments list - Add Container Registry on/off status to Admin Area !6638 (the-undefined) + - Allow empty merge requests !6384 (Artem Sidorenko) - Grouped pipeline dropdown is a scrollable container - Fix a typo in doc/api/labels.md diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index bc8693ae467..2158dc0cfe8 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -204,6 +204,18 @@ word-break: break-all; } +.commits-empty { + text-align: center; + + h4 { + padding-top: 20px; + padding-bottom: 10px; + } + svg { + width: 230px; + } +} + .mr-list { .merge-request { padding: 10px 15px; diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index e57791f6818..404f75616b5 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -4,7 +4,7 @@ module MergeRequests merge_request = MergeRequest.new(params) # Set MR attributes - merge_request.can_be_created = false + merge_request.can_be_created = true merge_request.compare_commits = [] merge_request.source_project = project unless merge_request.source_project @@ -22,6 +22,12 @@ module MergeRequests return build_failed(merge_request, message) end + if merge_request.source_project == merge_request.target_project && + merge_request.target_branch == merge_request.source_branch + + return build_failed(merge_request, 'You must select different branches') + end + compare = CompareService.new.execute( merge_request.source_project, merge_request.source_branch, @@ -29,17 +35,8 @@ module MergeRequests merge_request.target_branch, ) - commits = compare.commits - - # 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 = commits - merge_request.can_be_created = true - merge_request.compare = compare - else - merge_request.can_be_created = false - end + merge_request.compare_commits = compare.commits + merge_request.compare = compare set_title_and_description(merge_request) end @@ -89,6 +86,8 @@ module MergeRequests end end + merge_request.title = merge_request.wip_title if commits.empty? + merge_request end diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index de39964fca8..466ec1475d8 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -65,19 +65,6 @@ - if @merge_request.errors.any? = form_errors(@merge_request) - - elsif @merge_request.source_branch.present? && @merge_request.target_branch.present? - .light-well.append-bottom-default - .center - %h4 - There isn't anything to merge. - %p.slead - - if @merge_request.source_branch == @merge_request.target_branch - You'll need to use different branch names to get a valid comparison. - - else - %span.label-branch #{@merge_request.source_branch} - and - %span.label-branch #{@merge_request.target_branch} - are the same. = f.submit 'Compare branches and continue', class: "btn btn-new mr-compare-btn" :javascript diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 88d8013a0d1..da6927879a4 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -18,29 +18,35 @@ = f.hidden_field :target_branch .mr-compare.merge-request - %ul.merge-request-tabs.nav-links.no-top.no-bottom - %li.commits-tab.active - = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do - Commits - %span.badge= @commits.size - - if @pipeline - %li.builds-tab - = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do - Builds - %span.badge= @statuses.size - %li.diffs-tab - = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do - Changes - %span.badge= @merge_request.diff_size + - if @commits.empty? + .commits-empty + %h4 + There are no commits yet. + = custom_icon ('illustration_no_commits') + - else + %ul.merge-request-tabs.nav-links.no-top.no-bottom + %li.commits-tab.active + = link_to url_for(params), data: {target: 'div#commits', action: 'new', toggle: 'tab'} do + Commits + %span.badge= @commits.size + - if @pipeline + %li.builds-tab + = link_to url_for(params), data: {target: 'div#builds', action: 'builds', toggle: 'tab'} do + Builds + %span.badge= @statuses.size + %li.diffs-tab + = link_to url_for(params.merge(action: 'new_diffs')), data: {target: 'div#diffs', action: 'new/diffs', toggle: 'tab'} do + Changes + %span.badge= @merge_request.diff_size - .tab-content - #commits.commits.tab-pane.active - = render "projects/merge_requests/show/commits" - #diffs.diffs.tab-pane - - # This tab is always loaded via AJAX - - if @pipeline - #builds.builds.tab-pane - = render "projects/merge_requests/show/builds" + .tab-content + #commits.commits.tab-pane.active + = render "projects/merge_requests/show/commits" + #diffs.diffs.tab-pane + - # This tab is always loaded via AJAX + - if @pipeline + #builds.builds.tab-pane + = render "projects/merge_requests/show/builds" .mr-loading-status = spinner diff --git a/app/views/shared/icons/_illustration_no_commits.svg b/app/views/shared/icons/_illustration_no_commits.svg new file mode 100644 index 00000000000..4f9d9add60d --- /dev/null +++ b/app/views/shared/icons/_illustration_no_commits.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/spec/services/merge_requests/build_service_spec.rb b/spec/services/merge_requests/build_service_spec.rb index 0d586e2216b..3a3f07ddcb9 100644 --- a/spec/services/merge_requests/build_service_spec.rb +++ b/spec/services/merge_requests/build_service_spec.rb @@ -52,12 +52,28 @@ describe MergeRequests::BuildService, services: true do end end - context 'no commits in the diff' do - let(:commits) { [] } + context 'same source and target branch' do + let(:source_branch) { 'master' } it 'forbids the merge request from being created' do expect(merge_request.can_be_created).to eq(false) end + + it 'adds an error message to the merge request' do + expect(merge_request.errors).to contain_exactly('You must select different branches') + end + end + + context 'no commits in the diff' do + let(:commits) { [] } + + it 'allows the merge request to be created' do + expect(merge_request.can_be_created).to eq(true) + end + + it 'adds a WIP prefix to the merge request title' do + expect(merge_request.title).to eq('WIP: Feature branch') + end end context 'one commit in the diff' do -- cgit v1.2.1 From 815bf9b5892e215dec31ab0a45a5cc75b091e2af Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 11:07:37 +0200 Subject: Add new images in GitLab basics "Create issue" docs --- doc/gitlab-basics/create-issue.md | 33 ++++++++++++++++------------- doc/gitlab-basics/img/issue_title.png | Bin 8311 -> 0 bytes doc/gitlab-basics/img/issues.png | Bin 4153 -> 0 bytes doc/gitlab-basics/img/new_issue.png | Bin 2974 -> 0 bytes doc/gitlab-basics/img/new_issue_button.png | Bin 0 -> 3070 bytes doc/gitlab-basics/img/new_issue_page.png | Bin 0 -> 53268 bytes doc/gitlab-basics/img/newbranch.png | Bin 1244 -> 0 bytes doc/gitlab-basics/img/project_navbar.png | Bin 0 -> 5745 bytes doc/gitlab-basics/img/submit_new_issue.png | Bin 8644 -> 0 bytes 9 files changed, 18 insertions(+), 15 deletions(-) delete mode 100644 doc/gitlab-basics/img/issue_title.png delete mode 100644 doc/gitlab-basics/img/issues.png delete mode 100644 doc/gitlab-basics/img/new_issue.png create mode 100644 doc/gitlab-basics/img/new_issue_button.png create mode 100644 doc/gitlab-basics/img/new_issue_page.png delete mode 100644 doc/gitlab-basics/img/newbranch.png create mode 100644 doc/gitlab-basics/img/project_navbar.png delete mode 100644 doc/gitlab-basics/img/submit_new_issue.png diff --git a/doc/gitlab-basics/create-issue.md b/doc/gitlab-basics/create-issue.md index e11c86cafa0..13e5a738c89 100644 --- a/doc/gitlab-basics/create-issue.md +++ b/doc/gitlab-basics/create-issue.md @@ -1,27 +1,30 @@ # How to create an Issue in GitLab -The Issue Tracker is a good place to add things that need to be improved or solved in a project. +The issue tracker is a good place to add things that need to be improved or +solved in a project. -To create an Issue, sign in to GitLab. +--- -Go to the project where you'd like to create the Issue: +1. Go to the project where you'd like to create the issue and navigate to the + **Issues** tab on top. -![Select a project](img/select_project.png) + ![Issues](img/project_navbar.png) -Click on "Issues" on the left side of your screen: +1. Click on the **New issue** button on the right side of your screen. -![Issues](img/issues.png) + ![New issue](img/new_issue_button.png) -Click on the "+ new issue" button on the right side of your screen: +1. At the very minimum, add a title and a description to your issue. + You may assign it to a user, add a milestone or add labels (all optional). -![New issue](img/new_issue.png) + ![Issue title and description](img/new_issue_page.png) -Add a title and a description to your issue: +1. When ready, click on **Submit issue**. -![Issue title and description](img/issue_title.png) +--- -You may assign the Issue to a user, add a milestone and add labels (they are all optional). Then click on "submit new issue": - -![Submit new issue](img/submit_new_issue.png) - -Your Issue will now be added to the Issue Tracker and will be ready to be reviewed. You can comment on it and mention the people involved. You can also link Issues to the Merge Requests where the Issues are solved. To do this, you can use an [Issue closing pattern](../user/project/issues/automatic_issue_closing.md). +Your Issue will now be added to the issue tracker of the project you opened it +at and will be ready to be reviewed. You can comment on it and mention the +people involved. You can also link issues to the merge requests where the issues +are solved. To do this, you can use an +[issue closing pattern](../user/project/issues/automatic_issue_closing.md). diff --git a/doc/gitlab-basics/img/issue_title.png b/doc/gitlab-basics/img/issue_title.png deleted file mode 100644 index 60a6f7973be..00000000000 Binary files a/doc/gitlab-basics/img/issue_title.png and /dev/null differ diff --git a/doc/gitlab-basics/img/issues.png b/doc/gitlab-basics/img/issues.png deleted file mode 100644 index 14e9cdb64e1..00000000000 Binary files a/doc/gitlab-basics/img/issues.png and /dev/null differ diff --git a/doc/gitlab-basics/img/new_issue.png b/doc/gitlab-basics/img/new_issue.png deleted file mode 100644 index 94e7503dd8b..00000000000 Binary files a/doc/gitlab-basics/img/new_issue.png and /dev/null differ diff --git a/doc/gitlab-basics/img/new_issue_button.png b/doc/gitlab-basics/img/new_issue_button.png new file mode 100644 index 00000000000..46b626bed65 Binary files /dev/null and b/doc/gitlab-basics/img/new_issue_button.png differ diff --git a/doc/gitlab-basics/img/new_issue_page.png b/doc/gitlab-basics/img/new_issue_page.png new file mode 100644 index 00000000000..843504130b7 Binary files /dev/null and b/doc/gitlab-basics/img/new_issue_page.png differ diff --git a/doc/gitlab-basics/img/newbranch.png b/doc/gitlab-basics/img/newbranch.png deleted file mode 100644 index d5fcf33c4ea..00000000000 Binary files a/doc/gitlab-basics/img/newbranch.png and /dev/null differ diff --git a/doc/gitlab-basics/img/project_navbar.png b/doc/gitlab-basics/img/project_navbar.png new file mode 100644 index 00000000000..97cf3cd9702 Binary files /dev/null and b/doc/gitlab-basics/img/project_navbar.png differ diff --git a/doc/gitlab-basics/img/submit_new_issue.png b/doc/gitlab-basics/img/submit_new_issue.png deleted file mode 100644 index 78b854c8903..00000000000 Binary files a/doc/gitlab-basics/img/submit_new_issue.png and /dev/null differ -- cgit v1.2.1 From 54fd5e830a867bd20d263bce8b37385dd7bd7b6e Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 11:29:16 +0200 Subject: New images for GitLab basics "Create MR" docs --- doc/gitlab-basics/add-merge-request.md | 49 +++++++++------------ doc/gitlab-basics/img/button-create-mr.png | Bin 5927 -> 0 bytes doc/gitlab-basics/img/compare_branches.png | Bin 1520 -> 0 bytes doc/gitlab-basics/img/merge_request_new.png | Bin 0 -> 3596 bytes doc/gitlab-basics/img/merge_request_page.png | Bin 0 -> 91432 bytes .../img/merge_request_select_branch.png | Bin 0 -> 50707 bytes doc/gitlab-basics/img/new_merge_request.png | Bin 3162 -> 0 bytes doc/gitlab-basics/img/select_branch.png | Bin 11207 -> 0 bytes 8 files changed, 20 insertions(+), 29 deletions(-) delete mode 100644 doc/gitlab-basics/img/button-create-mr.png delete mode 100644 doc/gitlab-basics/img/compare_branches.png create mode 100644 doc/gitlab-basics/img/merge_request_new.png create mode 100644 doc/gitlab-basics/img/merge_request_page.png create mode 100644 doc/gitlab-basics/img/merge_request_select_branch.png delete mode 100644 doc/gitlab-basics/img/new_merge_request.png delete mode 100644 doc/gitlab-basics/img/select_branch.png diff --git a/doc/gitlab-basics/add-merge-request.md b/doc/gitlab-basics/add-merge-request.md index 082d1408aa2..bf01fe51dc3 100644 --- a/doc/gitlab-basics/add-merge-request.md +++ b/doc/gitlab-basics/add-merge-request.md @@ -1,42 +1,33 @@ # How to create a merge request -Merge Requests are useful to integrate separate changes that you've made to a project, on different branches. +Merge requests are useful to integrate separate changes that you've made to a +project, on different branches. This is a brief guide on how to create a merge +request. For more information, check the +[merge requests documentation](../user/project/merge_requests.md). -To create a new Merge Request, sign in to GitLab. +--- -Go to the project where you'd like to merge your changes: +1. Before you start, you should have already [created a branch](create-branch.md) + and [pushed your changes](basic-git-commands.md) to GitLab. -![Select a project](img/select_project.png) +1. You can then go to the project where you'd like to merge your changes and + click on the **Merge requests** tab. -Click on "Merge Requests" on the left side of your screen: + ![Merge requests](img/project_navbar.png) -![Merge requests](img/merge_requests.png) +1. Click on **New merge request** on the right side of the screen. -Click on "+ new Merge Request" on the right side of the screen: + ![New Merge Request](img/merge_request_new.png) -![New Merge Request](img/new_merge_request.png) +1. Select a source branch and click on the **Compare branches and continue** button. -Select a source branch or branch: + ![Select a branch](img/merge_request_select_branch.png) -![Select a branch](img/select_branch.png) +1. At a minimum, add a title and a description to your merge request. Optionally, + select a user to review your merge request and to accept or close it. You may + also select a milestone and labels. -Click on the "compare branches" button: + ![New merge request page](img/merge_request_page.png) -![Compare branches](img/compare_branches.png) - -Add a title and a description to your Merge Request: - -![Add a title and description](img/title_description_mr.png) - -Select a user to review your Merge Request and to accept or close it. You may also select milestones and labels (they are optional). Then click on the "submit new Merge Request" button: - -![Add a new merge request](img/add_new_merge_request.png) - -Your Merge Request will be ready to be approved and published. - -### Note - -After you created a new branch, you'll immediately find a "create a Merge Request" button at the top of your screen. -You may automatically create a Merge Request from your recently created branch when clicking on this button: - -![Automatic MR button](img/button-create-mr.png) +1. When ready, click on the **Submit merge request** button. Your merge request + will be ready to be approved and published. diff --git a/doc/gitlab-basics/img/button-create-mr.png b/doc/gitlab-basics/img/button-create-mr.png deleted file mode 100644 index b52ab148839..00000000000 Binary files a/doc/gitlab-basics/img/button-create-mr.png and /dev/null differ diff --git a/doc/gitlab-basics/img/compare_branches.png b/doc/gitlab-basics/img/compare_branches.png deleted file mode 100644 index 8a18453dd05..00000000000 Binary files a/doc/gitlab-basics/img/compare_branches.png and /dev/null differ diff --git a/doc/gitlab-basics/img/merge_request_new.png b/doc/gitlab-basics/img/merge_request_new.png new file mode 100644 index 00000000000..0aba5743f01 Binary files /dev/null and b/doc/gitlab-basics/img/merge_request_new.png differ diff --git a/doc/gitlab-basics/img/merge_request_page.png b/doc/gitlab-basics/img/merge_request_page.png new file mode 100644 index 00000000000..68c3bbf9444 Binary files /dev/null and b/doc/gitlab-basics/img/merge_request_page.png differ diff --git a/doc/gitlab-basics/img/merge_request_select_branch.png b/doc/gitlab-basics/img/merge_request_select_branch.png new file mode 100644 index 00000000000..516436ff6cc Binary files /dev/null and b/doc/gitlab-basics/img/merge_request_select_branch.png differ diff --git a/doc/gitlab-basics/img/new_merge_request.png b/doc/gitlab-basics/img/new_merge_request.png deleted file mode 100644 index 842f5ebed74..00000000000 Binary files a/doc/gitlab-basics/img/new_merge_request.png and /dev/null differ diff --git a/doc/gitlab-basics/img/select_branch.png b/doc/gitlab-basics/img/select_branch.png deleted file mode 100644 index f72a3ffb57f..00000000000 Binary files a/doc/gitlab-basics/img/select_branch.png and /dev/null differ -- cgit v1.2.1 From 777ca780275d7177dffa7d95e1e863192537da68 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 11:30:35 +0200 Subject: Rearrange GitLab basics READMEs [ci skip] --- doc/README.md | 2 +- doc/gitlab-basics/README.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/README.md b/doc/README.md index 9017b143260..99fbfc3438d 100644 --- a/doc/README.md +++ b/doc/README.md @@ -7,7 +7,7 @@ - [CI/CD](ci/README.md) GitLab Continuous Integration (CI) and Continuous Delivery (CD) getting started, `.gitlab-ci.yml` options, and examples. - [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. - [Container Registry](user/project/container_registry.md) Learn how to use GitLab Container Registry. -- [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab. +- [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/markdown.md) GitLab's advanced formatting system. diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md index 38843f36b2e..746ccb89705 100644 --- a/doc/gitlab-basics/README.md +++ b/doc/gitlab-basics/README.md @@ -11,5 +11,5 @@ Step-by-step guides on the basics of working with Git and GitLab. - [Fork a project](fork-project.md) - [Add a file](add-file.md) - [Add an image](add-image.md) -- [Create a Merge Request](add-merge-request.md) -- [Create an Issue](create-issue.md) +- [Create an issue](create-issue.md) +- [Create a merge request](add-merge-request.md) -- cgit v1.2.1 From d87df157987e5ba2690fc1a7937e557821efb997 Mon Sep 17 00:00:00 2001 From: Dimitrie Hoekstra Date: Tue, 11 Oct 2016 11:34:20 +0200 Subject: changed the scss for the top line connectors to be exactly centered --- app/assets/stylesheets/pages/pipelines.scss | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 21224447628..05f59279637 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -316,7 +316,7 @@ &::before { content: ''; position: absolute; - top: 49%; + top: 48%; left: -48px; border-top: 2px solid $border-color; width: 48px; @@ -493,7 +493,7 @@ &::after { content: ''; position: absolute; - top: 49%; + top: 48%; right: -48px; border-top: 2px solid $border-color; width: 48px; @@ -589,7 +589,7 @@ width: 21px; height: 25px; position: absolute; - top: -31px; + top: -32px; border-top: 2px solid $border-color; } -- cgit v1.2.1 From f7d2a97a84908c3cddf2d4fc550fbc2c7857af81 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 10 Oct 2016 17:28:09 +0100 Subject: Fixes padding in all clipboard icons that have .btn class --- app/helpers/button_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index a695aceea76..353e7fd33fd 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -22,7 +22,7 @@ module ButtonHelper class: "btn #{css_class}", data: data, type: :button, - title: "Copy to Clipboard" + title: 'Copy to Clipboard' end def http_clone_button(project, placement = 'right', append_link: true) -- cgit v1.2.1 From 4269e303515386f38a135fd07ad38955c027e424 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 10 Oct 2016 20:25:14 +0100 Subject: Adds entry to CHANGELOG --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 56c3ee90541..6afcbf28013 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -88,6 +88,7 @@ v 8.13.0 (unreleased) - Allow empty merge requests !6384 (Artem Sidorenko) - Grouped pipeline dropdown is a scrollable container - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) + - Fixes padding in all clipboard icons that have .btn class - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found -- cgit v1.2.1 From b0646c4f23aff0343c2fd455c7dc5de52ca6a362 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 11 Oct 2016 10:17:56 +0100 Subject: Adds btn-transparent class in correct place due to changes in master --- app/helpers/button_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/button_helper.rb b/app/helpers/button_helper.rb index 353e7fd33fd..85e1dc33ee8 100644 --- a/app/helpers/button_helper.rb +++ b/app/helpers/button_helper.rb @@ -15,7 +15,7 @@ module ButtonHelper # # See http://clipboardjs.com/#usage def clipboard_button(data = {}) - css_class = data[:class] || 'btn-clipboard' + css_class = data[:class] || 'btn-clipboard btn-transparent' data = { toggle: 'tooltip', placement: 'bottom', container: 'body' }.merge(data) content_tag :button, icon('clipboard'), -- cgit v1.2.1 From e98d4940fe8f5315f7467a13fcdd952ace73f9e8 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 13:14:08 +0200 Subject: Remove redundant images [ci skip] --- doc/gitlab-basics/README.md | 2 +- doc/gitlab-basics/img/add_new_merge_request.png | Bin 9003 -> 0 bytes doc/gitlab-basics/img/branch_info.png | Bin 7572 -> 0 bytes doc/gitlab-basics/img/branch_name.png | Bin 2137 -> 0 bytes doc/gitlab-basics/img/branches.png | Bin 3548 -> 0 bytes doc/gitlab-basics/img/commit_changes.png | Bin 4941 -> 0 bytes doc/gitlab-basics/img/commit_message.png | Bin 5103 -> 0 bytes doc/gitlab-basics/img/commits.png | Bin 4112 -> 0 bytes doc/gitlab-basics/img/create_file.png | Bin 2451 -> 0 bytes doc/gitlab-basics/img/edit_file.png | Bin 2221 -> 0 bytes doc/gitlab-basics/img/file_located.png | Bin 3078 -> 0 bytes doc/gitlab-basics/img/file_name.png | Bin 2412 -> 0 bytes doc/gitlab-basics/img/find_file.png | Bin 8426 -> 0 bytes doc/gitlab-basics/img/find_group.png | Bin 5897 -> 0 bytes doc/gitlab-basics/img/groups.png | Bin 4752 -> 0 bytes doc/gitlab-basics/img/image_file.png | Bin 2796 -> 0 bytes doc/gitlab-basics/img/merge_requests.png | Bin 4213 -> 0 bytes doc/gitlab-basics/img/select-group.png | Bin 6034 -> 0 bytes doc/gitlab-basics/img/settings.png | Bin 4149 -> 0 bytes doc/gitlab-basics/img/title_description_mr.png | Bin 11919 -> 0 bytes doc/gitlab-basics/img/white_space.png | Bin 2192 -> 0 bytes 21 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 doc/gitlab-basics/img/add_new_merge_request.png delete mode 100644 doc/gitlab-basics/img/branch_info.png delete mode 100644 doc/gitlab-basics/img/branch_name.png delete mode 100644 doc/gitlab-basics/img/branches.png delete mode 100644 doc/gitlab-basics/img/commit_changes.png delete mode 100644 doc/gitlab-basics/img/commit_message.png delete mode 100644 doc/gitlab-basics/img/commits.png delete mode 100644 doc/gitlab-basics/img/create_file.png delete mode 100644 doc/gitlab-basics/img/edit_file.png delete mode 100644 doc/gitlab-basics/img/file_located.png delete mode 100644 doc/gitlab-basics/img/file_name.png delete mode 100644 doc/gitlab-basics/img/find_file.png delete mode 100644 doc/gitlab-basics/img/find_group.png delete mode 100644 doc/gitlab-basics/img/groups.png delete mode 100644 doc/gitlab-basics/img/image_file.png delete mode 100644 doc/gitlab-basics/img/merge_requests.png delete mode 100644 doc/gitlab-basics/img/select-group.png delete mode 100644 doc/gitlab-basics/img/settings.png delete mode 100644 doc/gitlab-basics/img/title_description_mr.png delete mode 100644 doc/gitlab-basics/img/white_space.png diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md index 746ccb89705..d7e3aa35bdd 100644 --- a/doc/gitlab-basics/README.md +++ b/doc/gitlab-basics/README.md @@ -2,7 +2,7 @@ Step-by-step guides on the basics of working with Git and GitLab. -- [Command Line basics](command-line-commands.md) +- [Command line basics](command-line-commands.md) - [Start using Git on the command line](start-using-git.md) - [Create and add your SSH Keys](create-your-ssh-keys.md) - [Create a project](create-project.md) diff --git a/doc/gitlab-basics/img/add_new_merge_request.png b/doc/gitlab-basics/img/add_new_merge_request.png deleted file mode 100644 index e60992c4c6a..00000000000 Binary files a/doc/gitlab-basics/img/add_new_merge_request.png and /dev/null differ diff --git a/doc/gitlab-basics/img/branch_info.png b/doc/gitlab-basics/img/branch_info.png deleted file mode 100644 index 2264f3c5bf2..00000000000 Binary files a/doc/gitlab-basics/img/branch_info.png and /dev/null differ diff --git a/doc/gitlab-basics/img/branch_name.png b/doc/gitlab-basics/img/branch_name.png deleted file mode 100644 index 75fe8313611..00000000000 Binary files a/doc/gitlab-basics/img/branch_name.png and /dev/null differ diff --git a/doc/gitlab-basics/img/branches.png b/doc/gitlab-basics/img/branches.png deleted file mode 100644 index 8621bc05776..00000000000 Binary files a/doc/gitlab-basics/img/branches.png and /dev/null differ diff --git a/doc/gitlab-basics/img/commit_changes.png b/doc/gitlab-basics/img/commit_changes.png deleted file mode 100644 index a88809c5a3f..00000000000 Binary files a/doc/gitlab-basics/img/commit_changes.png and /dev/null differ diff --git a/doc/gitlab-basics/img/commit_message.png b/doc/gitlab-basics/img/commit_message.png deleted file mode 100644 index 4abe4517f98..00000000000 Binary files a/doc/gitlab-basics/img/commit_message.png and /dev/null differ diff --git a/doc/gitlab-basics/img/commits.png b/doc/gitlab-basics/img/commits.png deleted file mode 100644 index 2bfcaf75f01..00000000000 Binary files a/doc/gitlab-basics/img/commits.png and /dev/null differ diff --git a/doc/gitlab-basics/img/create_file.png b/doc/gitlab-basics/img/create_file.png deleted file mode 100644 index 5ebe1b227dd..00000000000 Binary files a/doc/gitlab-basics/img/create_file.png and /dev/null differ diff --git a/doc/gitlab-basics/img/edit_file.png b/doc/gitlab-basics/img/edit_file.png deleted file mode 100644 index 9d3e817d036..00000000000 Binary files a/doc/gitlab-basics/img/edit_file.png and /dev/null differ diff --git a/doc/gitlab-basics/img/file_located.png b/doc/gitlab-basics/img/file_located.png deleted file mode 100644 index e357cb5c6ab..00000000000 Binary files a/doc/gitlab-basics/img/file_located.png and /dev/null differ diff --git a/doc/gitlab-basics/img/file_name.png b/doc/gitlab-basics/img/file_name.png deleted file mode 100644 index 01639c77d0d..00000000000 Binary files a/doc/gitlab-basics/img/file_name.png and /dev/null differ diff --git a/doc/gitlab-basics/img/find_file.png b/doc/gitlab-basics/img/find_file.png deleted file mode 100644 index 6f26d26ae18..00000000000 Binary files a/doc/gitlab-basics/img/find_file.png and /dev/null differ diff --git a/doc/gitlab-basics/img/find_group.png b/doc/gitlab-basics/img/find_group.png deleted file mode 100644 index 1211510aae9..00000000000 Binary files a/doc/gitlab-basics/img/find_group.png and /dev/null differ diff --git a/doc/gitlab-basics/img/groups.png b/doc/gitlab-basics/img/groups.png deleted file mode 100644 index ef3dca60cc8..00000000000 Binary files a/doc/gitlab-basics/img/groups.png and /dev/null differ diff --git a/doc/gitlab-basics/img/image_file.png b/doc/gitlab-basics/img/image_file.png deleted file mode 100644 index 7f304b8e1f2..00000000000 Binary files a/doc/gitlab-basics/img/image_file.png and /dev/null differ diff --git a/doc/gitlab-basics/img/merge_requests.png b/doc/gitlab-basics/img/merge_requests.png deleted file mode 100644 index 570164df18b..00000000000 Binary files a/doc/gitlab-basics/img/merge_requests.png and /dev/null differ diff --git a/doc/gitlab-basics/img/select-group.png b/doc/gitlab-basics/img/select-group.png deleted file mode 100644 index 33b978dd899..00000000000 Binary files a/doc/gitlab-basics/img/select-group.png and /dev/null differ diff --git a/doc/gitlab-basics/img/settings.png b/doc/gitlab-basics/img/settings.png deleted file mode 100644 index 78637013d9b..00000000000 Binary files a/doc/gitlab-basics/img/settings.png and /dev/null differ diff --git a/doc/gitlab-basics/img/title_description_mr.png b/doc/gitlab-basics/img/title_description_mr.png deleted file mode 100644 index c31d61ec336..00000000000 Binary files a/doc/gitlab-basics/img/title_description_mr.png and /dev/null differ diff --git a/doc/gitlab-basics/img/white_space.png b/doc/gitlab-basics/img/white_space.png deleted file mode 100644 index eaa969bdcf4..00000000000 Binary files a/doc/gitlab-basics/img/white_space.png and /dev/null differ -- cgit v1.2.1 From c64721f16331f2115d3fe87f3c04134f8cba0163 Mon Sep 17 00:00:00 2001 From: Nick Thomas Date: Wed, 5 Oct 2016 17:40:13 +0100 Subject: Document the new CI_DEBUG_TRACE variable [ci skip] --- doc/ci/pipelines.md | 2 ++ doc/ci/variables/README.md | 34 ++++++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md index ca9b986a060..729c1dc8c0d 100644 --- a/doc/ci/pipelines.md +++ b/doc/ci/pipelines.md @@ -31,6 +31,8 @@ project. ## Seeing build status Clicking on a pipeline will show the builds that were run for that pipeline. +Clicking on an individual build will show you its build trace, and allow you to +cancel the build, retry it, or erase the build trace. ## Badges diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 22d67bd9964..a4c3a731a20 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -48,6 +48,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used | | **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab | | **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags | +| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled | | **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the build | | **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the build | @@ -105,6 +106,39 @@ Variables can be defined at a global level, but also at a job level. More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md). +#### Debug tracing + +> **WARNING:** Enabling debug tracing can have severe security implications. The + output **will** contain the content of all your secure variables and any other + secrets! The output **will** be uploaded to the GitLab server and made visible + in build traces! + +By default, GitLab Runner hides most of the details of what it is doing when +processing a job. This behaviour keeps build traces short, and prevents secrets +from being leaked into the trace unless your script writes them to the screen. + +If a job isn't working as expected, this can make the problem difficult to +investigate; in these cases, you can enable debug tracing in `.gitlab-ci.yml`. +Available on GitLab Runner v1.7+, this feature enables the shell's execution +trace, resulting in a verbose build trace listing all commands that were run, +variables that were set, etc. + +Before enabling this, you should ensure builds are visible to +[team members only](../../../user/permissions.md#project-features). You should +also [erase](../pipelines.md#seeing-build-traces) all generated build traces +before making them visible again. + +To enable debug traces, set the `CI_DEBUG_TRACE` variable to `true`: + +```yaml +job1: + variables: + CI_DEBUG_TRACE: "true" +``` + +The [example project](https://gitlab.com/gitlab-examples/ci-debug-trace) +demonstrates a working configuration, including build trace examples. + ### User-defined variables (Secure Variables) **This feature requires GitLab Runner 0.4.0 or higher** -- cgit v1.2.1 From f361b1c624443f0e693d8b7c9bd81fb713c943cf Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 14:22:18 +0200 Subject: Refactor the SubGit/SVN documentation [ci skip] --- doc/workflow/importing/migrating_from_svn.md | 92 ++++++++++++++++------------ 1 file changed, 52 insertions(+), 40 deletions(-) diff --git a/doc/workflow/importing/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md index fc27a38f735..423b095e69e 100644 --- a/doc/workflow/importing/migrating_from_svn.md +++ b/doc/workflow/importing/migrating_from_svn.md @@ -10,7 +10,7 @@ There are two approaches to SVN to Git migration: 1. [Git/SVN Mirror](#smooth-migration-with-a-gitsvn-mirror-using-subgit) which: - Makes the GitLab repository to mirror the SVN project. - - Git and SVN repositories are kept in sync; you can use either one. + - Git and SVN repositories are kept in sync; you can use either one. - Smoothens the migration process and allows to manage migration risks. 1. [Cut over migration](#cut-over-migration-with-svn2git) which: @@ -19,82 +19,94 @@ There are two approaches to SVN to Git migration: ## Smooth migration with a Git/SVN mirror using SubGit -#### Prerequisites +[SubGit](https://subgit.com) is a tool for a smooth, stress-free SVN to Git +migration. It creates a writable Git mirror of a local or remote Subversion +repository and that way you can use both Subversion and Git as long as you like. +It requires access to your GitLab server as it talks with the Git repositories +directly in a filesystem level. -Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions follow this -[instruction](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html). +### SubGit prerequisites -Download SubGit tool from [https://subgit.com/download/](https://subgit.com/download/) +1. Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions you can + follow [this article](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html). +1. Download SubGit from https://subgit.com/download/. +1. Unpack the downloaded SubGit zip archive to the `/opt` directory. The `subgit` + command will be available at `/opt/subgit-VERSION/bin/subgit`. -Unpack downloaded SubGit zip archive to `/opt` directory, subgit will be available -at `/opt/subgit-VERSION/bin/subgit` +### SubGit configuration -#### Configuration - -In GitLab create new empty repository. In filesystem it will be located at -`/var/opt/gitlab/git-data/repositories/USER/REPOS.git` path by default. -For convenice, assign this path to a variable: +The first step to mirror you SVN repository in GitLab is to create a new empty +project which will be used as a mirror. For Omnibus installations the path to +the repository will be located at +`/var/opt/gitlab/git-data/repositories/USER/REPO.git` by default. For +installations from source, the default repository directory will be +`/home/git/repositories/USER/REPO.git`. For convenience, assign this path to a +variable: ``` -GIT_REPOS_PATH=/var/opt/gitlab/git-data/repositories/USER/REPOS.git +GIT_REPO_PATH=/var/opt/gitlab/git-data/repositories/USER/REPOS.git ``` -SubGit will keep this repository will be kept in sync with a remote SVN project. -For convenience, assign remote SVN project URL to a variable: +SubGit will keep this repository in sync with a remote SVN project. For +convenience, assign your remote SVN project URL to a variable: ``` SVN_PROJECT_URL=http://svn.company.com/repos/project ``` -Run SubGit to set up a Git/SVN mirror. Make sure `subgit` command is ran -on behalf of the same user that keeps ownership of GitLab Git repositories (`git` by default): +Next you need to run SubGit to set up a Git/SVN mirror. Make sure the following +`subgit` command is ran on behalf of the same user that keeps ownership of +GitLab Git repositories (by default `git`): ``` -subgit configure --layout auto $SVN_PROJECT_URL $GIT_REPOS_PATH +subgit configure --layout auto $SVN_PROJECT_URL $GIT_REPO_PATH ``` -Adjust authors and branches mappings, if necessary: +Adjust authors and branches mappings, if necessary. Open with your favorite +text editor: ``` -edit $GIT_REPOS_PATH/subgit/authors.txt -edit $GIT_REPOS_PATH/subgit/config +edit $GIT_REPO_PATH/subgit/authors.txt +edit $GIT_REPO_PATH/subgit/config ``` -For more information regarding SubGit configuration options, refer to -[documentation](https://subgit.com/documentation.html) at SubGit web site. +For more information regarding the SubGit configuration options, refer to +[SubGit's documentation](https://subgit.com/documentation.html) website. -#### Initial translation +### Initial translation -Run `subgit` to perform initial translation of existing SVN revisions into -Git repository: +Now that SubGit has configured the Git/SVN repos, run `subgit` to perform the +initial translation of existing SVN revisions into the Git repository: ``` subgit install $GIT_REPOS_PATH ``` -After initial translation is completed, GitLab Git repository and SVN project -will be kept in sync by `subgit` - new Git commits will be translated to SVN -revisions and new SVN revisions will be translated to Git commits. Mirror works -transparently and does not require any special commands. +After the initial translation is completed, the Git repository and the SVN +project will be kept in sync by `subgit` - new Git commits will be translated to +SVN revisions and new SVN revisions will be translated to Git commits. Mirror +works transparently and does not require any special commands. -Would you prefer to perform one-time cut over migration with `subgit` use -`import` command in place of `install`: +If you would prefer to perform one-time cut over migration with `subgit`, use +the `import` command instead of `install`: ``` -subgit import $GIT_REPOS_PATH +subgit import $GIT_REPO_PATH ``` -#### Licensing +### SubGit licensing -Running SubGit in a mirror mode requires [registration](https://subgit.com/pricing.html). Registration is free for Open Source, -Academic and Startup projects. +Running SubGit in a mirror mode requires a +[registration](https://subgit.com/pricing.html). Registration is free for open +source, academic and startup projects. -We're currently working on deeper GitLab/SubGit intergation. You may track our +We're currently working on deeper GitLab/SubGit integration. You may track our progress at [this issue](https://gitlab.com/gitlab-org/gitlab-ee/issues/990). -#### Support +### SubGit support -For any questions related to SVN to GitLab migration with SubGit you can contact SubGit team at [support@subgit.com](mailto:support@subgit.com). +For any questions related to SVN to GitLab migration with SubGit, you can +contact the SubGit team directly at [support@subgit.com](mailto:support@subgit.com). ## Cut over migration with svn2git @@ -168,4 +180,4 @@ git push --tags origin ## Contribute to this guide We welcome all contributions that would expand this guide with instructions on -how to migrate from SVN and other version control systems. \ No newline at end of file +how to migrate from SVN and other version control systems. -- cgit v1.2.1 From 1022456bb15d18b05c14fe344950fb75c7c69f48 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Fri, 7 Oct 2016 16:49:48 +0100 Subject: Allow browsing branches that end with '.atom' We need to do two things to support this: 1. Simplify the regex capture in the routing for the CommitsController to not exclude the '.atom' suffix. That's a perfectly valid git branch name, so we shouldn't blow up if we get it. 2. Because Rails now can't automatically detect the request format, add some code to do so in `ExtractPath` when there is no path. This means that, given branches 'foo' and 'foo.atom', the Atom feed for the former is unroutable. To fix this: don't do that! Give the branches different names! --- CHANGELOG | 1 + config/routes/project.rb | 2 +- lib/extracts_path.rb | 33 +++++++- .../projects/commits_controller_spec.rb | 41 +++++++--- spec/lib/extracts_path_spec.rb | 87 +++++++++++++++++++++- spec/routing/project_routing_spec.rb | 2 +- 6 files changed, 152 insertions(+), 14 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 392a356c79a..2f2396ad29f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.13.0 (unreleased) - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - Add tag shortcut from the Commit page. !6543 - Keep refs for each deployment + - Allow browsing branches that end with '.atom' - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) - Add more tests for calendar contribution (ClemMakesApps) - Cache rendered markdown in the database, rather than Redis diff --git a/config/routes/project.rb b/config/routes/project.rb index e8807ef06a7..4c1da5c4df5 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -159,7 +159,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: get( '/commits/*id', to: 'commits#show', - constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }, + constraints: { id: /.+/, format: false }, as: :commits ) end diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index a4558d157c0..e4d996a3fb6 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -52,8 +52,7 @@ module ExtractsPath # Append a trailing slash if we only get a ref and no file path id += '/' unless id.ends_with?('/') - valid_refs = @project.repository.ref_names - valid_refs.select! { |v| id.start_with?("#{v}/") } + valid_refs = ref_names.select { |v| id.start_with?("#{v}/") } if valid_refs.length == 0 # No exact ref match, so just try our best @@ -74,6 +73,19 @@ module ExtractsPath pair end + # If we have an ID of 'foo.atom', and the controller provides Atom and HTML + # formats, then we have to check if the request was for the Atom version of + # the ID without the '.atom' suffix, or the HTML version of the ID including + # the suffix. We only check this if the version including the suffix doesn't + # match, so it is possible to create a branch which has an unroutable Atom + # feed. + def extract_ref_without_atom(id) + id_without_atom = id.sub(/\.atom$/, '') + valid_refs = ref_names.select { |v| "#{id_without_atom}/".start_with?("#{v}/") } + + valid_refs.max_by(&:length) + end + # Assigns common instance variables for views working with Git tree-ish objects # # Assignments are: @@ -86,6 +98,10 @@ module ExtractsPath # If the :id parameter appears to be requesting a specific response format, # that will be handled as well. # + # If there is no path and the ref doesn't exist in the repo, try to resolve + # the ref without an '.atom' suffix. If _that_ ref is found, set the request's + # format to Atom manually. + # # Automatically renders `not_found!` if a valid tree path could not be # resolved (e.g., when a user inserts an invalid path or ref). def assign_ref_vars @@ -103,6 +119,13 @@ module ExtractsPath @commit = @repo.commit(@options[:extended_sha1]) end + if @path.empty? && !@commit + @id = @ref = extract_ref_without_atom(@id) + @commit = @repo.commit(@ref) + + request.format = :atom if @commit + end + raise InvalidPathError unless @commit @hex_path = Digest::SHA1.hexdigest(@path) @@ -125,4 +148,10 @@ module ExtractsPath id += "/" + params[:path] unless params[:path].blank? id end + + def ref_names + return [] unless @project + + @ref_names ||= @project.repository.ref_names + end end diff --git a/spec/controllers/projects/commits_controller_spec.rb b/spec/controllers/projects/commits_controller_spec.rb index 2518a48e336..1ac7e03a2db 100644 --- a/spec/controllers/projects/commits_controller_spec.rb +++ b/spec/controllers/projects/commits_controller_spec.rb @@ -10,15 +10,38 @@ describe Projects::CommitsController do end describe "GET show" do - context "as atom feed" do - it "renders as atom" do - get(:show, - namespace_id: project.namespace.to_param, - project_id: project.to_param, - id: "master", - format: "atom") - expect(response).to be_success - expect(response.content_type).to eq('application/atom+xml') + context "when the ref name ends in .atom" do + render_views + + context "when the ref does not exist with the suffix" do + it "renders as atom" do + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: "master.atom") + + expect(response).to be_success + expect(response.content_type).to eq('application/atom+xml') + end + end + + context "when the ref exists with the suffix" do + before do + commit = project.repository.commit('master') + + allow_any_instance_of(Repository).to receive(:commit).and_call_original + allow_any_instance_of(Repository).to receive(:commit).with('master.atom').and_return(commit) + + get(:show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + id: "master.atom") + end + + it "renders as HTML" do + expect(response).to be_success + expect(response.content_type).to eq('text/html') + end end end end diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index e10c1f5c547..0e85e302f29 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -6,6 +6,7 @@ describe ExtractsPath, lib: true do include Gitlab::Routing.url_helpers let(:project) { double('project') } + let(:request) { double('request') } before do @project = project @@ -15,9 +16,10 @@ describe ExtractsPath, lib: true do allow(project).to receive(:repository).and_return(repo) allow(project).to receive(:path_with_namespace). and_return('gitlab/gitlab-ci') + allow(request).to receive(:format=) end - describe '#assign_ref' do + describe '#assign_ref_vars' do let(:ref) { sample_commit[:id] } let(:params) { { path: sample_commit[:line_code_path], ref: ref } } @@ -61,6 +63,75 @@ describe ExtractsPath, lib: true do expect(@id).to eq(get_id) end end + + context 'ref only exists without .atom suffix' do + context 'with a path' do + let(:params) { { ref: 'v1.0.0.atom', path: 'README.md' } } + + it 'renders a 404' do + expect(self).to receive(:render_404) + + assign_ref_vars + end + end + + context 'without a path' do + let(:params) { { ref: 'v1.0.0.atom' } } + before { assign_ref_vars } + + it 'sets the un-suffixed version as @ref' do + expect(@ref).to eq('v1.0.0') + end + + it 'sets the request format to Atom' do + expect(request).to have_received(:format=).with(:atom) + end + end + end + + context 'ref exists with .atom suffix' do + context 'with a path' do + let(:params) { { ref: 'master.atom', path: 'README.md' } } + + before do + repository = @project.repository + allow(repository).to receive(:commit).and_call_original + allow(repository).to receive(:commit).with('master.atom').and_return(repository.commit('master')) + + assign_ref_vars + end + + it 'sets the suffixed version as @ref' do + expect(@ref).to eq('master.atom') + end + + it 'does not change the request format' do + expect(request).not_to have_received(:format=) + end + end + + context 'without a path' do + let(:params) { { ref: 'master.atom' } } + + before do + repository = @project.repository + allow(repository).to receive(:commit).and_call_original + allow(repository).to receive(:commit).with('master.atom').and_return(repository.commit('master')) + end + + it 'sets the suffixed version as @ref' do + assign_ref_vars + + expect(@ref).to eq('master.atom') + end + + it 'does not change the request format' do + expect(request).not_to receive(:format=) + + assign_ref_vars + end + end + end end describe '#extract_ref' do @@ -115,4 +186,18 @@ describe ExtractsPath, lib: true do end end end + + describe '#extract_ref_without_atom' do + it 'ignores any matching refs suffixed with atom' do + expect(extract_ref_without_atom('master.atom')).to eq('master') + end + + it 'returns the longest matching ref' do + expect(extract_ref_without_atom('release/app/v1.0.0.atom')).to eq('release/app/v1.0.0') + end + + it 'returns nil if there are no matching refs' do + expect(extract_ref_without_atom('foo.atom')).to eq(nil) + end + end end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 77842057a10..2322430d212 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -337,7 +337,7 @@ describe Projects::CommitsController, 'routing' do end it 'to #show' do - expect(get('/gitlab/gitlabhq/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'atom') + expect(get('/gitlab/gitlabhq/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master.atom') end end -- cgit v1.2.1 From 75fa98bb93a416aaf16ff832b0bcc98abbbdaa19 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 11 Oct 2016 06:30:50 -0700 Subject: Remove duplicate CHANGELOG entry --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 2095e4fc0fd..8c07ce32d08 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,7 +2,6 @@ Please view this file on the master branch, on stable branches it's out of date. v 8.13.0 (unreleased) - Truncate long labels with ellipsis in labels page - - Bump mail_room to v0.8.1 to fix thread cleanup issue - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - Improve issue load time performance by avoiding ORDER BY in find_by call -- cgit v1.2.1 From ce8aedf030f0980b3712a3b0090b918fa1451a2d Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 15:06:46 +0200 Subject: Add examples of fake tokens to be used in docs [ci skip] --- doc/development/doc_styleguide.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/doc/development/doc_styleguide.md b/doc/development/doc_styleguide.md index 39b801f761d..0b725cf200c 100644 --- a/doc/development/doc_styleguide.md +++ b/doc/development/doc_styleguide.md @@ -314,6 +314,29 @@ In this case: - different highlighting languages are used for each config in the code block - the [references](#references) guide is used for reconfigure/restart +## Fake tokens + +There may be times where a token is needed to demonstrate an API call using +cURL or a secret variable used in CI. It is strongly advised not to use real +tokens in documentation even if the probability of a token being exploited is +low. + +You can use the following fake tokens as examples. + +| **Token type** | **Token value** | +| --------------------- | --------------------------------- | +| Private user token | `9koXpg98eAheJpvBs5tK` | +| Personal access token | `n671WNGecHugsdEDPsyo` | +| Application ID | `2fcb195768c39e9a94cec2c2e32c59c0aad7a3365c10892e8116b5d83d4096b6` | +| Application secret | `04f294d1eaca42b8692017b426d53bbc8fe75f827734f0260710b83a556082df` | +| Secret CI variable | `Li8j-mLUVA3eZYjPfd_H` | +| Specific Runner token | `yrnZW46BrtBFqM7xDzE7dddd` | +| Shared Runner token | `6Vk7ZsosqQyfreAxXTZr` | +| Trigger token | `be20d8dcc028677c931e04f3871a9b` | +| Webhook secret token | `6XhDroRcYPM5by_h-HLY` | +| Health check token | `Tu7BgjR9qeZTEyRzGG2P` | +| Request profile token | `7VgpS4Ax5utVD2esNstz` | + ## API Here is a list of must-have items. Use them in the exact order that appears -- cgit v1.2.1 From 8caf097a162cc43cea59162600bbb1fbc981f0bc Mon Sep 17 00:00:00 2001 From: henrik Date: Tue, 11 Oct 2016 15:41:10 +0200 Subject: Convert unicode emojis to images. --- CHANGELOG | 1 + app/models/merge_request_diff.rb | 14 +++++- doc/ci/pipelines.md | 2 + doc/ci/variables/README.md | 34 ++++++++++++++ lib/banzai/filter/emoji_filter.rb | 54 ++++++++++++++++++---- lib/gitlab/emoji.rb | 7 ++- spec/lib/banzai/filter/emoji_filter_spec.rb | 71 ++++++++++++++++++++++++++++- spec/models/merge_request_diff_spec.rb | 10 ++++ 8 files changed, 181 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 06dc2993b73..2095e4fc0fd 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.13.0 (unreleased) - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) + - Fix Error 500 when viewing old merge requests with bad diff data - Speed-up group milestones show page - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 36b8b70870b..3f7e96186a1 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -6,6 +6,9 @@ class MergeRequestDiff < ActiveRecord::Base # Prevent store of diff if commits amount more then 500 COMMITS_SAFE_SIZE = 100 + # Valid types of serialized diffs allowed by Gitlab::Git::Diff + VALID_CLASSES = [Hash, Rugged::Patch, Rugged::Diff::Delta] + belongs_to :merge_request state_machine :state, initial: :empty do @@ -170,6 +173,15 @@ class MergeRequestDiff < ActiveRecord::Base private + # Old GitLab implementations may have generated diffs as ["--broken-diff"]. + # Avoid an error 500 by ignoring bad elements. See: + # https://gitlab.com/gitlab-org/gitlab-ce/issues/20776 + def valid_raw_diff?(raw) + return false unless raw.respond_to?(:each) + + raw.any? { |element| VALID_CLASSES.include?(element.class) } + end + def dump_commits(commits) commits.map(&:to_hash) end @@ -200,7 +212,7 @@ class MergeRequestDiff < ActiveRecord::Base end def load_diffs(raw, options) - if raw.respond_to?(:each) + if valid_raw_diff?(raw) if paths = options[:paths] raw = raw.select do |diff| paths.include?(diff[:old_path]) || paths.include?(diff[:new_path]) diff --git a/doc/ci/pipelines.md b/doc/ci/pipelines.md index ca9b986a060..729c1dc8c0d 100644 --- a/doc/ci/pipelines.md +++ b/doc/ci/pipelines.md @@ -31,6 +31,8 @@ project. ## Seeing build status Clicking on a pipeline will show the builds that were run for that pipeline. +Clicking on an individual build will show you its build trace, and allow you to +cancel the build, retry it, or erase the build trace. ## Badges diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 22d67bd9964..a4c3a731a20 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -48,6 +48,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used | | **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab | | **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags | +| **CI_DEBUG_TRACE** | all | 1.7 | Whether [debug tracing](#debug-tracing) is enabled | | **GITLAB_USER_ID** | 8.12 | all | The id of the user who started the build | | **GITLAB_USER_EMAIL** | 8.12 | all | The email of the user who started the build | @@ -105,6 +106,39 @@ Variables can be defined at a global level, but also at a job level. More information about Docker integration can be found in [Using Docker Images](../docker/using_docker_images.md). +#### Debug tracing + +> **WARNING:** Enabling debug tracing can have severe security implications. The + output **will** contain the content of all your secure variables and any other + secrets! The output **will** be uploaded to the GitLab server and made visible + in build traces! + +By default, GitLab Runner hides most of the details of what it is doing when +processing a job. This behaviour keeps build traces short, and prevents secrets +from being leaked into the trace unless your script writes them to the screen. + +If a job isn't working as expected, this can make the problem difficult to +investigate; in these cases, you can enable debug tracing in `.gitlab-ci.yml`. +Available on GitLab Runner v1.7+, this feature enables the shell's execution +trace, resulting in a verbose build trace listing all commands that were run, +variables that were set, etc. + +Before enabling this, you should ensure builds are visible to +[team members only](../../../user/permissions.md#project-features). You should +also [erase](../pipelines.md#seeing-build-traces) all generated build traces +before making them visible again. + +To enable debug traces, set the `CI_DEBUG_TRACE` variable to `true`: + +```yaml +job1: + variables: + CI_DEBUG_TRACE: "true" +``` + +The [example project](https://gitlab.com/gitlab-examples/ci-debug-trace) +demonstrates a working configuration, including build trace examples. + ### User-defined variables (Secure Variables) **This feature requires GitLab Runner 0.4.0 or higher** diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb index 2492b5213ac..23ae6dfc79a 100644 --- a/lib/banzai/filter/emoji_filter.rb +++ b/lib/banzai/filter/emoji_filter.rb @@ -1,6 +1,6 @@ module Banzai module Filter - # HTML filter that replaces :emoji: with images. + # HTML filter that replaces :emoji: and unicode with images. # # Based on HTML::Pipeline::EmojiFilter # @@ -13,14 +13,14 @@ module Banzai def call search_text_nodes(doc).each do |node| content = node.to_html - next unless content.include?(':') next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS) + if content.include?(':') || node.text.match(emoji_unicode_pattern) + html = emoji_name_image_filter(content) + html = emoji_unicode_image_filter(html) + next if html == content + node.replace(html) + end - html = emoji_image_filter(content) - - next if html == content - - node.replace(html) end doc @@ -31,18 +31,34 @@ module Banzai # text - String text to replace :emoji: in. # # Returns a String with :emoji: replaced with images. - def emoji_image_filter(text) + def emoji_name_image_filter(text) text.gsub(emoji_pattern) do |match| name = $1 ":#{name}:" end end + + # Replace unicode emojis with corresponding images if they exist. + # + # text - String text to replace unicode emojis in. + # + # Returns a String with unicode emojis replaced with images. + + def emoji_unicode_image_filter(text) + text.gsub(emoji_unicode_pattern) do |moji| + ":#{Gitlab::Emoji.emojis_by_moji[moji][" + 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 + # Build a regexp that matches all valid unicode emojis names. + def self.emoji_unicode_pattern + @emoji_unicode_pattern ||= /(#{Gitlab::Emoji.emojis_unicodes.map { |moji| Regexp.escape(moji) }.join('|')})/ + end private def emoji_url(name) @@ -60,6 +76,21 @@ module Banzai end end + def emoji_unicode_url(moji) + emoji_unicode_path = emoji_unicode_filename(moji) + + if context[:asset_host] + # Asset host is specified. + url_to_image(emoji_unicode_path) + elsif context[:asset_root] + # Gitlab url is specified + File.join(context[:asset_root], url_to_image(emoji_unicode_path)) + else + # All other cases + url_to_image(emoji_unicode_path) + end + end + def url_to_image(image) ActionController::Base.helpers.url_to_image(image) end @@ -71,6 +102,13 @@ module Banzai def emoji_filename(name) "#{Gitlab::Emoji.emoji_filename(name)}.png" end + def emoji_unicode_pattern + self.class.emoji_unicode_pattern + end + + def emoji_unicode_filename(name) + "#{Gitlab::Emoji.emoji_unicode_filename(name)}.png" + end end end end diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb index b63213ae208..803b0c2d057 100644 --- a/lib/gitlab/emoji.rb +++ b/lib/gitlab/emoji.rb @@ -9,7 +9,9 @@ module Gitlab def emojis_by_moji Gemojione.index.instance_variable_get(:@emoji_by_moji) end - + def emojis_unicodes + emojis_by_moji.keys.sort + end def emojis_names emojis.keys.sort end @@ -17,5 +19,8 @@ module Gitlab def emoji_filename(name) emojis[name]["unicode"] end + def emoji_unicode_filename(moji) + emojis_by_moji[moji]["unicode"] + end end end diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb index b5b38cf0c8c..475160bb5ec 100644 --- a/spec/lib/banzai/filter/emoji_filter_spec.rb +++ b/spec/lib/banzai/filter/emoji_filter_spec.rb @@ -12,11 +12,14 @@ describe Banzai::Filter::EmojiFilter, lib: true do ActionController::Base.asset_host = @original_asset_host end - it 'replaces supported emoji' do + it 'replaces supported name emoji' do doc = filter('

:heart:

') expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' end - + it 'replaces supported unicode emoji' do + doc = filter('

❤️

') + expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' + end it 'ignores unsupported emoji' do exp = act = '

:foo:

' doc = filter(act) @@ -28,46 +31,96 @@ describe Banzai::Filter::EmojiFilter, lib: true do expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png' end + it 'correctly encodes unicode to the URL' do + doc = filter('

👍

') + expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/1F44D.png' + end + it 'matches at the start of a string' do doc = filter(':+1:') expect(doc.css('img').size).to eq 1 end + it 'unicode matches at the start of a string' do + doc = filter("'👍'") + expect(doc.css('img').size).to eq 1 + end + it 'matches at the end of a string' do doc = filter('This gets a :-1:') expect(doc.css('img').size).to eq 1 end + it 'unicode matches at the end of a string' do + doc = filter('This gets a 👍') + expect(doc.css('img').size).to eq 1 + end + it 'matches with adjacent text' do doc = filter('+1 (:+1:)') expect(doc.css('img').size).to eq 1 end + it 'unicode matches with adjacent text' do + doc = filter('+1 (👍)') + expect(doc.css('img').size).to eq 1 + end + it 'matches multiple emoji in a row' do doc = filter(':see_no_evil::hear_no_evil::speak_no_evil:') expect(doc.css('img').size).to eq 3 end + it 'unicode matches multiple emoji in a row' do + doc = filter("'🙈🙉🙊'") + expect(doc.css('img').size).to eq 3 + end + + it 'mixed matches multiple emoji in a row' do + doc = filter("'🙈:see_no_evil:🙉:hear_no_evil:🙊:speak_no_evil:'") + expect(doc.css('img').size).to eq 6 + end + it 'has a title attribute' do doc = filter(':-1:') expect(doc.css('img').first.attr('title')).to eq ':-1:' end + it 'unicode has a title attribute' do + doc = filter("'👎'") + expect(doc.css('img').first.attr('title')).to eq ':thumbsdown:' + end + it 'has an alt attribute' do doc = filter(':-1:') expect(doc.css('img').first.attr('alt')).to eq ':-1:' end + it 'unicode has an alt attribute' do + doc = filter("'👎'") + expect(doc.css('img').first.attr('alt')).to eq ':thumbsdown:' + end + it 'has an align attribute' do doc = filter(':8ball:') expect(doc.css('img').first.attr('align')).to eq 'absmiddle' end + it 'unicode has an align attribute' do + doc = filter("'🎱'") + expect(doc.css('img').first.attr('align')).to eq 'absmiddle' + end + it 'has an emoji class' do doc = filter(':cat:') expect(doc.css('img').first.attr('class')).to eq 'emoji' end + it 'unicode has an emoji class' do + doc = filter("'🐱'") + expect(doc.css('img').first.attr('class')).to eq 'emoji' + end + it 'has height and width attributes' do doc = filter(':dog:') img = doc.css('img').first @@ -76,12 +129,26 @@ describe Banzai::Filter::EmojiFilter, lib: true do expect(img.attr('height')).to eq '20' end + it 'unicode has height and width attributes' do + doc = filter("'🐶'") + img = doc.css('img').first + + expect(img.attr('width')).to eq '20' + expect(img.attr('height')).to eq '20' + end + it 'keeps whitespace intact' do doc = filter('This deserves a :+1:, big time.') expect(doc.to_html).to match(/^This deserves a , big time\.\z/) end + it 'unicode keeps whitespace intact' do + doc = filter('This deserves a 🎱, big time.') + + expect(doc.to_html).to match(/^This deserves a , big time\.\z/) + end + it 'uses a custom asset_root context' do root = Gitlab.config.gitlab.url + 'gitlab/root' diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 530a7def553..96f1f60dbc0 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -44,6 +44,16 @@ describe MergeRequestDiff, models: true do end end + context 'when the raw diffs have invalid content' do + before { mr_diff.update_attributes(st_diffs: ["--broken-diff"]) } + + it 'returns an empty DiffCollection' do + expect(mr_diff.raw_diffs.to_a).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.raw_diffs).to be_a(Gitlab::Git::DiffCollection) -- cgit v1.2.1 From b4004488f76d7360acd2f38277d617447c76b888 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Tue, 4 Oct 2016 15:52:08 +0300 Subject: Make guests unable to view MRs --- CHANGELOG | 1 + app/models/event.rb | 8 ++- app/policies/project_policy.rb | 3 +- app/services/notification_service.rb | 6 ++- app/services/todo_service.rb | 6 +-- doc/user/permissions.md | 1 + .../projects/guest_navigation_menu_spec.rb | 28 +++++++++++ .../security/project/private_access_spec.rb | 2 +- spec/models/event_spec.rb | 11 +++++ spec/policies/project_policy_spec.rb | 5 +- .../services/merge_requests/update_service_spec.rb | 6 +++ spec/services/notification_service_spec.rb | 57 +++++++++++++++++++++- spec/services/todo_service_spec.rb | 22 ++++++++- 13 files changed, 143 insertions(+), 13 deletions(-) create mode 100644 spec/features/projects/guest_navigation_menu_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 2095e4fc0fd..9653c4da17f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -93,6 +93,7 @@ v 8.13.0 (unreleased) - Cleanup Ci::ApplicationController. !6757 (Takuya Noguchi) - Fix a typo in doc/api/labels.md - API: all unknown routing will be handled with 404 Not Found + - Make guests unable to view MRs on private projects v 8.12.5 (unreleased) - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread diff --git a/app/models/event.rb b/app/models/event.rb index 314d5ba438f..0764cb8cabd 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -68,8 +68,10 @@ class Event < ActiveRecord::Base true elsif issue? || issue_note? Ability.allowed?(user, :read_issue, note? ? note_target : target) + elsif merge_request? || merge_request_note? + Ability.allowed?(user, :read_merge_request, note? ? note_target : target) else - ((merge_request? || note?) && target.present?) || milestone? + milestone? end end @@ -280,6 +282,10 @@ class Event < ActiveRecord::Base note? && target && target.for_issue? end + def merge_request_note? + note? && target && target.for_merge_request? + end + def project_snippet_note? target.for_snippet? end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index a806cf83782..be4721d7a51 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -40,7 +40,6 @@ class ProjectPolicy < BasePolicy can! :read_milestone can! :read_project_snippet can! :read_project_member - can! :read_merge_request can! :read_note can! :create_project can! :create_issue @@ -63,6 +62,7 @@ class ProjectPolicy < BasePolicy can! :read_pipeline can! :read_environment can! :read_deployment + can! :read_merge_request end # Permissions given when an user is team member of a project @@ -117,6 +117,7 @@ class ProjectPolicy < BasePolicy can! :read_container_image can! :build_download_code can! :build_read_container_image + can! :read_merge_request end def owner_access! diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index de8049b8e2e..72712afc07e 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -475,10 +475,12 @@ class NotificationService end def reject_users_without_access(recipients, target) - return recipients unless target.is_a?(Issue) + return recipients unless target.is_a?(Issuable) + + ability = :"read_#{target.to_ability_name}" recipients.select do |user| - user.can?(:read_issue, target) + user.can?(ability, target) end end diff --git a/app/services/todo_service.rb b/app/services/todo_service.rb index 776530ac0a5..f8e6b2ef094 100644 --- a/app/services/todo_service.rb +++ b/app/services/todo_service.rb @@ -273,12 +273,12 @@ class TodoService end def reject_users_without_access(users, project, target) - if target.is_a?(Note) && target.for_issue? + if target.is_a?(Note) && (target.for_issue? || target.for_merge_request?) target = target.noteable end - if target.is_a?(Issue) - select_users(users, :read_issue, target) + if target.is_a?(Issuable) + select_users(users, :"read_#{target.to_ability_name}", target) else select_users(users, :read_project, project) end diff --git a/doc/user/permissions.md b/doc/user/permissions.md index c0dc80325b6..d6216a8dd50 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -32,6 +32,7 @@ The following table depicts the various user permission levels in a project. | See a commit status | | ✓ | ✓ | ✓ | ✓ | | See a container registry | | ✓ | ✓ | ✓ | ✓ | | See environments | | ✓ | ✓ | ✓ | ✓ | +| See a list of merge requests | | ✓ | ✓ | ✓ | ✓ | | Manage/Accept merge requests | | | ✓ | ✓ | ✓ | | Create new merge request | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ | diff --git a/spec/features/projects/guest_navigation_menu_spec.rb b/spec/features/projects/guest_navigation_menu_spec.rb new file mode 100644 index 00000000000..c22441f8929 --- /dev/null +++ b/spec/features/projects/guest_navigation_menu_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe "Guest navigation menu" do + let(:project) { create :empty_project, :private } + let(:guest) { create :user } + + before do + project.team << [guest, :guest] + + login_as(guest) + end + + it "shows allowed tabs only" do + visit namespace_project_path(project.namespace, project) + + within(".nav-links") do + expect(page).to have_content 'Project' + expect(page).to have_content 'Activity' + expect(page).to have_content 'Issues' + expect(page).to have_content 'Wiki' + + expect(page).not_to have_content 'Repository' + expect(page).not_to have_content 'Pipelines' + expect(page).not_to have_content 'Graphs' + expect(page).not_to have_content 'Merge Requests' + end + end +end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index ccb5c06dab0..79417c769a8 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -203,7 +203,7 @@ describe "Private Project Access", feature: true do it { is_expected.to be_allowed_for master } it { is_expected.to be_allowed_for developer } it { is_expected.to be_allowed_for reporter } - it { is_expected.to be_allowed_for guest } + it { is_expected.to be_denied_for guest } it { is_expected.to be_denied_for :user } it { is_expected.to be_denied_for :external } it { is_expected.to be_denied_for :visitor } diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index af5002487cc..06cac929bbf 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -135,6 +135,17 @@ describe Event, models: true do it { expect(event.visible_to_user?(member)).to eq true } it { expect(event.visible_to_user?(guest)).to eq true } it { expect(event.visible_to_user?(admin)).to eq true } + + context 'private project' do + let(:project) { create(:project, :private) } + + it { expect(event.visible_to_user?(non_member)).to eq false } + it { expect(event.visible_to_user?(author)).to eq true } + it { expect(event.visible_to_user?(assignee)).to eq true } + it { expect(event.visible_to_user?(member)).to eq true } + it { expect(event.visible_to_user?(guest)).to eq false } + it { expect(event.visible_to_user?(admin)).to eq true } + end end end diff --git a/spec/policies/project_policy_spec.rb b/spec/policies/project_policy_spec.rb index 43c8d884a47..658e3c13a73 100644 --- a/spec/policies/project_policy_spec.rb +++ b/spec/policies/project_policy_spec.rb @@ -12,7 +12,7 @@ describe ProjectPolicy, models: true do [ :read_project, :read_board, :read_list, :read_wiki, :read_issue, :read_label, :read_milestone, :read_project_snippet, :read_project_member, - :read_merge_request, :read_note, :create_project, :create_issue, :create_note, + :read_note, :create_project, :create_issue, :create_note, :upload_file ] end @@ -21,7 +21,8 @@ describe ProjectPolicy, models: true do [ :download_code, :fork_project, :create_project_snippet, :update_issue, :admin_issue, :admin_label, :admin_list, :read_commit_status, :read_build, - :read_container_image, :read_pipeline, :read_environment, :read_deployment + :read_container_image, :read_pipeline, :read_environment, :read_deployment, + :read_merge_request ] end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 33db34c0f62..fd5f94047c2 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -17,6 +17,7 @@ describe MergeRequests::UpdateService, services: true do before do project.team << [user, :master] project.team << [user2, :developer] + project.team << [user3, :developer] end describe 'execute' do @@ -188,6 +189,11 @@ describe MergeRequests::UpdateService, services: true do let!(:non_subscriber) { create(:user) } let!(:subscriber) { create(:user).tap { |u| label.toggle_subscription(u) } } + before do + project.team << [non_subscriber, :developer] + project.team << [subscriber, :developer] + end + it 'sends notifications for subscribers of newly added labels' do opts = { label_ids: [label.id] } diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index d820646ebdf..699b9925b4e 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -331,7 +331,7 @@ describe NotificationService, services: true do describe '#new_note' do it "records sent notifications" do # Ensure create SentNotification by noteable = merge_request 6 times, not noteable = note - expect(SentNotification).to receive(:record_note).with(note, any_args).exactly(4).times.and_call_original + expect(SentNotification).to receive(:record_note).with(note, any_args).exactly(3).times.and_call_original notification.new_note(note) @@ -1169,6 +1169,61 @@ describe NotificationService, services: true do end end + context 'guest user in private project' do + let(:private_project) { create(:empty_project, :private) } + let(:guest) { create(:user) } + let(:developer) { create(:user) } + let(:assignee) { create(:user) } + let(:merge_request) { create(:merge_request, source_project: private_project, assignee: assignee) } + let(:merge_request1) { create(:merge_request, source_project: private_project, assignee: assignee, description: "cc @#{guest.username}") } + let(:note) { create(:note, noteable: merge_request, project: private_project) } + + before do + private_project.team << [assignee, :developer] + private_project.team << [developer, :developer] + private_project.team << [guest, :guest] + + ActionMailer::Base.deliveries.clear + end + + it 'filters out guests when new note is created' do + expect(SentNotification).to receive(:record).with(merge_request, any_args).exactly(1).times + + notification.new_note(note) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when new merge request is created' do + notification.new_merge_request(merge_request1, @u_disabled) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when merge request is closed' do + notification.close_mr(merge_request, developer) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when merge request is reopened' do + notification.reopen_mr(merge_request, developer) + + should_not_email(guest) + should_email(assignee) + end + + it 'filters out guests when merge request is merged' do + notification.merge_mr(merge_request, developer) + + should_not_email(guest) + should_email(assignee) + end + end + def build_team(project) @u_watcher = create_global_setting_for(create(:user), :watch) @u_participating = create_global_setting_for(create(:user), :participating) diff --git a/spec/services/todo_service_spec.rb b/spec/services/todo_service_spec.rb index b41f6f14fbd..ed55791d24e 100644 --- a/spec/services/todo_service_spec.rb +++ b/spec/services/todo_service_spec.rb @@ -345,7 +345,7 @@ describe TodoService, services: true do service.new_merge_request(mr_assigned, author) should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) - should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) @@ -357,7 +357,7 @@ describe TodoService, services: true do service.update_merge_request(mr_assigned, author) should_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) - should_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_create_todo(user: author, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) @@ -381,6 +381,7 @@ describe TodoService, services: true do should_not_create_todo(user: john_doe, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: member, target: mr_assigned, action: Todo::MENTIONED) should_not_create_todo(user: non_member, target: mr_assigned, action: Todo::MENTIONED) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) end it 'does not raise an error when description not change' do @@ -430,6 +431,11 @@ describe TodoService, services: true do should_create_todo(user: john_doe, target: mr_assigned, author: john_doe, action: Todo::ASSIGNED) end + + it 'does not create a todo for guests' do + service.reassigned_merge_request(mr_assigned, author) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + end end describe '#merge_merge_request' do @@ -441,6 +447,11 @@ describe TodoService, services: true do expect(first_todo.reload).to be_done expect(second_todo.reload).to be_done end + + it 'does not create todo for guests' do + service.merge_merge_request(mr_assigned, john_doe) + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + end end describe '#new_award_emoji' do @@ -495,6 +506,13 @@ describe TodoService, services: true do should_create_todo(user: john_doe, target: mr_unassigned, author: author, action: Todo::MENTIONED, note: legacy_diff_note_on_merge_request) end + + it 'does not create todo for guests' do + note_on_merge_request = create :note_on_merge_request, project: project, noteable: mr_assigned, note: mentions + service.new_note(note_on_merge_request, author) + + should_not_create_todo(user: guest, target: mr_assigned, action: Todo::MENTIONED) + end end end -- cgit v1.2.1 From 1bbe1cea919d174b9bc1bdd39e6235ffadcccb50 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Tue, 11 Oct 2016 15:55:32 +0200 Subject: Move health check docs under user/admin_area/monitoring [ci skip] --- doc/README.md | 2 +- doc/administration/monitoring/health_check.md | 66 --------------------- .../monitoring/img/health_check_token.png | Bin 6630 -> 0 bytes doc/monitoring/health_check.md | 2 +- doc/user/admin_area/monitoring/health_check.md | 66 +++++++++++++++++++++ .../monitoring/img/health_check_token.png | Bin 0 -> 6630 bytes 6 files changed, 68 insertions(+), 68 deletions(-) delete mode 100644 doc/administration/monitoring/health_check.md delete mode 100644 doc/administration/monitoring/img/health_check_token.png create mode 100644 doc/user/admin_area/monitoring/health_check.md create mode 100644 doc/user/admin_area/monitoring/img/health_check_token.png diff --git a/doc/README.md b/doc/README.md index 04f713a6c5f..effd92b43df 100644 --- a/doc/README.md +++ b/doc/README.md @@ -48,7 +48,7 @@ - [Git LFS configuration](workflow/lfs/lfs_administration.md) - [Housekeeping](administration/housekeeping.md) Keep your Git repository tidy and fast. - [GitLab Performance Monitoring](administration/monitoring/performance/introduction.md) Configure GitLab and InfluxDB for measuring performance metrics. -- [Monitoring uptime](administration/monitoring/health_check.md) Check the server status using the health check endpoint. +- [Monitoring uptime](user/admin_area/monitoring/health_check.md) Check the server status using the health check endpoint. - [Debugging Tips](administration/troubleshooting/debug.md) Tips to debug problems when things go wrong - [Sidekiq Troubleshooting](administration/troubleshooting/sidekiq.md) Debug when Sidekiq appears hung and is not processing jobs. - [High Availability](administration/high_availability/README.md) Configure multiple servers for scaling or high availability. diff --git a/doc/administration/monitoring/health_check.md b/doc/administration/monitoring/health_check.md deleted file mode 100644 index eac57bc3de4..00000000000 --- a/doc/administration/monitoring/health_check.md +++ /dev/null @@ -1,66 +0,0 @@ -# Health Check - -> [Introduced][ce-3888] in GitLab 8.8. - -GitLab provides a health check endpoint for uptime monitoring on the `health_check` web -endpoint. The health check reports on the overall system status based on the status of -the database connection, the state of the database migrations, and the ability to write -and access the cache. This endpoint can be provided to uptime monitoring services like -[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. - -## Access Token - -An access token needs to be provided while accessing the health check endpoint. The current -accepted token can be found on the `admin/health_check` page of your GitLab instance. - -![access token](img/health_check_token.png) - -The access token can be passed as a URL parameter: - -``` -https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN -``` - -or as an HTTP header: - -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` - -## Using the Endpoint - -Once you have the access token, health information can be retrieved as plain text, JSON, -or XML using the `health_check` endpoint: - -- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` - -You can also ask for the status of specific services: - -- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` -- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` - -For example, the JSON output of the following health check: - -```bash -curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json -``` - -would be like: - -``` -{"healthy":true,"message":"success"} -``` - -## Status - -On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint -will return a valid successful HTTP status code, and a `success` message. Ideally your -uptime monitoring should look for the success message. - -[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 -[pingdom]: https://www.pingdom.com -[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html -[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring diff --git a/doc/administration/monitoring/img/health_check_token.png b/doc/administration/monitoring/img/health_check_token.png deleted file mode 100644 index 2d7c82a65a8..00000000000 Binary files a/doc/administration/monitoring/img/health_check_token.png and /dev/null differ diff --git a/doc/monitoring/health_check.md b/doc/monitoring/health_check.md index 23ae1b17258..6cf93c33ec2 100644 --- a/doc/monitoring/health_check.md +++ b/doc/monitoring/health_check.md @@ -1 +1 @@ -This document was moved to [administration/monitoring/health_check](../administration/monitoring/health_check.md). +This document was moved to [user/admin_area/monitoring/health_check](../user/admin_area/monitoring/health_check.md). diff --git a/doc/user/admin_area/monitoring/health_check.md b/doc/user/admin_area/monitoring/health_check.md new file mode 100644 index 00000000000..eac57bc3de4 --- /dev/null +++ b/doc/user/admin_area/monitoring/health_check.md @@ -0,0 +1,66 @@ +# Health Check + +> [Introduced][ce-3888] in GitLab 8.8. + +GitLab provides a health check endpoint for uptime monitoring on the `health_check` web +endpoint. The health check reports on the overall system status based on the status of +the database connection, the state of the database migrations, and the ability to write +and access the cache. This endpoint can be provided to uptime monitoring services like +[Pingdom][pingdom], [Nagios][nagios-health], and [NewRelic][newrelic-health]. + +## Access Token + +An access token needs to be provided while accessing the health check endpoint. The current +accepted token can be found on the `admin/health_check` page of your GitLab instance. + +![access token](img/health_check_token.png) + +The access token can be passed as a URL parameter: + +``` +https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN +``` + +or as an HTTP header: + +```bash +curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json +``` + +## Using the Endpoint + +Once you have the access token, health information can be retrieved as plain text, JSON, +or XML using the `health_check` endpoint: + +- `https://gitlab.example.com/health_check?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check.xml?token=ACCESS_TOKEN` + +You can also ask for the status of specific services: + +- `https://gitlab.example.com/health_check/cache.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check/database.json?token=ACCESS_TOKEN` +- `https://gitlab.example.com/health_check/migrations.json?token=ACCESS_TOKEN` + +For example, the JSON output of the following health check: + +```bash +curl --header "TOKEN: ACCESS_TOKEN" https://gitlab.example.com/health_check.json +``` + +would be like: + +``` +{"healthy":true,"message":"success"} +``` + +## Status + +On failure, the endpoint will return a `500` HTTP status code. On success, the endpoint +will return a valid successful HTTP status code, and a `success` message. Ideally your +uptime monitoring should look for the success message. + +[ce-3888]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/3888 +[pingdom]: https://www.pingdom.com +[nagios-health]: https://nagios-plugins.org/doc/man/check_http.html +[newrelic-health]: https://docs.newrelic.com/docs/alerts/alert-policies/downtime-alerts/availability-monitoring diff --git a/doc/user/admin_area/monitoring/img/health_check_token.png b/doc/user/admin_area/monitoring/img/health_check_token.png new file mode 100644 index 00000000000..2d7c82a65a8 Binary files /dev/null and b/doc/user/admin_area/monitoring/img/health_check_token.png differ -- cgit v1.2.1 From dd11c8f5c6984aed434c73197b9b592eee2646e1 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 11 Oct 2016 09:10:12 -0500 Subject: Fix overflow to show grouped builds arrow --- app/assets/stylesheets/pages/pipelines.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 05f59279637..28e850767b7 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -421,7 +421,6 @@ right: -197px; top: -9px; max-height: 245px; - overflow-y: scroll; a { color: $gl-text-color; -- cgit v1.2.1 From d8f33c0a51d9106ece6cd4bae469e40734e05f85 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Sun, 25 Sep 2016 12:44:09 +0200 Subject: Move operations/ to new location [ci skip] --- doc/README.md | 2 +- doc/administration/operations.md | 6 + .../operations/cleaning_up_redis_sessions.md | 52 ++++++ .../operations/moving_repositories.md | 180 ++++++++++++++++++++ .../operations/sidekiq_memory_killer.md | 40 +++++ doc/administration/operations/unicorn.md | 86 ++++++++++ doc/operations/README.md | 6 +- doc/operations/cleaning_up_redis_sessions.md | 53 +----- doc/operations/moving_repositories.md | 181 +-------------------- doc/operations/sidekiq_memory_killer.md | 41 +---- doc/operations/unicorn.md | 87 +--------- 11 files changed, 370 insertions(+), 364 deletions(-) create mode 100644 doc/administration/operations.md create mode 100644 doc/administration/operations/cleaning_up_redis_sessions.md create mode 100644 doc/administration/operations/moving_repositories.md create mode 100644 doc/administration/operations/sidekiq_memory_killer.md create mode 100644 doc/administration/operations/unicorn.md diff --git a/doc/README.md b/doc/README.md index dd0eb97489e..ed2d09bedec 100644 --- a/doc/README.md +++ b/doc/README.md @@ -34,7 +34,7 @@ - [Libravatar](customization/libravatar.md) Use Libravatar instead of Gravatar for user avatars. - [Log system](administration/logs.md) Log system. - [Environment Variables](administration/environment_variables.md) to configure GitLab. -- [Operations](operations/README.md) Keeping GitLab up and running. +- [Operations](administration/operations.md) Keeping GitLab up and running. - [Raketasks](raketasks/README.md) Backups, maintenance, automatic webhook setup and the importing of projects. - [Repository checks](administration/repository_checks.md) Periodic Git repository checks. - [Repository storages](administration/repository_storages.md) Manage the paths used to store repositories. diff --git a/doc/administration/operations.md b/doc/administration/operations.md new file mode 100644 index 00000000000..4b582d16b64 --- /dev/null +++ b/doc/administration/operations.md @@ -0,0 +1,6 @@ +# GitLab operations + +- [Sidekiq MemoryKiller](operations/sidekiq_memory_killer.md) +- [Cleaning up Redis sessions](operations/cleaning_up_redis_sessions.md) +- [Understanding Unicorn and unicorn-worker-killer](operations/unicorn.md) +- [Moving repositories to a new location](operations/moving_repositories.md) diff --git a/doc/administration/operations/cleaning_up_redis_sessions.md b/doc/administration/operations/cleaning_up_redis_sessions.md new file mode 100644 index 00000000000..93521e976d5 --- /dev/null +++ b/doc/administration/operations/cleaning_up_redis_sessions.md @@ -0,0 +1,52 @@ +# Cleaning up stale Redis sessions + +Since version 6.2, GitLab stores web user sessions as key-value pairs in Redis. +Prior to GitLab 7.3, user sessions did not automatically expire from Redis. If +you have been running a large GitLab server (thousands of users) since before +GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis +database after you upgrade to GitLab 7.3. You can also perform a cleanup while +still running GitLab 7.2 or older, but in that case new stale sessions will +start building up again after you clean up. + +In GitLab versions prior to 7.3.0, the session keys in Redis are 16-byte +hexadecimal values such as '976aa289e2189b17d7ef525a6702ace9'. Starting with +GitLab 7.3.0, the keys are +prefixed with 'session:gitlab:', so they would look like +'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to +remove the keys in the old format. + +First we define a shell function with the proper Redis connection details. + +``` +rcli() { + # This example works for Omnibus installations of GitLab 7.3 or newer. For an + # installation from source you will have to change the socket path and the + # path to redis-cli. + sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@" +} + +# test the new shell function; the response should be PONG +rcli ping +``` + +Now we do a search to see if there are any session keys in the old format for +us to clean up. + +``` +# returns the number of old-format session keys in Redis +rcli keys '*' | grep '^[a-f0-9]\{32\}$' | wc -l +``` + +If the number is larger than zero, you can proceed to expire the keys from +Redis. If the number is zero there is nothing to clean up. + +``` +# Tell Redis to expire each matched key after 600 seconds. +rcli keys '*' | grep '^[a-f0-9]\{32\}$' | awk '{ print "expire", $0, 600 }' | rcli +# This will print '(integer) 1' for each key that gets expired. +``` + +Over the next 15 minutes (10 minutes expiry time plus 5 minutes Redis +background save interval) your Redis database will be compacted. If you are +still using GitLab 7.2, users who are not clicking around in GitLab during the +10 minute expiry window will be signed out of GitLab. diff --git a/doc/administration/operations/moving_repositories.md b/doc/administration/operations/moving_repositories.md new file mode 100644 index 00000000000..54adb99386a --- /dev/null +++ b/doc/administration/operations/moving_repositories.md @@ -0,0 +1,180 @@ +# Moving repositories managed by GitLab + +Sometimes you need to move all repositories managed by GitLab to +another filesystem or another server. In this document we will look +at some of the ways you can copy all your repositories from +`/var/opt/gitlab/git-data/repositories` to `/mnt/gitlab/repositories`. + +We will look at three scenarios: the target directory is empty, the +target directory contains an outdated copy of the repositories, and +how to deal with thousands of repositories. + +**Each of the approaches we list can/will overwrite data in the +target directory `/mnt/gitlab/repositories`. Do not mix up the +source and the target.** + +## Target directory is empty: use a tar pipe + +If the target directory `/mnt/gitlab/repositories` is empty the +simplest thing to do is to use a tar pipe. This method has low +overhead and tar is almost always already installed on your system. +However, it is not possible to resume an interrupted tar pipe: if +that happens then all data must be copied again. + +``` +# As the git user +tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ + tar -C /mnt/gitlab/repositories -xf - +``` + +If you want to see progress, replace `-xf` with `-xvf`. + +### Tar pipe to another server + +You can also use a tar pipe to copy data to another server. If your +'git' user has SSH access to the newserver as 'git@newserver', you +can pipe the data through SSH. + +``` +# As the git user +tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ + ssh git@newserver tar -C /mnt/gitlab/repositories -xf - +``` + +If you want to compress the data before it goes over the network +(which will cost you CPU cycles) you can replace `ssh` with `ssh -C`. + +## The target directory contains an outdated copy of the repositories: use rsync + +If the target directory already contains a partial / outdated copy +of the repositories it may be wasteful to copy all the data again +with tar. In this scenario it is better to use rsync. This utility +is either already installed on your system or easily installable +via apt, yum etc. + +``` +# As the 'git' user +rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ + /mnt/gitlab/repositories +``` + +The `/.` in the command above is very important, without it you can +easily get the wrong directory structure in the target directory. +If you want to see progress, replace `-a` with `-av`. + +### Single rsync to another server + +If the 'git' user on your source system has SSH access to the target +server you can send the repositories over the network with rsync. + +``` +# As the 'git' user +rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ + git@newserver:/mnt/gitlab/repositories +``` + +## Thousands of Git repositories: use one rsync per repository + +Every time you start an rsync job it has to inspect all files in +the source directory, all files in the target directory, and then +decide what files to copy or not. If the source or target directory +has many contents this startup phase of rsync can become a burden +for your GitLab server. In cases like this you can make rsync's +life easier by dividing its work in smaller pieces, and sync one +repository at a time. + +In addition to rsync we will use [GNU +Parallel](http://www.gnu.org/software/parallel/). This utility is +not included in GitLab so you need to install it yourself with apt +or yum. Also note that the GitLab scripts we used below were added +in GitLab 8.1. + +** This process does not clean up repositories at the target location that no +longer exist at the source. ** If you start using your GitLab instance with +`/mnt/gitlab/repositories`, you need to run `gitlab-rake gitlab:cleanup:repos` +after switching to the new repository storage directory. + +### Parallel rsync for all repositories known to GitLab + +This will sync repositories with 10 rsync processes at a time. We keep +track of progress so that the transfer can be restarted if necessary. + +First we create a new directory, owned by 'git', to hold transfer +logs. We assume the directory is empty before we start the transfer +procedure, and that we are the only ones writing files in it. + +``` +# Omnibus +sudo mkdir /var/opt/gitlab/transfer-logs +sudo chown git:git /var/opt/gitlab/transfer-logs + +# Source +sudo -u git -H mkdir /home/git/transfer-logs +``` + +We seed the process with a list of the directories we want to copy. + +``` +# Omnibus +sudo -u git sh -c 'gitlab-rake gitlab:list_repos > /var/opt/gitlab/transfer-logs/all-repos-$(date +%s).txt' + +# Source +cd /home/git/gitlab +sudo -u git -H sh -c 'bundle exec rake gitlab:list_repos > /home/git/transfer-logs/all-repos-$(date +%s).txt' +``` + +Now we can start the transfer. The command below is idempotent, and +the number of jobs done by GNU Parallel should converge to zero. If it +does not some repositories listed in all-repos-1234.txt may have been +deleted/renamed before they could be copied. + +``` +# Omnibus +sudo -u git sh -c ' +cat /var/opt/gitlab/transfer-logs/* | sort | uniq -u |\ + /usr/bin/env JOBS=10 \ + /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ + /var/opt/gitlab/transfer-logs/success-$(date +%s).log \ + /var/opt/gitlab/git-data/repositories \ + /mnt/gitlab/repositories +' + +# Source +cd /home/git/gitlab +sudo -u git -H sh -c ' +cat /home/git/transfer-logs/* | sort | uniq -u |\ + /usr/bin/env JOBS=10 \ + bin/parallel-rsync-repos \ + /home/git/transfer-logs/success-$(date +%s).log \ + /home/git/repositories \ + /mnt/gitlab/repositories +` +``` + +### Parallel rsync only for repositories with recent activity + +Suppose you have already done one sync that started after 2015-10-1 12:00 UTC. +Then you might only want to sync repositories that were changed via GitLab +_after_ that time. You can use the 'SINCE' variable to tell 'rake +gitlab:list_repos' to only print repositories with recent activity. + +``` +# Omnibus +sudo gitlab-rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ + sudo -u git \ + /usr/bin/env JOBS=10 \ + /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ + success-$(date +%s).log \ + /var/opt/gitlab/git-data/repositories \ + /mnt/gitlab/repositories + +# Source +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ + sudo -u git -H \ + /usr/bin/env JOBS=10 \ + bin/parallel-rsync-repos \ + success-$(date +%s).log \ + /home/git/repositories \ + /mnt/gitlab/repositories +``` diff --git a/doc/administration/operations/sidekiq_memory_killer.md b/doc/administration/operations/sidekiq_memory_killer.md new file mode 100644 index 00000000000..b5e78348989 --- /dev/null +++ b/doc/administration/operations/sidekiq_memory_killer.md @@ -0,0 +1,40 @@ +# Sidekiq MemoryKiller + +The GitLab Rails application code suffers from memory leaks. For web requests +this problem is made manageable using +[unicorn-worker-killer](https://github.com/kzk/unicorn-worker-killer) which +restarts Unicorn worker processes in between requests when needed. The Sidekiq +MemoryKiller applies the same approach to the Sidekiq processes used by GitLab +to process background jobs. + +Unlike unicorn-worker-killer, which is enabled by default for all GitLab +installations since GitLab 6.4, the Sidekiq MemoryKiller is enabled by default +_only_ for Omnibus packages. The reason for this is that the MemoryKiller +relies on Runit to restart Sidekiq after a memory-induced shutdown and GitLab +installations from source do not all use Runit or an equivalent. + +With the default settings, the MemoryKiller will cause a Sidekiq restart no +more often than once every 15 minutes, with the restart causing about one +minute of delay for incoming background jobs. + +## Configuring the MemoryKiller + +The MemoryKiller is controlled using environment variables. + +- `SIDEKIQ_MEMORY_KILLER_MAX_RSS`: if this variable is set, and its value is + greater than 0, then after each Sidekiq job, the MemoryKiller will check the + RSS of the Sidekiq process that executed the job. If the RSS of the Sidekiq + process (expressed in kilobytes) exceeds SIDEKIQ_MEMORY_KILLER_MAX_RSS, a + delayed shutdown is triggered. The default value for Omnibus packages is set + [in the omnibus-gitlab + repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb). +- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults 900 seconds (15 minutes). When + a shutdown is triggered, the Sidekiq process will keep working normally for + another 15 minutes. +- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace + time has expired, the MemoryKiller tells Sidekiq to stop accepting new jobs. + Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells + Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must + restart Sidekiq. +- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL`: defaults to `SIGKILL`. The name of + the final signal sent to the Sidekiq process when we want it to shut down. diff --git a/doc/administration/operations/unicorn.md b/doc/administration/operations/unicorn.md new file mode 100644 index 00000000000..bad61151bda --- /dev/null +++ b/doc/administration/operations/unicorn.md @@ -0,0 +1,86 @@ +# Understanding Unicorn and unicorn-worker-killer + +## Unicorn + +GitLab uses [Unicorn](http://unicorn.bogomips.org/), a pre-forking Ruby web +server, to handle web requests (web browsers and Git HTTP clients). Unicorn is +a daemon written in Ruby and C that can load and run a Ruby on Rails +application; in our case the Rails application is GitLab Community Edition or +GitLab Enterprise Edition. + +Unicorn has a multi-process architecture to make better use of available CPU +cores (processes can run on different cores) and to have stronger fault +tolerance (most failures stay isolated in only one process and cannot take down +GitLab entirely). On startup, the Unicorn 'master' process loads a clean Ruby +environment with the GitLab application code, and then spawns 'workers' which +inherit this clean initial environment. The 'master' never handles any +requests, that is left to the workers. The operating system network stack +queues incoming requests and distributes them among the workers. + +In a perfect world, the master would spawn its pool of workers once, and then +the workers handle incoming web requests one after another until the end of +time. In reality, worker processes can crash or time out: if the master notices +that a worker takes too long to handle a request it will terminate the worker +process with SIGKILL ('kill -9'). No matter how the worker process ended, the +master process will replace it with a new 'clean' process again. Unicorn is +designed to be able to replace 'crashed' workers without dropping user +requests. + +This is what a Unicorn worker timeout looks like in `unicorn_stderr.log`. The +master process has PID 56227 below. + +``` +[2015-06-05T10:58:08.660325 #56227] ERROR -- : worker=10 PID:53009 timeout (61s > 60s), killing +[2015-06-05T10:58:08.699360 #56227] ERROR -- : reaped # worker=10 +[2015-06-05T10:58:08.708141 #62538] INFO -- : worker=10 spawned pid=62538 +[2015-06-05T10:58:08.708824 #62538] INFO -- : worker=10 ready +``` + +### Tunables + +The main tunables for Unicorn are the number of worker processes and the +request timeout after which the Unicorn master terminates a worker process. +See the [omnibus-gitlab Unicorn settings +documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md) +if you want to adjust these settings. + +## unicorn-worker-killer + +GitLab has memory leaks. These memory leaks manifest themselves in long-running +processes, such as Unicorn workers. (The Unicorn master process is not known to +leak memory, probably because it does not handle user requests.) + +To make these memory leaks manageable, GitLab comes with the +[unicorn-worker-killer gem](https://github.com/kzk/unicorn-worker-killer). This +gem [monkey-patches](https://en.wikipedia.org/wiki/Monkey_patch) the Unicorn +workers to do a memory self-check after every 16 requests. If the memory of the +Unicorn worker exceeds a pre-set limit then the worker process exits. The +Unicorn master then automatically replaces the worker process. + +This is a robust way to handle memory leaks: Unicorn is designed to handle +workers that 'crash' so no user requests will be dropped. The +unicorn-worker-killer gem is designed to only terminate a worker process _in +between requests_, so no user requests are affected. + +This is what a Unicorn worker memory restart looks like in unicorn_stderr.log. +You see that worker 4 (PID 125918) is inspecting itself and decides to exit. +The threshold memory value was 254802235 bytes, about 250MB. With GitLab this +threshold is a random value between 200 and 250 MB. The master process (PID +117565) then reaps the worker process and spawns a new 'worker 4' with PID +127549. + +``` +[2015-06-05T12:07:41.828374 #125918] WARN -- : #: worker (pid: 125918) exceeds memory limit (256413696 bytes > 254802235 bytes) +[2015-06-05T12:07:41.828472 #125918] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 125918) alive: 23 sec (trial 1) +[2015-06-05T12:07:42.025916 #117565] INFO -- : reaped # worker=4 +[2015-06-05T12:07:42.034527 #127549] INFO -- : worker=4 spawned pid=127549 +[2015-06-05T12:07:42.035217 #127549] INFO -- : worker=4 ready +``` + +One other thing that stands out in the log snippet above, taken from +GitLab.com, is that 'worker 4' was serving requests for only 23 seconds. This +is a normal value for our current GitLab.com setup and traffic. + +The high frequency of Unicorn memory restarts on some GitLab sites can be a +source of confusion for administrators. Usually they are a [red +herring](https://en.wikipedia.org/wiki/Red_herring). diff --git a/doc/operations/README.md b/doc/operations/README.md index 6a35dab7b6c..58f16aff7bd 100644 --- a/doc/operations/README.md +++ b/doc/operations/README.md @@ -1,5 +1 @@ -# GitLab operations - -- [Sidekiq MemoryKiller](sidekiq_memory_killer.md) -- [Cleaning up Redis sessions](cleaning_up_redis_sessions.md) -- [Understanding Unicorn and unicorn-worker-killer](unicorn.md) +This document was moved to [administration/operations](../administration/operations.md). diff --git a/doc/operations/cleaning_up_redis_sessions.md b/doc/operations/cleaning_up_redis_sessions.md index 93521e976d5..2a1d0a8c8eb 100644 --- a/doc/operations/cleaning_up_redis_sessions.md +++ b/doc/operations/cleaning_up_redis_sessions.md @@ -1,52 +1 @@ -# Cleaning up stale Redis sessions - -Since version 6.2, GitLab stores web user sessions as key-value pairs in Redis. -Prior to GitLab 7.3, user sessions did not automatically expire from Redis. If -you have been running a large GitLab server (thousands of users) since before -GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis -database after you upgrade to GitLab 7.3. You can also perform a cleanup while -still running GitLab 7.2 or older, but in that case new stale sessions will -start building up again after you clean up. - -In GitLab versions prior to 7.3.0, the session keys in Redis are 16-byte -hexadecimal values such as '976aa289e2189b17d7ef525a6702ace9'. Starting with -GitLab 7.3.0, the keys are -prefixed with 'session:gitlab:', so they would look like -'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to -remove the keys in the old format. - -First we define a shell function with the proper Redis connection details. - -``` -rcli() { - # This example works for Omnibus installations of GitLab 7.3 or newer. For an - # installation from source you will have to change the socket path and the - # path to redis-cli. - sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@" -} - -# test the new shell function; the response should be PONG -rcli ping -``` - -Now we do a search to see if there are any session keys in the old format for -us to clean up. - -``` -# returns the number of old-format session keys in Redis -rcli keys '*' | grep '^[a-f0-9]\{32\}$' | wc -l -``` - -If the number is larger than zero, you can proceed to expire the keys from -Redis. If the number is zero there is nothing to clean up. - -``` -# Tell Redis to expire each matched key after 600 seconds. -rcli keys '*' | grep '^[a-f0-9]\{32\}$' | awk '{ print "expire", $0, 600 }' | rcli -# This will print '(integer) 1' for each key that gets expired. -``` - -Over the next 15 minutes (10 minutes expiry time plus 5 minutes Redis -background save interval) your Redis database will be compacted. If you are -still using GitLab 7.2, users who are not clicking around in GitLab during the -10 minute expiry window will be signed out of GitLab. +This document was moved to [administration/operations/cleaning_up_redis_sessions](../administration/operations/cleaning_up_redis_sessions.md). diff --git a/doc/operations/moving_repositories.md b/doc/operations/moving_repositories.md index 54adb99386a..c54bca324a5 100644 --- a/doc/operations/moving_repositories.md +++ b/doc/operations/moving_repositories.md @@ -1,180 +1 @@ -# Moving repositories managed by GitLab - -Sometimes you need to move all repositories managed by GitLab to -another filesystem or another server. In this document we will look -at some of the ways you can copy all your repositories from -`/var/opt/gitlab/git-data/repositories` to `/mnt/gitlab/repositories`. - -We will look at three scenarios: the target directory is empty, the -target directory contains an outdated copy of the repositories, and -how to deal with thousands of repositories. - -**Each of the approaches we list can/will overwrite data in the -target directory `/mnt/gitlab/repositories`. Do not mix up the -source and the target.** - -## Target directory is empty: use a tar pipe - -If the target directory `/mnt/gitlab/repositories` is empty the -simplest thing to do is to use a tar pipe. This method has low -overhead and tar is almost always already installed on your system. -However, it is not possible to resume an interrupted tar pipe: if -that happens then all data must be copied again. - -``` -# As the git user -tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ - tar -C /mnt/gitlab/repositories -xf - -``` - -If you want to see progress, replace `-xf` with `-xvf`. - -### Tar pipe to another server - -You can also use a tar pipe to copy data to another server. If your -'git' user has SSH access to the newserver as 'git@newserver', you -can pipe the data through SSH. - -``` -# As the git user -tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ - ssh git@newserver tar -C /mnt/gitlab/repositories -xf - -``` - -If you want to compress the data before it goes over the network -(which will cost you CPU cycles) you can replace `ssh` with `ssh -C`. - -## The target directory contains an outdated copy of the repositories: use rsync - -If the target directory already contains a partial / outdated copy -of the repositories it may be wasteful to copy all the data again -with tar. In this scenario it is better to use rsync. This utility -is either already installed on your system or easily installable -via apt, yum etc. - -``` -# As the 'git' user -rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ - /mnt/gitlab/repositories -``` - -The `/.` in the command above is very important, without it you can -easily get the wrong directory structure in the target directory. -If you want to see progress, replace `-a` with `-av`. - -### Single rsync to another server - -If the 'git' user on your source system has SSH access to the target -server you can send the repositories over the network with rsync. - -``` -# As the 'git' user -rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ - git@newserver:/mnt/gitlab/repositories -``` - -## Thousands of Git repositories: use one rsync per repository - -Every time you start an rsync job it has to inspect all files in -the source directory, all files in the target directory, and then -decide what files to copy or not. If the source or target directory -has many contents this startup phase of rsync can become a burden -for your GitLab server. In cases like this you can make rsync's -life easier by dividing its work in smaller pieces, and sync one -repository at a time. - -In addition to rsync we will use [GNU -Parallel](http://www.gnu.org/software/parallel/). This utility is -not included in GitLab so you need to install it yourself with apt -or yum. Also note that the GitLab scripts we used below were added -in GitLab 8.1. - -** This process does not clean up repositories at the target location that no -longer exist at the source. ** If you start using your GitLab instance with -`/mnt/gitlab/repositories`, you need to run `gitlab-rake gitlab:cleanup:repos` -after switching to the new repository storage directory. - -### Parallel rsync for all repositories known to GitLab - -This will sync repositories with 10 rsync processes at a time. We keep -track of progress so that the transfer can be restarted if necessary. - -First we create a new directory, owned by 'git', to hold transfer -logs. We assume the directory is empty before we start the transfer -procedure, and that we are the only ones writing files in it. - -``` -# Omnibus -sudo mkdir /var/opt/gitlab/transfer-logs -sudo chown git:git /var/opt/gitlab/transfer-logs - -# Source -sudo -u git -H mkdir /home/git/transfer-logs -``` - -We seed the process with a list of the directories we want to copy. - -``` -# Omnibus -sudo -u git sh -c 'gitlab-rake gitlab:list_repos > /var/opt/gitlab/transfer-logs/all-repos-$(date +%s).txt' - -# Source -cd /home/git/gitlab -sudo -u git -H sh -c 'bundle exec rake gitlab:list_repos > /home/git/transfer-logs/all-repos-$(date +%s).txt' -``` - -Now we can start the transfer. The command below is idempotent, and -the number of jobs done by GNU Parallel should converge to zero. If it -does not some repositories listed in all-repos-1234.txt may have been -deleted/renamed before they could be copied. - -``` -# Omnibus -sudo -u git sh -c ' -cat /var/opt/gitlab/transfer-logs/* | sort | uniq -u |\ - /usr/bin/env JOBS=10 \ - /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ - /var/opt/gitlab/transfer-logs/success-$(date +%s).log \ - /var/opt/gitlab/git-data/repositories \ - /mnt/gitlab/repositories -' - -# Source -cd /home/git/gitlab -sudo -u git -H sh -c ' -cat /home/git/transfer-logs/* | sort | uniq -u |\ - /usr/bin/env JOBS=10 \ - bin/parallel-rsync-repos \ - /home/git/transfer-logs/success-$(date +%s).log \ - /home/git/repositories \ - /mnt/gitlab/repositories -` -``` - -### Parallel rsync only for repositories with recent activity - -Suppose you have already done one sync that started after 2015-10-1 12:00 UTC. -Then you might only want to sync repositories that were changed via GitLab -_after_ that time. You can use the 'SINCE' variable to tell 'rake -gitlab:list_repos' to only print repositories with recent activity. - -``` -# Omnibus -sudo gitlab-rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ - sudo -u git \ - /usr/bin/env JOBS=10 \ - /opt/gitlab/embedded/service/gitlab-rails/bin/parallel-rsync-repos \ - success-$(date +%s).log \ - /var/opt/gitlab/git-data/repositories \ - /mnt/gitlab/repositories - -# Source -cd /home/git/gitlab -sudo -u git -H bundle exec rake gitlab:list_repos SINCE='2015-10-1 12:00 UTC' |\ - sudo -u git -H \ - /usr/bin/env JOBS=10 \ - bin/parallel-rsync-repos \ - success-$(date +%s).log \ - /home/git/repositories \ - /mnt/gitlab/repositories -``` +This document was moved to [administration/operations/moving_repositories](../administration/operations/moving_repositories.md). diff --git a/doc/operations/sidekiq_memory_killer.md b/doc/operations/sidekiq_memory_killer.md index b5e78348989..cf7c3b2e2ed 100644 --- a/doc/operations/sidekiq_memory_killer.md +++ b/doc/operations/sidekiq_memory_killer.md @@ -1,40 +1 @@ -# Sidekiq MemoryKiller - -The GitLab Rails application code suffers from memory leaks. For web requests -this problem is made manageable using -[unicorn-worker-killer](https://github.com/kzk/unicorn-worker-killer) which -restarts Unicorn worker processes in between requests when needed. The Sidekiq -MemoryKiller applies the same approach to the Sidekiq processes used by GitLab -to process background jobs. - -Unlike unicorn-worker-killer, which is enabled by default for all GitLab -installations since GitLab 6.4, the Sidekiq MemoryKiller is enabled by default -_only_ for Omnibus packages. The reason for this is that the MemoryKiller -relies on Runit to restart Sidekiq after a memory-induced shutdown and GitLab -installations from source do not all use Runit or an equivalent. - -With the default settings, the MemoryKiller will cause a Sidekiq restart no -more often than once every 15 minutes, with the restart causing about one -minute of delay for incoming background jobs. - -## Configuring the MemoryKiller - -The MemoryKiller is controlled using environment variables. - -- `SIDEKIQ_MEMORY_KILLER_MAX_RSS`: if this variable is set, and its value is - greater than 0, then after each Sidekiq job, the MemoryKiller will check the - RSS of the Sidekiq process that executed the job. If the RSS of the Sidekiq - process (expressed in kilobytes) exceeds SIDEKIQ_MEMORY_KILLER_MAX_RSS, a - delayed shutdown is triggered. The default value for Omnibus packages is set - [in the omnibus-gitlab - repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb). -- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults 900 seconds (15 minutes). When - a shutdown is triggered, the Sidekiq process will keep working normally for - another 15 minutes. -- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace - time has expired, the MemoryKiller tells Sidekiq to stop accepting new jobs. - Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells - Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must - restart Sidekiq. -- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_SIGNAL`: defaults to `SIGKILL`. The name of - the final signal sent to the Sidekiq process when we want it to shut down. +This document was moved to [administration/operations/sidekiq_memory_killer](../administration/operations/sidekiq_memory_killer.md). diff --git a/doc/operations/unicorn.md b/doc/operations/unicorn.md index bad61151bda..fbc9697b755 100644 --- a/doc/operations/unicorn.md +++ b/doc/operations/unicorn.md @@ -1,86 +1 @@ -# Understanding Unicorn and unicorn-worker-killer - -## Unicorn - -GitLab uses [Unicorn](http://unicorn.bogomips.org/), a pre-forking Ruby web -server, to handle web requests (web browsers and Git HTTP clients). Unicorn is -a daemon written in Ruby and C that can load and run a Ruby on Rails -application; in our case the Rails application is GitLab Community Edition or -GitLab Enterprise Edition. - -Unicorn has a multi-process architecture to make better use of available CPU -cores (processes can run on different cores) and to have stronger fault -tolerance (most failures stay isolated in only one process and cannot take down -GitLab entirely). On startup, the Unicorn 'master' process loads a clean Ruby -environment with the GitLab application code, and then spawns 'workers' which -inherit this clean initial environment. The 'master' never handles any -requests, that is left to the workers. The operating system network stack -queues incoming requests and distributes them among the workers. - -In a perfect world, the master would spawn its pool of workers once, and then -the workers handle incoming web requests one after another until the end of -time. In reality, worker processes can crash or time out: if the master notices -that a worker takes too long to handle a request it will terminate the worker -process with SIGKILL ('kill -9'). No matter how the worker process ended, the -master process will replace it with a new 'clean' process again. Unicorn is -designed to be able to replace 'crashed' workers without dropping user -requests. - -This is what a Unicorn worker timeout looks like in `unicorn_stderr.log`. The -master process has PID 56227 below. - -``` -[2015-06-05T10:58:08.660325 #56227] ERROR -- : worker=10 PID:53009 timeout (61s > 60s), killing -[2015-06-05T10:58:08.699360 #56227] ERROR -- : reaped # worker=10 -[2015-06-05T10:58:08.708141 #62538] INFO -- : worker=10 spawned pid=62538 -[2015-06-05T10:58:08.708824 #62538] INFO -- : worker=10 ready -``` - -### Tunables - -The main tunables for Unicorn are the number of worker processes and the -request timeout after which the Unicorn master terminates a worker process. -See the [omnibus-gitlab Unicorn settings -documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md) -if you want to adjust these settings. - -## unicorn-worker-killer - -GitLab has memory leaks. These memory leaks manifest themselves in long-running -processes, such as Unicorn workers. (The Unicorn master process is not known to -leak memory, probably because it does not handle user requests.) - -To make these memory leaks manageable, GitLab comes with the -[unicorn-worker-killer gem](https://github.com/kzk/unicorn-worker-killer). This -gem [monkey-patches](https://en.wikipedia.org/wiki/Monkey_patch) the Unicorn -workers to do a memory self-check after every 16 requests. If the memory of the -Unicorn worker exceeds a pre-set limit then the worker process exits. The -Unicorn master then automatically replaces the worker process. - -This is a robust way to handle memory leaks: Unicorn is designed to handle -workers that 'crash' so no user requests will be dropped. The -unicorn-worker-killer gem is designed to only terminate a worker process _in -between requests_, so no user requests are affected. - -This is what a Unicorn worker memory restart looks like in unicorn_stderr.log. -You see that worker 4 (PID 125918) is inspecting itself and decides to exit. -The threshold memory value was 254802235 bytes, about 250MB. With GitLab this -threshold is a random value between 200 and 250 MB. The master process (PID -117565) then reaps the worker process and spawns a new 'worker 4' with PID -127549. - -``` -[2015-06-05T12:07:41.828374 #125918] WARN -- : #: worker (pid: 125918) exceeds memory limit (256413696 bytes > 254802235 bytes) -[2015-06-05T12:07:41.828472 #125918] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 125918) alive: 23 sec (trial 1) -[2015-06-05T12:07:42.025916 #117565] INFO -- : reaped # worker=4 -[2015-06-05T12:07:42.034527 #127549] INFO -- : worker=4 spawned pid=127549 -[2015-06-05T12:07:42.035217 #127549] INFO -- : worker=4 ready -``` - -One other thing that stands out in the log snippet above, taken from -GitLab.com, is that 'worker 4' was serving requests for only 23 seconds. This -is a normal value for our current GitLab.com setup and traffic. - -The high frequency of Unicorn memory restarts on some GitLab sites can be a -source of confusion for administrators. Usually they are a [red -herring](https://en.wikipedia.org/wiki/Red_herring). +This document was moved to [administration/operations/unicorn](../administration/operations/unicorn.md). -- cgit v1.2.1 From fb5a4202062d07d2dbca544f4cfb475a65411716 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 11:42:37 -0300 Subject: Allow projects to have many boards --- app/models/project.rb | 3 +-- spec/models/project_spec.rb | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 74d54e69648..795a456b094 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -65,8 +65,7 @@ class Project < ActiveRecord::Base belongs_to :namespace has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' - - has_one :board, dependent: :destroy + has_many :boards, dependent: :destroy # Project services has_many :services diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index dae546a0cdc..3748b1c7f5f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -24,7 +24,7 @@ describe Project, models: true do it { is_expected.to have_one(:slack_service).dependent(:destroy) } it { is_expected.to have_one(:pushover_service).dependent(:destroy) } it { is_expected.to have_one(:asana_service).dependent(:destroy) } - it { is_expected.to have_one(:board).dependent(:destroy) } + it { is_expected.to have_many(:boards).dependent(:destroy) } it { is_expected.to have_one(:campfire_service).dependent(:destroy) } it { is_expected.to have_one(:drone_ci_service).dependent(:destroy) } it { is_expected.to have_one(:emails_on_push_service).dependent(:destroy) } -- cgit v1.2.1 From 95a5cc9285a8583988ece697ebdb948730b5db55 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 15:58:28 -0300 Subject: Restrict the number of permitted boards per project to one --- app/models/project.rb | 7 ++++++- spec/models/project_spec.rb | 9 +++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index 795a456b094..30db7ed50b3 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -16,6 +16,7 @@ class Project < ActiveRecord::Base extend Gitlab::ConfigHelper + NUMBER_OF_PERMITTED_BOARDS = 1 UNKNOWN_IMPORT_URL = 'http://unknown.git' cache_markdown_field :description, pipeline: :description @@ -65,7 +66,7 @@ class Project < ActiveRecord::Base belongs_to :namespace has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' - has_many :boards, dependent: :destroy + has_many :boards, before_add: :validate_board_limit, dependent: :destroy # Project services has_many :services @@ -1338,4 +1339,8 @@ class Project < ActiveRecord::Base shared_projects.any? end + + def validate_board_limit(board) + raise StandardError, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS + end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 3748b1c7f5f..1b13f1be477 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -94,6 +94,15 @@ describe Project, models: true do end end end + + describe '#boards' do + it 'raises an error when attempting to add more than one board to the project' do + subject.boards.build + + expect { subject.boards.build }.to raise_error(StandardError, 'Number of permitted boards exceeded') + expect(subject.boards.size).to eq 1 + end + end end describe 'modules' do -- cgit v1.2.1 From e46a4aabd925e0182c31976b5d28c38b9a8a0872 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 16:20:21 -0300 Subject: Update Boards::CreateService to handle with the has_many association --- app/services/boards/create_service.rb | 15 ++++++++++----- spec/factories/boards.rb | 5 +++++ spec/factories/projects.rb | 8 -------- spec/services/boards/create_service_spec.rb | 22 ++++++++++------------ 4 files changed, 25 insertions(+), 25 deletions(-) diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb index 072a0749285..a9dbd76a44a 100644 --- a/app/services/boards/create_service.rb +++ b/app/services/boards/create_service.rb @@ -1,16 +1,21 @@ module Boards class CreateService < Boards::BaseService def execute - create_board! unless project.board.present? - project.board + if project.boards.empty? + create_board! + else + project.boards.first + end end private def create_board! - project.create_board - project.board.lists.create(list_type: :backlog) - project.board.lists.create(list_type: :done) + board = project.boards.create + board.lists.create(list_type: :backlog) + board.lists.create(list_type: :done) + + board end end end diff --git a/spec/factories/boards.rb b/spec/factories/boards.rb index 35c4a0b6f08..ec46146d9b5 100644 --- a/spec/factories/boards.rb +++ b/spec/factories/boards.rb @@ -1,5 +1,10 @@ FactoryGirl.define do factory :board do project factory: :empty_project + + after(:create) do |board| + board.lists.create(list_type: :backlog) + board.lists.create(list_type: :done) + end end end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 331172445e4..719ef17f57e 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -124,12 +124,4 @@ FactoryGirl.define do ) end end - - factory :project_with_board, parent: :empty_project do - after(:create) do |project| - project.create_board - project.board.lists.create(list_type: :backlog) - project.board.lists.create(list_type: :done) - end - end end diff --git a/spec/services/boards/create_service_spec.rb b/spec/services/boards/create_service_spec.rb index a1a4dd4c57c..fde807cc410 100644 --- a/spec/services/boards/create_service_spec.rb +++ b/spec/services/boards/create_service_spec.rb @@ -2,33 +2,31 @@ require 'spec_helper' describe Boards::CreateService, services: true do describe '#execute' do + let(:project) { create(:empty_project) } + subject(:service) { described_class.new(project, double) } context 'when project does not have a board' do - let(:project) { create(:empty_project, board: nil) } - it 'creates a new board' do expect { service.execute }.to change(Board, :count).by(1) end it 'creates default lists' do - service.execute + board = service.execute - expect(project.board.lists.size).to eq 2 - expect(project.board.lists.first).to be_backlog - expect(project.board.lists.last).to be_done + expect(board.lists.size).to eq 2 + expect(board.lists.first).to be_backlog + expect(board.lists.last).to be_done end end context 'when project has a board' do - let!(:project) { create(:project_with_board) } - - it 'does not create a new board' do - expect { service.execute }.not_to change(Board, :count) + before do + create(:board, project: project) end - it 'does not create board lists' do - expect { service.execute }.not_to change(project.board.lists, :count) + it 'does not create a new board' do + expect { service.execute }.not_to change(project.boards, :count) end end end -- cgit v1.2.1 From a2c485473a142a5aad2f03abefb4bfafed645995 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 13:54:46 -0300 Subject: Add Boards::Lists::ListService to list lists for a specific board --- app/services/boards/lists/list_service.rb | 9 +++++++++ spec/services/boards/lists/list_service_spec.rb | 18 ++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 app/services/boards/lists/list_service.rb create mode 100644 spec/services/boards/lists/list_service_spec.rb diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb new file mode 100644 index 00000000000..ff739bc7d9c --- /dev/null +++ b/app/services/boards/lists/list_service.rb @@ -0,0 +1,9 @@ +module Boards + module Lists + class ListService < Boards::BaseService + def execute(board) + board.lists + end + end + end +end diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb new file mode 100644 index 00000000000..4464f80f796 --- /dev/null +++ b/spec/services/boards/lists/list_service_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +describe Boards::Lists::ListService, services: true do + describe '#execute' do + it "returns board's lists" do + project = create(:empty_project) + board = create(:board, project: project) + label = create(:label, project: project) + backlog_list = create(:backlog_list, board: board) + list = create(:list, board: board, label: label) + done_list = create(:done_list, board: board) + + service = described_class.new(project, double) + + expect(service.execute(board)).to eq [backlog_list, list, done_list] + end + end +end -- cgit v1.2.1 From 9110746370c9759402af5087a612cbcfdf667c3c Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:00:18 -0300 Subject: Update Boards::Lists::CreateService to create lists for a specific board --- app/services/boards/base_service.rb | 1 - app/services/boards/lists/create_service.rb | 10 +++++----- spec/services/boards/lists/create_service_spec.rb | 14 +++++++------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/services/boards/base_service.rb b/app/services/boards/base_service.rb index b2069ca825a..7eacacbaf7e 100644 --- a/app/services/boards/base_service.rb +++ b/app/services/boards/base_service.rb @@ -1,5 +1,4 @@ module Boards class BaseService < ::BaseService - delegate :board, to: :project end end diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb index b1887820bd4..da6f59c0399 100644 --- a/app/services/boards/lists/create_service.rb +++ b/app/services/boards/lists/create_service.rb @@ -1,23 +1,23 @@ module Boards module Lists class CreateService < Boards::BaseService - def execute + def execute(board) List.transaction do label = project.labels.find(params[:label_id]) - position = next_position + position = next_position(board) - create_list(label, position) + create_list(board, label, position) end end private - def next_position + def next_position(board) max_position = board.lists.movable.maximum(:position) max_position.nil? ? 0 : max_position.succ end - def create_list(label, position) + def create_list(board, label, position) board.lists.create(label: label, list_type: :label, position: position) end end diff --git a/spec/services/boards/lists/create_service_spec.rb b/spec/services/boards/lists/create_service_spec.rb index bff9c1fd1fe..e7806add916 100644 --- a/spec/services/boards/lists/create_service_spec.rb +++ b/spec/services/boards/lists/create_service_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Boards::Lists::CreateService, services: true do describe '#execute' do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:label) { create(:label, project: project, name: 'in-progress') } @@ -11,7 +11,7 @@ describe Boards::Lists::CreateService, services: true do context 'when board lists is empty' do it 'creates a new list at beginning of the list' do - list = service.execute + list = service.execute(board) expect(list.position).to eq 0 end @@ -19,7 +19,7 @@ describe Boards::Lists::CreateService, services: true do context 'when board lists has backlog, and done lists' do it 'creates a new list at beginning of the list' do - list = service.execute + list = service.execute(board) expect(list.position).to eq 0 end @@ -30,7 +30,7 @@ describe Boards::Lists::CreateService, services: true do create(:list, board: board, position: 0) create(:list, board: board, position: 1) - list = service.execute + list = service.execute(board) expect(list.position).to eq 2 end @@ -40,7 +40,7 @@ describe Boards::Lists::CreateService, services: true do it 'creates a new list at end of the label lists' do list1 = create(:list, board: board, position: 0) - list2 = service.execute + list2 = service.execute(board) expect(list1.reload.position).to eq 0 expect(list2.reload.position).to eq 1 @@ -52,7 +52,7 @@ describe Boards::Lists::CreateService, services: true do label = create(:label, name: 'in-development') service = described_class.new(project, user, label_id: label.id) - expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) + expect { service.execute(board) }.to raise_error(ActiveRecord::RecordNotFound) end end end -- cgit v1.2.1 From af87cf7c6ee1778b283ed285cdd7edbaaffc5fa5 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:06:34 -0300 Subject: Update Boards::Lists::GenerateService to generate for a specific board --- app/services/boards/lists/generate_service.rb | 8 ++++---- spec/services/boards/lists/generate_service_spec.rb | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb index 830e386c98b..686e4e4b336 100644 --- a/app/services/boards/lists/generate_service.rb +++ b/app/services/boards/lists/generate_service.rb @@ -1,11 +1,11 @@ module Boards module Lists class GenerateService < Boards::BaseService - def execute + def execute(board) return false unless board.lists.movable.empty? List.transaction do - label_params.each { |params| create_list(params) } + label_params.each { |params| create_list(board, params) } end true @@ -13,9 +13,9 @@ module Boards private - def create_list(params) + def create_list(board, params) label = find_or_create_label(params) - Lists::CreateService.new(project, current_user, label_id: label.id).execute + Lists::CreateService.new(project, current_user, label_id: label.id).execute(board) end def find_or_create_label(params) diff --git a/spec/services/boards/lists/generate_service_spec.rb b/spec/services/boards/lists/generate_service_spec.rb index 4171e4d816c..8b2f5e81338 100644 --- a/spec/services/boards/lists/generate_service_spec.rb +++ b/spec/services/boards/lists/generate_service_spec.rb @@ -2,15 +2,15 @@ require 'spec_helper' describe Boards::Lists::GenerateService, services: true do describe '#execute' do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } subject(:service) { described_class.new(project, user) } context 'when board lists is empty' do it 'creates the default lists' do - expect { service.execute }.to change(board.lists, :count).by(2) + expect { service.execute(board) }.to change(board.lists, :count).by(2) end end @@ -18,13 +18,13 @@ describe Boards::Lists::GenerateService, services: true do it 'does not creates the default lists' do create(:list, board: board) - expect { service.execute }.not_to change(board.lists, :count) + expect { service.execute(board) }.not_to change(board.lists, :count) end end context 'when project labels does not contains any list label' do it 'creates labels' do - expect { service.execute }.to change(project.labels, :count).by(2) + expect { service.execute(board) }.to change(project.labels, :count).by(2) end end @@ -32,7 +32,7 @@ describe Boards::Lists::GenerateService, services: true do it 'creates the missing labels' do create(:label, project: project, name: 'Doing') - expect { service.execute }.to change(project.labels, :count).by(1) + expect { service.execute(board) }.to change(project.labels, :count).by(1) end end end -- cgit v1.2.1 From 1a4b1e9735c8b3502d54f1e9ecbce0c3f235f5c7 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:13:43 -0300 Subject: Update Boards::Lists::MoveService to move lists inside a specific board --- app/services/boards/lists/move_service.rb | 3 ++- spec/services/boards/lists/move_service_spec.rb | 8 ++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/app/services/boards/lists/move_service.rb b/app/services/boards/lists/move_service.rb index 020ff69f4a7..7d0730e8332 100644 --- a/app/services/boards/lists/move_service.rb +++ b/app/services/boards/lists/move_service.rb @@ -2,6 +2,7 @@ module Boards module Lists class MoveService < Boards::BaseService def execute(list) + @board = list.board @old_position = list.position @new_position = params[:position] @@ -16,7 +17,7 @@ module Boards private - attr_reader :old_position, :new_position + attr_reader :board, :old_position, :new_position def valid_move? new_position.present? && new_position != old_position && diff --git a/spec/services/boards/lists/move_service_spec.rb b/spec/services/boards/lists/move_service_spec.rb index 102ed67449d..63fa0bb8c5f 100644 --- a/spec/services/boards/lists/move_service_spec.rb +++ b/spec/services/boards/lists/move_service_spec.rb @@ -2,16 +2,16 @@ require 'spec_helper' describe Boards::Lists::MoveService, services: true do describe '#execute' do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } - let!(:backlog) { project.board.backlog_list } + let!(:backlog) { create(:backlog_list, board: board) } let!(:planning) { create(:list, board: board, position: 0) } let!(:development) { create(:list, board: board, position: 1) } let!(:review) { create(:list, board: board, position: 2) } let!(:staging) { create(:list, board: board, position: 3) } - let!(:done) { project.board.done_list } + let!(:done) { create(:done_list, board: board) } context 'when list type is set to label' do it 'keeps position of lists when new position is nil' do -- cgit v1.2.1 From 1fa3f30811c3599a2f060803b201441046926b87 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:26:28 -0300 Subject: Update Boards::Issues::ListService to list issues for a board list --- app/services/boards/issues/list_service.rb | 4 +++ spec/services/boards/issues/list_service_spec.rb | 31 ++++++++++++++++++------ 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index 435a8c6e681..782dbf0db1a 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -10,6 +10,10 @@ module Boards private + def board + @board ||= project.boards.find(params[:board_id]) + end + def list @list ||= board.lists.find(params[:id]) end diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index 5b9f454fd2d..cc96fd05189 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -4,7 +4,7 @@ describe Boards::Issues::ListService, services: true do describe '#execute' do let(:user) { create(:user) } let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:board) { create(:board, project: project) } let(:bug) { create(:label, project: project, name: 'Bug') } let(:development) { create(:label, project: project, name: 'Development') } @@ -13,10 +13,10 @@ describe Boards::Issues::ListService, services: true do let(:p2) { create(:label, title: 'P2', project: project, priority: 2) } let(:p3) { create(:label, title: 'P3', project: project, priority: 3) } - let!(:backlog) { project.board.backlog_list } + let!(:backlog) { create(:backlog_list, board: board) } let!(:list1) { create(:list, board: board, label: development, position: 0) } let!(:list2) { create(:list, board: board, label: testing, position: 1) } - let!(:done) { project.board.done_list } + let!(:done) { create(:done_list, board: board) } let!(:opened_issue1) { create(:labeled_issue, project: project, labels: [bug]) } let!(:opened_issue2) { create(:labeled_issue, project: project, labels: [p2]) } @@ -37,7 +37,7 @@ describe Boards::Issues::ListService, services: true do end it 'delegates search to IssuesFinder' do - params = { id: list1.id } + params = { board_id: board.id, id: list1.id } expect_any_instance_of(IssuesFinder).to receive(:execute).once.and_call_original @@ -46,7 +46,7 @@ describe Boards::Issues::ListService, services: true do context 'sets default order to priority' do it 'returns opened issues when listing issues from Backlog' do - params = { id: backlog.id } + params = { board_id: board.id, id: backlog.id } issues = described_class.new(project, user, params).execute @@ -54,7 +54,7 @@ describe Boards::Issues::ListService, services: true do end it 'returns closed issues when listing issues from Done' do - params = { id: done.id } + params = { board_id: board.id, id: done.id } issues = described_class.new(project, user, params).execute @@ -62,12 +62,29 @@ describe Boards::Issues::ListService, services: true do end it 'returns opened issues that have label list applied when listing issues from a label list' do - params = { id: list1.id } + params = { board_id: board.id, id: list1.id } issues = described_class.new(project, user, params).execute expect(issues).to eq [list1_issue3, list1_issue1, list1_issue2] end end + + context 'with list that does not belongs to the board' do + it 'raises an error' do + list = create(:list) + service = described_class.new(project, user, board_id: board.id, id: list.id) + + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + end + + context 'with invalid list id' do + it 'raises an error' do + service = described_class.new(project, user, board_id: board.id, id: nil) + + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) + end + end end end -- cgit v1.2.1 From 104c4f88cdc5aaa40334c83112f7c994ff7128c6 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:32:53 -0300 Subject: Update Boards::Issues::MoveService to move issues on a specific board --- app/services/boards/issues/move_service.rb | 4 ++++ spec/services/boards/issues/move_service_spec.rb | 24 ++++++++++++------------ 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb index 84dc3f70e76..24f6f2c7025 100644 --- a/app/services/boards/issues/move_service.rb +++ b/app/services/boards/issues/move_service.rb @@ -10,6 +10,10 @@ module Boards private + def board + @board ||= project.boards.find(params[:board_id]) + end + def valid_move? moving_from_list.present? && moving_to_list.present? && moving_from_list != moving_to_list diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb index 180f1b08631..9cf5a17e128 100644 --- a/spec/services/boards/issues/move_service_spec.rb +++ b/spec/services/boards/issues/move_service_spec.rb @@ -3,17 +3,17 @@ require 'spec_helper' describe Boards::Issues::MoveService, services: true do describe '#execute' do let(:user) { create(:user) } - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:bug) { create(:label, project: project, name: 'Bug') } let(:development) { create(:label, project: project, name: 'Development') } let(:testing) { create(:label, project: project, name: 'Testing') } - let!(:backlog) { project.board.backlog_list } + let!(:backlog) { create(:backlog_list, board: board) } let!(:list1) { create(:list, board: board, label: development, position: 0) } let!(:list2) { create(:list, board: board, label: testing, position: 1) } - let!(:done) { project.board.done_list } + let!(:done) { create(:done_list, board: board) } before do project.team << [user, :developer] @@ -22,7 +22,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from backlog' do it 'adds the label of the list it goes to' do issue = create(:labeled_issue, project: project, labels: [bug]) - params = { from_list_id: backlog.id, to_list_id: list1.id } + params = { board_id: board.id, from_list_id: backlog.id, to_list_id: list1.id } described_class.new(project, user, params).execute(issue) @@ -33,7 +33,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving to backlog' do it 'removes all list-labels' do issue = create(:labeled_issue, project: project, labels: [bug, development, testing]) - params = { from_list_id: list1.id, to_list_id: backlog.id } + params = { board_id: board.id, from_list_id: list1.id, to_list_id: backlog.id } described_class.new(project, user, params).execute(issue) @@ -44,7 +44,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from backlog to done' do it 'closes the issue' do issue = create(:labeled_issue, project: project, labels: [bug]) - params = { from_list_id: backlog.id, to_list_id: done.id } + params = { board_id: board.id, from_list_id: backlog.id, to_list_id: done.id } described_class.new(project, user, params).execute(issue) issue.reload @@ -56,7 +56,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving an issue between lists' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { from_list_id: list1.id, to_list_id: list2.id } } + let(:params) { { board_id: board.id, from_list_id: list1.id, to_list_id: list2.id } } it 'delegates the label changes to Issues::UpdateService' do expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once @@ -73,7 +73,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving to done' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing]) } - let(:params) { { from_list_id: list2.id, to_list_id: done.id } } + let(:params) { { board_id: board.id, from_list_id: list2.id, to_list_id: done.id } } it 'delegates the close proceedings to Issues::CloseService' do expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once @@ -92,7 +92,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from done' do let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) } - let(:params) { { from_list_id: done.id, to_list_id: list2.id } } + let(:params) { { board_id: board.id, from_list_id: done.id, to_list_id: list2.id } } it 'delegates the re-open proceedings to Issues::ReopenService' do expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once @@ -112,7 +112,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from done to backlog' do it 'reopens the issue' do issue = create(:labeled_issue, :closed, project: project, labels: [bug]) - params = { from_list_id: done.id, to_list_id: backlog.id } + params = { board_id: board.id, from_list_id: done.id, to_list_id: backlog.id } described_class.new(project, user, params).execute(issue) issue.reload @@ -124,7 +124,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving to same list' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { from_list_id: list1.id, to_list_id: list1.id } } + let(:params) { { board_id: board.id, from_list_id: list1.id, to_list_id: list1.id } } it 'returns false' do expect(described_class.new(project, user, params).execute(issue)).to eq false -- cgit v1.2.1 From 8b15e328a60f050d4ff24c0c7203461ae2724a30 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 14:50:44 -0300 Subject: Removes all labels from project boards when moving and issue to done --- app/services/boards/issues/move_service.rb | 2 +- spec/services/boards/issues/move_service_spec.rb | 34 +++++++++++++----------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb index 24f6f2c7025..9a16609a8b5 100644 --- a/app/services/boards/issues/move_service.rb +++ b/app/services/boards/issues/move_service.rb @@ -53,7 +53,7 @@ module Boards if moving_to_list.movable? moving_from_list.label_id else - board.lists.movable.pluck(:label_id) + project.boards.joins(:lists).merge(List.movable).pluck(:label_id) end Array(label_ids).compact diff --git a/spec/services/boards/issues/move_service_spec.rb b/spec/services/boards/issues/move_service_spec.rb index 9cf5a17e128..c43b2aec490 100644 --- a/spec/services/boards/issues/move_service_spec.rb +++ b/spec/services/boards/issues/move_service_spec.rb @@ -4,16 +4,16 @@ describe Boards::Issues::MoveService, services: true do describe '#execute' do let(:user) { create(:user) } let(:project) { create(:empty_project) } - let(:board) { create(:board, project: project) } + let(:board1) { create(:board, project: project) } let(:bug) { create(:label, project: project, name: 'Bug') } let(:development) { create(:label, project: project, name: 'Development') } let(:testing) { create(:label, project: project, name: 'Testing') } - let!(:backlog) { create(:backlog_list, board: board) } - let!(:list1) { create(:list, board: board, label: development, position: 0) } - let!(:list2) { create(:list, board: board, label: testing, position: 1) } - let!(:done) { create(:done_list, board: board) } + let!(:backlog) { create(:backlog_list, board: board1) } + let!(:list1) { create(:list, board: board1, label: development, position: 0) } + let!(:list2) { create(:list, board: board1, label: testing, position: 1) } + let!(:done) { create(:done_list, board: board1) } before do project.team << [user, :developer] @@ -22,7 +22,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from backlog' do it 'adds the label of the list it goes to' do issue = create(:labeled_issue, project: project, labels: [bug]) - params = { board_id: board.id, from_list_id: backlog.id, to_list_id: list1.id } + params = { board_id: board1.id, from_list_id: backlog.id, to_list_id: list1.id } described_class.new(project, user, params).execute(issue) @@ -33,7 +33,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving to backlog' do it 'removes all list-labels' do issue = create(:labeled_issue, project: project, labels: [bug, development, testing]) - params = { board_id: board.id, from_list_id: list1.id, to_list_id: backlog.id } + params = { board_id: board1.id, from_list_id: list1.id, to_list_id: backlog.id } described_class.new(project, user, params).execute(issue) @@ -44,7 +44,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from backlog to done' do it 'closes the issue' do issue = create(:labeled_issue, project: project, labels: [bug]) - params = { board_id: board.id, from_list_id: backlog.id, to_list_id: done.id } + params = { board_id: board1.id, from_list_id: backlog.id, to_list_id: done.id } described_class.new(project, user, params).execute(issue) issue.reload @@ -56,7 +56,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving an issue between lists' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { board_id: board.id, from_list_id: list1.id, to_list_id: list2.id } } + let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list2.id } } it 'delegates the label changes to Issues::UpdateService' do expect_any_instance_of(Issues::UpdateService).to receive(:execute).with(issue).once @@ -72,8 +72,12 @@ describe Boards::Issues::MoveService, services: true do end context 'when moving to done' do - let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing]) } - let(:params) { { board_id: board.id, from_list_id: list2.id, to_list_id: done.id } } + let(:board2) { create(:board, project: project) } + let(:regression) { create(:label, project: project, name: 'Regression') } + let!(:list3) { create(:list, board: board2, label: regression, position: 1) } + + let(:issue) { create(:labeled_issue, project: project, labels: [bug, development, testing, regression]) } + let(:params) { { board_id: board1.id, from_list_id: list2.id, to_list_id: done.id } } it 'delegates the close proceedings to Issues::CloseService' do expect_any_instance_of(Issues::CloseService).to receive(:execute).with(issue).once @@ -81,7 +85,7 @@ describe Boards::Issues::MoveService, services: true do described_class.new(project, user, params).execute(issue) end - it 'removes all list-labels and close the issue' do + it 'removes all list-labels from project boards and close the issue' do described_class.new(project, user, params).execute(issue) issue.reload @@ -92,7 +96,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from done' do let(:issue) { create(:labeled_issue, :closed, project: project, labels: [bug]) } - let(:params) { { board_id: board.id, from_list_id: done.id, to_list_id: list2.id } } + let(:params) { { board_id: board1.id, from_list_id: done.id, to_list_id: list2.id } } it 'delegates the re-open proceedings to Issues::ReopenService' do expect_any_instance_of(Issues::ReopenService).to receive(:execute).with(issue).once @@ -112,7 +116,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving from done to backlog' do it 'reopens the issue' do issue = create(:labeled_issue, :closed, project: project, labels: [bug]) - params = { board_id: board.id, from_list_id: done.id, to_list_id: backlog.id } + params = { board_id: board1.id, from_list_id: done.id, to_list_id: backlog.id } described_class.new(project, user, params).execute(issue) issue.reload @@ -124,7 +128,7 @@ describe Boards::Issues::MoveService, services: true do context 'when moving to same list' do let(:issue) { create(:labeled_issue, project: project, labels: [bug, development]) } - let(:params) { { board_id: board.id, from_list_id: list1.id, to_list_id: list1.id } } + let(:params) { { board_id: board1.id, from_list_id: list1.id, to_list_id: list1.id } } it 'returns false' do expect(described_class.new(project, user, params).execute(issue)).to eq false -- cgit v1.2.1 From b4b8e0ec9405c4b5d17b53552612397e847e734d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 16:01:06 -0300 Subject: Add Boards::ListService service to list project boards --- app/services/boards/list_service.rb | 14 ++++++++++++ spec/services/boards/list_service_spec.rb | 37 +++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 app/services/boards/list_service.rb create mode 100644 spec/services/boards/list_service_spec.rb diff --git a/app/services/boards/list_service.rb b/app/services/boards/list_service.rb new file mode 100644 index 00000000000..cca4bdd82a2 --- /dev/null +++ b/app/services/boards/list_service.rb @@ -0,0 +1,14 @@ +module Boards + class ListService < Boards::BaseService + def execute + create_board! if project.boards.empty? + project.boards + end + + private + + def create_board! + Boards::CreateService.new(project, current_user).execute + end + end +end diff --git a/spec/services/boards/list_service_spec.rb b/spec/services/boards/list_service_spec.rb new file mode 100644 index 00000000000..dff33e4bcbb --- /dev/null +++ b/spec/services/boards/list_service_spec.rb @@ -0,0 +1,37 @@ +require 'spec_helper' + +describe Boards::ListService, services: true do + describe '#execute' do + let(:project) { create(:empty_project) } + + subject(:service) { described_class.new(project, double) } + + context 'when project does not have a board' do + it 'creates a new project board' do + expect { service.execute }.to change(project.boards, :count).by(1) + end + + it 'delegates the project board creation to Boards::CreateService' do + expect_any_instance_of(Boards::CreateService).to receive(:execute).once + + service.execute + end + end + + context 'when project has a board' do + before do + create(:board, project: project) + end + + it 'does not create a new board' do + expect { service.execute }.not_to change(project.boards, :count) + end + end + + it 'returns project boards' do + board = create(:board, project: project) + + expect(service.execute).to match_array [board] + end + end +end -- cgit v1.2.1 From ecf4c10e9c395604583820ad01167db34d09d4aa Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 16:24:29 -0300 Subject: Add index action to Projects::BoardsController to return project boards --- app/controllers/projects/boards_controller.rb | 15 +++++-- app/views/projects/boards/index.html.haml | 0 config/routes/project.rb | 2 +- .../controllers/projects/boards_controller_spec.rb | 47 ++++++++++++++++++++++ spec/fixtures/api/schemas/board.json | 12 ++++++ spec/fixtures/api/schemas/boards.json | 4 ++ 6 files changed, 76 insertions(+), 4 deletions(-) create mode 100644 app/views/projects/boards/index.html.haml create mode 100644 spec/fixtures/api/schemas/board.json create mode 100644 spec/fixtures/api/schemas/boards.json diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 0035633b774..56bc54fbd1c 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -1,9 +1,18 @@ class Projects::BoardsController < Projects::ApplicationController include IssuableCollections - - respond_to :html - before_action :authorize_read_board!, only: [:show] + before_action :authorize_read_board!, only: [:index, :show] + + def index + @boards = ::Boards::ListService.new(project, current_user).execute + + respond_to do |format| + format.html + format.json do + render json: @boards.as_json(only: [:id, :name]) + end + end + end def show ::Boards::CreateService.new(project, current_user).execute diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml new file mode 100644 index 00000000000..e69de29bb2d diff --git a/config/routes/project.rb b/config/routes/project.rb index e8807ef06a7..3a7c4f86301 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -416,7 +416,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: end end - resource :board, only: [:show] do + resources :boards, only: [:index, :show] do scope module: :boards do resources :issues, only: [:update] diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index 6f6e608e1f3..d7698afd141 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -9,6 +9,53 @@ describe Projects::BoardsController do sign_in(user) end + describe 'GET index' do + it 'creates a new project board when project does not have one' do + expect { list_boards }.to change(project.boards, :count).by(1) + end + + context 'when format is HTML' do + it 'renders template' do + list_boards + + expect(response).to render_template :index + expect(response.content_type).to eq 'text/html' + end + end + + context 'when format is JSON' do + it 'returns a list of project boards' do + create_list(:board, 2, project: project) + + list_boards format: :json + + parsed_response = JSON.parse(response.body) + + expect(response).to match_response_schema('boards') + expect(parsed_response.length).to eq 2 + end + end + + context 'with unauthorized user' do + before do + allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(true) + allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false) + end + + it 'returns a not found 404 response' do + list_boards + + expect(response).to have_http_status(404) + end + end + + def list_boards(format: :html) + get :index, namespace_id: project.namespace.to_param, + project_id: project.to_param, + format: format + end + end + describe 'GET show' do it 'creates a new board when project does not have one' do expect { read_board }.to change(Board, :count).by(1) diff --git a/spec/fixtures/api/schemas/board.json b/spec/fixtures/api/schemas/board.json new file mode 100644 index 00000000000..6c6e2bee8cb --- /dev/null +++ b/spec/fixtures/api/schemas/board.json @@ -0,0 +1,12 @@ +{ + "type": "object", + "required" : [ + "id", + "name" + ], + "properties" : { + "id": { "type": "integer" }, + "name": { "type": "string" } + }, + "additionalProperties": false +} diff --git a/spec/fixtures/api/schemas/boards.json b/spec/fixtures/api/schemas/boards.json new file mode 100644 index 00000000000..117564ef77a --- /dev/null +++ b/spec/fixtures/api/schemas/boards.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "board.json" } +} -- cgit v1.2.1 From 723ed9cc3a76f7ce0e2d1b358a33d05fb05865c9 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 17:08:55 -0300 Subject: Update Projects::BoardsController#show to look up for a specific board --- app/controllers/projects/boards_controller.rb | 15 +++++++-- .../controllers/projects/boards_controller_spec.rb | 37 ++++++++++++++++------ 2 files changed, 41 insertions(+), 11 deletions(-) diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index 56bc54fbd1c..bbb1b1bf1e1 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -9,13 +9,20 @@ class Projects::BoardsController < Projects::ApplicationController respond_to do |format| format.html format.json do - render json: @boards.as_json(only: [:id, :name]) + render json: serialize_as_json(@boards) end end end def show - ::Boards::CreateService.new(project, current_user).execute + @board = project.boards.find(params[:id]) + + respond_to do |format| + format.html + format.json do + render json: serialize_as_json(@board) + end + end end private @@ -23,4 +30,8 @@ class Projects::BoardsController < Projects::ApplicationController def authorize_read_board! return access_denied! unless can?(current_user, :read_board, project) end + + def serialize_as_json(resource) + resource.as_json(only: [:id, :name]) + end end diff --git a/spec/controllers/projects/boards_controller_spec.rb b/spec/controllers/projects/boards_controller_spec.rb index d7698afd141..cc19035740e 100644 --- a/spec/controllers/projects/boards_controller_spec.rb +++ b/spec/controllers/projects/boards_controller_spec.rb @@ -57,15 +57,23 @@ describe Projects::BoardsController do end describe 'GET show' do - it 'creates a new board when project does not have one' do - expect { read_board }.to change(Board, :count).by(1) + let!(:board) { create(:board, project: project) } + + context 'when format is HTML' do + it 'renders template' do + read_board board: board + + expect(response).to render_template :show + expect(response.content_type).to eq 'text/html' + end end - it 'renders HTML template' do - read_board + context 'when format is JSON' do + it 'returns project board' do + read_board board: board, format: :json - expect(response).to render_template :show - expect(response.content_type).to eq 'text/html' + expect(response).to match_response_schema('board') + end end context 'with unauthorized user' do @@ -74,16 +82,27 @@ describe Projects::BoardsController do allow(Ability).to receive(:allowed?).with(user, :read_board, project).and_return(false) end - it 'returns a successful 404 response' do - read_board + it 'returns a not found 404 response' do + read_board board: board + + expect(response).to have_http_status(404) + end + end + + context 'when board does not belong to project' do + it 'returns a not found 404 response' do + another_board = create(:board) + + read_board board: another_board expect(response).to have_http_status(404) end end - def read_board(format: :html) + def read_board(board:, format: :html) get :show, namespace_id: project.namespace.to_param, project_id: project.to_param, + id: board.to_param, format: format end end -- cgit v1.2.1 From 5de4fba6e34ebf31dc6e737bf96dbd65d3daf958 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 19:24:42 -0300 Subject: Update Boards::Lists::DestroyService to remove list on a specic board --- app/services/boards/lists/destroy_service.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/services/boards/lists/destroy_service.rb b/app/services/boards/lists/destroy_service.rb index 25da3bfb56d..d75c5fd3dc6 100644 --- a/app/services/boards/lists/destroy_service.rb +++ b/app/services/boards/lists/destroy_service.rb @@ -4,6 +4,8 @@ module Boards def execute(list) return false unless list.destroyable? + @board = list.board + list.with_lock do decrement_higher_lists(list) remove_list(list) @@ -12,6 +14,8 @@ module Boards private + attr_reader :board + def decrement_higher_lists(list) board.lists.movable.where('position > ?', list.position) .update_all('position = position - 1') -- cgit v1.2.1 From e1f889df6463203e5bd899fca8e98de6b705cd43 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 19:25:29 -0300 Subject: Update endpoints to handle with board list changes --- .../projects/boards/lists_controller.rb | 18 ++++--- .../projects/boards/lists_controller_spec.rb | 57 ++++++++++++---------- 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/app/controllers/projects/boards/lists_controller.rb b/app/controllers/projects/boards/lists_controller.rb index b995f586737..76ae41319c4 100644 --- a/app/controllers/projects/boards/lists_controller.rb +++ b/app/controllers/projects/boards/lists_controller.rb @@ -5,11 +5,11 @@ module Projects before_action :authorize_read_list!, only: [:index] def index - render json: serialize_as_json(project.board.lists) + render json: serialize_as_json(board.lists) end def create - list = ::Boards::Lists::CreateService.new(project, current_user, list_params).execute + list = ::Boards::Lists::CreateService.new(project, current_user, list_params).execute(board) if list.valid? render json: serialize_as_json(list) @@ -19,7 +19,7 @@ module Projects end def update - list = project.board.lists.movable.find(params[:id]) + list = board.lists.movable.find(params[:id]) service = ::Boards::Lists::MoveService.new(project, current_user, move_params) if service.execute(list) @@ -30,8 +30,8 @@ module Projects end def destroy - list = project.board.lists.destroyable.find(params[:id]) - service = ::Boards::Lists::DestroyService.new(project, current_user, params) + list = board.lists.destroyable.find(params[:id]) + service = ::Boards::Lists::DestroyService.new(project, current_user) if service.execute(list) head :ok @@ -43,8 +43,8 @@ module Projects def generate service = ::Boards::Lists::GenerateService.new(project, current_user) - if service.execute - render json: serialize_as_json(project.board.lists.movable) + if service.execute(board) + render json: serialize_as_json(board.lists.movable) else head :unprocessable_entity end @@ -60,6 +60,10 @@ module Projects return render_403 unless can?(current_user, :read_list, project) end + def board + @board ||= project.boards.find(params[:board_id]) + end + def list_params params.require(:list).permit(:label_id) end diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb index 709006a3601..a53a5feef44 100644 --- a/spec/controllers/projects/boards/lists_controller_spec.rb +++ b/spec/controllers/projects/boards/lists_controller_spec.rb @@ -13,7 +13,7 @@ describe Projects::Boards::ListsController do describe 'GET index' do it 'returns a successful 200 response' do - read_board_list user: user + read_board_list user: user, board: board expect(response).to have_http_status(200) expect(response.content_type).to eq 'application/json' @@ -22,7 +22,7 @@ describe Projects::Boards::ListsController do it 'returns a list of board lists' do create(:list, board: board) - read_board_list user: user + read_board_list user: user, board: board parsed_response = JSON.parse(response.body) @@ -37,17 +37,18 @@ describe Projects::Boards::ListsController do end it 'returns a forbidden 403 response' do - read_board_list user: user + read_board_list user: user, board: board expect(response).to have_http_status(403) end end - def read_board_list(user:) + def read_board_list(user:, board:) sign_in(user) get :index, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, format: :json end end @@ -57,13 +58,13 @@ describe Projects::Boards::ListsController do let(:label) { create(:label, project: project, name: 'Development') } it 'returns a successful 200 response' do - create_board_list user: user, label_id: label.id + create_board_list user: user, board: board, label_id: label.id expect(response).to have_http_status(200) end it 'returns the created list' do - create_board_list user: user, label_id: label.id + create_board_list user: user, board: board, label_id: label.id expect(response).to match_response_schema('list') end @@ -72,7 +73,7 @@ describe Projects::Boards::ListsController do context 'with invalid params' do context 'when label is nil' do it 'returns a not found 404 response' do - create_board_list user: user, label_id: nil + create_board_list user: user, board: board, label_id: nil expect(response).to have_http_status(404) end @@ -82,7 +83,7 @@ describe Projects::Boards::ListsController do it 'returns a not found 404 response' do label = create(:label, name: 'Development') - create_board_list user: user, label_id: label.id + create_board_list user: user, board: board, label_id: label.id expect(response).to have_http_status(404) end @@ -93,17 +94,18 @@ describe Projects::Boards::ListsController do it 'returns a forbidden 403 response' do label = create(:label, project: project, name: 'Development') - create_board_list user: guest, label_id: label.id + create_board_list user: guest, board: board, label_id: label.id expect(response).to have_http_status(403) end end - def create_board_list(user:, label_id:) + def create_board_list(user:, board:, label_id:) sign_in(user) post :create, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, list: { label_id: label_id }, format: :json end @@ -115,13 +117,13 @@ describe Projects::Boards::ListsController do context 'with valid position' do it 'returns a successful 200 response' do - move user: user, list: planning, position: 1 + move user: user, board: board, list: planning, position: 1 expect(response).to have_http_status(200) end it 'moves the list to the desired position' do - move user: user, list: planning, position: 1 + move user: user, board: board, list: planning, position: 1 expect(planning.reload.position).to eq 1 end @@ -129,7 +131,7 @@ describe Projects::Boards::ListsController do context 'with invalid position' do it 'returns an unprocessable entity 422 response' do - move user: user, list: planning, position: 6 + move user: user, board: board, list: planning, position: 6 expect(response).to have_http_status(422) end @@ -137,7 +139,7 @@ describe Projects::Boards::ListsController do context 'with invalid list id' do it 'returns a not found 404 response' do - move user: user, list: 999, position: 1 + move user: user, board: board, list: 999, position: 1 expect(response).to have_http_status(404) end @@ -145,17 +147,18 @@ describe Projects::Boards::ListsController do context 'with unauthorized user' do it 'returns a forbidden 403 response' do - move user: guest, list: planning, position: 6 + move user: guest, board: board, list: planning, position: 6 expect(response).to have_http_status(403) end end - def move(user:, list:, position:) + def move(user:, board:, list:, position:) sign_in(user) patch :update, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, id: list.to_param, list: { position: position }, format: :json @@ -167,19 +170,19 @@ describe Projects::Boards::ListsController do context 'with valid list id' do it 'returns a successful 200 response' do - remove_board_list user: user, list: planning + remove_board_list user: user, board: board, list: planning expect(response).to have_http_status(200) end it 'removes list from board' do - expect { remove_board_list user: user, list: planning }.to change(board.lists, :size).by(-1) + expect { remove_board_list user: user, board: board, list: planning }.to change(board.lists, :size).by(-1) end end context 'with invalid list id' do it 'returns a not found 404 response' do - remove_board_list user: user, list: 999 + remove_board_list user: user, board: board, list: 999 expect(response).to have_http_status(404) end @@ -187,17 +190,18 @@ describe Projects::Boards::ListsController do context 'with unauthorized user' do it 'returns a forbidden 403 response' do - remove_board_list user: guest, list: planning + remove_board_list user: guest, board: board, list: planning expect(response).to have_http_status(403) end end - def remove_board_list(user:, list:) + def remove_board_list(user:, board:, list:) sign_in(user) delete :destroy, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, id: list.to_param, format: :json end @@ -206,13 +210,13 @@ describe Projects::Boards::ListsController do describe 'POST generate' do context 'when board lists is empty' do it 'returns a successful 200 response' do - generate_default_board_lists user: user + generate_default_lists user: user, board: board expect(response).to have_http_status(200) end it 'returns the defaults lists' do - generate_default_board_lists user: user + generate_default_lists user: user, board: board expect(response).to match_response_schema('lists') end @@ -222,7 +226,7 @@ describe Projects::Boards::ListsController do it 'returns an unprocessable entity 422 response' do create(:list, board: board) - generate_default_board_lists user: user + generate_default_lists user: user, board: board expect(response).to have_http_status(422) end @@ -230,17 +234,18 @@ describe Projects::Boards::ListsController do context 'with unauthorized user' do it 'returns a forbidden 403 response' do - generate_default_board_lists user: guest + generate_default_lists user: guest, board: board expect(response).to have_http_status(403) end end - def generate_default_board_lists(user:) + def generate_default_lists(user:, board: board) sign_in(user) post :generate, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, format: :json end end -- cgit v1.2.1 From 67515098657704505935119503ef23e45f0fda04 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 23:44:44 -0300 Subject: Update endpoints to handle with board issues --- .../projects/boards/issues_controller.rb | 4 +- .../projects/boards/issues_controller_spec.rb | 53 ++++++++++++++++------ 2 files changed, 40 insertions(+), 17 deletions(-) diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 095af6c35eb..82c99e4e8d9 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -60,11 +60,11 @@ module Projects end def filter_params - params.merge(id: params[:list_id]) + params.merge(board_id: params[:board_id], id: params[:list_id]) end def move_params - params.permit(:id, :from_list_id, :to_list_id) + params.permit(:board_id, :id, :from_list_id, :to_list_id) end def issue_params diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb index 566658b508d..92e77291da9 100644 --- a/spec/controllers/projects/boards/issues_controller_spec.rb +++ b/spec/controllers/projects/boards/issues_controller_spec.rb @@ -1,15 +1,16 @@ require 'spec_helper' describe Projects::Boards::IssuesController do - let(:project) { create(:project_with_board) } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:guest) { create(:user) } let(:planning) { create(:label, project: project, name: 'Planning') } let(:development) { create(:label, project: project, name: 'Development') } - let!(:list1) { create(:list, board: project.board, label: planning, position: 0) } - let!(:list2) { create(:list, board: project.board, label: development, position: 1) } + let!(:list1) { create(:list, board: board, label: planning, position: 0) } + let!(:list2) { create(:list, board: board, label: development, position: 1) } before do project.team << [user, :master] @@ -24,7 +25,7 @@ describe Projects::Boards::IssuesController do create(:labeled_issue, project: project, labels: [development]) create(:labeled_issue, project: project, labels: [development], assignee: johndoe) - list_issues user: user, list_id: list2 + list_issues user: user, board: board, list: list2 parsed_response = JSON.parse(response.body) @@ -33,9 +34,17 @@ describe Projects::Boards::IssuesController do end end + context 'with invalid board id' do + it 'returns a not found 404 response' do + list_issues user: user, board: 999, list: list2 + + expect(response).to have_http_status(404) + end + end + context 'with invalid list id' do it 'returns a not found 404 response' do - list_issues user: user, list_id: 999 + list_issues user: user, board: board, list: 999 expect(response).to have_http_status(404) end @@ -47,19 +56,20 @@ describe Projects::Boards::IssuesController do allow(Ability).to receive(:allowed?).with(user, :read_issue, project).and_return(false) end - it 'returns a successful 403 response' do - list_issues user: user, list_id: list2 + it 'returns a forbidden 403 response' do + list_issues user: user, board: board, list: list2 expect(response).to have_http_status(403) end end - def list_issues(user:, list_id:) + def list_issues(user:, board:, list:) sign_in(user) get :index, namespace_id: project.namespace.to_param, project_id: project.to_param, - list_id: list_id.to_param + board_id: board.to_param, + list_id: list.to_param end end @@ -122,13 +132,13 @@ describe Projects::Boards::IssuesController do context 'with valid params' do it 'returns a successful 200 response' do - move user: user, issue: issue, from_list_id: list1.id, to_list_id: list2.id + move user: user, board: board, issue: issue, from_list_id: list1.id, to_list_id: list2.id expect(response).to have_http_status(200) end it 'moves issue to the desired list' do - move user: user, issue: issue, from_list_id: list1.id, to_list_id: list2.id + move user: user, board: board, issue: issue, from_list_id: list1.id, to_list_id: list2.id expect(issue.reload.labels).to contain_exactly(development) end @@ -136,31 +146,44 @@ describe Projects::Boards::IssuesController do context 'with invalid params' do it 'returns a unprocessable entity 422 response for invalid lists' do - move user: user, issue: issue, from_list_id: nil, to_list_id: nil + move user: user, board: board, issue: issue, from_list_id: nil, to_list_id: nil expect(response).to have_http_status(422) end + it 'returns a not found 404 response for invalid board id' do + move user: user, board: 999, issue: issue, from_list_id: list1.id, to_list_id: list2.id + + expect(response).to have_http_status(404) + end + it 'returns a not found 404 response for invalid issue id' do - move user: user, issue: 999, from_list_id: list1.id, to_list_id: list2.id + move user: user, board: board, issue: 999, from_list_id: list1.id, to_list_id: list2.id expect(response).to have_http_status(404) end end context 'with unauthorized user' do + let(:guest) { create(:user) } + + before do + project.team << [guest, :guest] + end + it 'returns a forbidden 403 response' do - move user: guest, issue: issue, from_list_id: list1.id, to_list_id: list2.id + move user: guest, board: board, issue: issue, from_list_id: list1.id, to_list_id: list2.id expect(response).to have_http_status(403) end end - def move(user:, issue:, from_list_id:, to_list_id:) + def move(user:, board:, issue:, from_list_id:, to_list_id:) sign_in(user) patch :update, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, id: issue.to_param, from_list_id: from_list_id, to_list_id: to_list_id, -- cgit v1.2.1 From a88ed81fb97cdd04b3ca222a534f8319319d7091 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 23:45:54 -0300 Subject: Remove unused Projects::BoardListsController controller --- app/controllers/projects/board_lists_controller.rb | 65 ---------------------- 1 file changed, 65 deletions(-) delete mode 100644 app/controllers/projects/board_lists_controller.rb diff --git a/app/controllers/projects/board_lists_controller.rb b/app/controllers/projects/board_lists_controller.rb deleted file mode 100644 index 3cfb08d5822..00000000000 --- a/app/controllers/projects/board_lists_controller.rb +++ /dev/null @@ -1,65 +0,0 @@ -class Projects::BoardListsController < Projects::ApplicationController - respond_to :json - - before_action :authorize_admin_list! - - rescue_from ActiveRecord::RecordNotFound, with: :record_not_found - - def create - list = Boards::Lists::CreateService.new(project, current_user, list_params).execute - - if list.valid? - render json: list.as_json(only: [:id, :list_type, :position], methods: [:title], include: { label: { only: [:id, :title, :description, :color, :priority] } }) - else - render json: list.errors, status: :unprocessable_entity - end - end - - def update - service = Boards::Lists::MoveService.new(project, current_user, move_params) - - if service.execute - head :ok - else - head :unprocessable_entity - end - end - - def destroy - service = Boards::Lists::DestroyService.new(project, current_user, params) - - if service.execute - head :ok - else - head :unprocessable_entity - end - end - - def generate - service = Boards::Lists::GenerateService.new(project, current_user) - - if service.execute - render json: project.board.lists.label.as_json(only: [:id, :list_type, :position], methods: [:title], include: { label: { only: [:id, :title, :description, :color, :priority] } }) - else - head :unprocessable_entity - end - end - - private - - def authorize_admin_list! - return render_403 unless can?(current_user, :admin_list, project) - end - - def list_params - params.require(:list).permit(:label_id) - end - - def move_params - params.require(:list).permit(:position).merge(id: params[:id]) - end - - def record_not_found(exception) - render json: { error: exception.message }, status: :not_found - end -end -- cgit v1.2.1 From 38cece495715df191fcb0c4ec0c5c2e44da2debf Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 5 Oct 2016 23:50:35 -0300 Subject: Fix rubocop offenses --- .../projects/boards/lists_controller_spec.rb | 2 +- spec/services/boards/issues/list_service_spec.rb | 22 +++++++++++----------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb index a53a5feef44..afede0191dc 100644 --- a/spec/controllers/projects/boards/lists_controller_spec.rb +++ b/spec/controllers/projects/boards/lists_controller_spec.rb @@ -240,7 +240,7 @@ describe Projects::Boards::ListsController do end end - def generate_default_lists(user:, board: board) + def generate_default_lists(user:, board:) sign_in(user) post :generate, namespace_id: project.namespace.to_param, diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index cc96fd05189..218a6d2bc2f 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -70,21 +70,21 @@ describe Boards::Issues::ListService, services: true do end end - context 'with list that does not belongs to the board' do - it 'raises an error' do - list = create(:list) - service = described_class.new(project, user, board_id: board.id, id: list.id) + context 'with list that does not belongs to the board' do + it 'raises an error' do + list = create(:list) + service = described_class.new(project, user, board_id: board.id, id: list.id) - expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) - end + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) end + end - context 'with invalid list id' do - it 'raises an error' do - service = described_class.new(project, user, board_id: board.id, id: nil) + context 'with invalid list id' do + it 'raises an error' do + service = described_class.new(project, user, board_id: board.id, id: nil) - expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) - end + expect { service.execute }.to raise_error(ActiveRecord::RecordNotFound) end + end end end -- cgit v1.2.1 From 9dc4ebc8ea5cd223b17cbc1d772348bebc50cdeb Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 14:35:58 -0300 Subject: Fix links to issue boards --- app/views/layouts/nav/_project.html.haml | 2 +- app/views/projects/boards/show.html.haml | 2 +- app/views/projects/issues/_head.html.haml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index e44a2bfed9d..99a58bbb676 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -116,4 +116,4 @@ -# Shortcut to issue boards %li.hidden - = link_to 'Issue Boards', namespace_project_board_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards' + = link_to 'Issue Boards', namespace_project_boards_path(@project.namespace, @project), title: 'Issue Boards', class: 'shortcuts-issue-boards' diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index edbbd3f3d2a..5b441b48caf 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -11,7 +11,7 @@ = render 'shared/issuable/filter', type: :boards .boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project)}", + "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } .boards-app-loading.text-center{ "v-if" => "loading" } diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml index 509b01c548a..4825820c4d9 100644 --- a/app/views/projects/issues/_head.html.haml +++ b/app/views/projects/issues/_head.html.haml @@ -10,7 +10,7 @@ Issues = nav_link(controller: :boards) do - = link_to namespace_project_board_path(@project.namespace, @project), title: 'Board' do + = link_to namespace_project_boards_path(@project.namespace, @project), title: 'Board' do %span Board -- cgit v1.2.1 From 81c253ded12c96bfa3f5cf27f0e23c61619ad914 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 14:37:41 -0300 Subject: Add Project#boards to import/export configuration file --- spec/lib/gitlab/import_export/all_models.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 006569254a6..5d5836e9bee 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -118,7 +118,7 @@ project: - creator - group - namespace -- board +- boards - last_event - services - campfire_service -- cgit v1.2.1 From 2c2a1dea67ef41a6e283c4816865645fba318a16 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 17:13:33 -0300 Subject: Refactoring service to create a new issue in a board list --- app/controllers/projects/boards/issues_controller.rb | 5 ++--- app/services/boards/issues/create_service.rb | 15 +++++++++++---- .../controllers/projects/boards/issues_controller_spec.rb | 13 +++++++------ spec/services/boards/issues/create_service_spec.rb | 12 ++++++------ 4 files changed, 26 insertions(+), 19 deletions(-) diff --git a/app/controllers/projects/boards/issues_controller.rb b/app/controllers/projects/boards/issues_controller.rb index 82c99e4e8d9..71eb56aed0b 100644 --- a/app/controllers/projects/boards/issues_controller.rb +++ b/app/controllers/projects/boards/issues_controller.rb @@ -16,9 +16,8 @@ module Projects end def create - list = project.board.lists.find(params[:list_id]) service = ::Boards::Issues::CreateService.new(project, current_user, issue_params) - issue = service.execute(list) + issue = service.execute if issue.valid? render json: serialize_as_json(issue) @@ -68,7 +67,7 @@ module Projects end def issue_params - params.require(:issue).permit(:title).merge(request: request) + params.require(:issue).permit(:title).merge(board_id: params[:board_id], list_id: params[:list_id], request: request) end def serialize_as_json(resource) diff --git a/app/services/boards/issues/create_service.rb b/app/services/boards/issues/create_service.rb index 3701afd441f..a181e74043b 100644 --- a/app/services/boards/issues/create_service.rb +++ b/app/services/boards/issues/create_service.rb @@ -1,14 +1,21 @@ module Boards module Issues class CreateService < Boards::BaseService - def execute(list) - params.merge!(label_ids: [list.label_id]) - create_issue + def execute + create_issue(params.merge(label_ids: [list.label_id])) end private - def create_issue + def board + @board ||= project.boards.find(params.delete(:board_id)) + end + + def list + @list ||= board.lists.find(params.delete(:list_id)) + end + + def create_issue(params) ::Issues::CreateService.new(project, current_user, params).execute end end diff --git a/spec/controllers/projects/boards/issues_controller_spec.rb b/spec/controllers/projects/boards/issues_controller_spec.rb index 92e77291da9..da59642f24d 100644 --- a/spec/controllers/projects/boards/issues_controller_spec.rb +++ b/spec/controllers/projects/boards/issues_controller_spec.rb @@ -76,13 +76,13 @@ describe Projects::Boards::IssuesController do describe 'POST create' do context 'with valid params' do it 'returns a successful 200 response' do - create_issue user: user, list: list1, title: 'New issue' + create_issue user: user, board: board, list: list1, title: 'New issue' expect(response).to have_http_status(200) end it 'returns the created issue' do - create_issue user: user, list: list1, title: 'New issue' + create_issue user: user, board: board, list: list1, title: 'New issue' expect(response).to match_response_schema('issue') end @@ -91,7 +91,7 @@ describe Projects::Boards::IssuesController do context 'with invalid params' do context 'when title is nil' do it 'returns an unprocessable entity 422 response' do - create_issue user: user, list: list1, title: nil + create_issue user: user, board: board, list: list1, title: nil expect(response).to have_http_status(422) end @@ -101,7 +101,7 @@ describe Projects::Boards::IssuesController do it 'returns a not found 404 response' do list = create(:list) - create_issue user: user, list: list, title: 'New issue' + create_issue user: user, board: board, list: list, title: 'New issue' expect(response).to have_http_status(404) end @@ -110,17 +110,18 @@ describe Projects::Boards::IssuesController do context 'with unauthorized user' do it 'returns a forbidden 403 response' do - create_issue user: guest, list: list1, title: 'New issue' + create_issue user: guest, board: board, list: list1, title: 'New issue' expect(response).to have_http_status(403) end end - def create_issue(user:, list:, title:) + def create_issue(user:, board:, list:, title:) sign_in(user) post :create, namespace_id: project.namespace.to_param, project_id: project.to_param, + board_id: board.to_param, list_id: list.to_param, issue: { title: title }, format: :json diff --git a/spec/services/boards/issues/create_service_spec.rb b/spec/services/boards/issues/create_service_spec.rb index 33e10e79f6d..360ee398f77 100644 --- a/spec/services/boards/issues/create_service_spec.rb +++ b/spec/services/boards/issues/create_service_spec.rb @@ -2,13 +2,13 @@ require 'spec_helper' describe Boards::Issues::CreateService, services: true do describe '#execute' do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:label) { create(:label, project: project, name: 'in-progress') } let!(:list) { create(:list, board: board, label: label, position: 0) } - subject(:service) { described_class.new(project, user, title: 'New issue') } + subject(:service) { described_class.new(project, user, board_id: board.id, list_id: list.id, title: 'New issue') } before do project.team << [user, :developer] @@ -17,15 +17,15 @@ describe Boards::Issues::CreateService, services: true do it 'delegates the create proceedings to Issues::CreateService' do expect_any_instance_of(Issues::CreateService).to receive(:execute).once - service.execute(list) + service.execute end it 'creates a new issue' do - expect { service.execute(list) }.to change(project.issues, :count).by(1) + expect { service.execute }.to change(project.issues, :count).by(1) end it 'adds the label of the list to the issue' do - issue = service.execute(list) + issue = service.execute expect(issue.labels).to eq [label] end -- cgit v1.2.1 From 1a2002d9084db71d5a0a2631985012b4544c4037 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 17:14:39 -0300 Subject: Update board specs to use board factory instead of project_with_board --- spec/controllers/projects/boards/lists_controller_spec.rb | 4 ++-- spec/features/boards/boards_spec.rb | 5 +++-- spec/features/boards/new_issue_spec.rb | 3 ++- spec/services/boards/issues/list_service_spec.rb | 2 +- spec/services/boards/lists/destroy_service_spec.rb | 12 ++++++------ spec/services/boards/lists/list_service_spec.rb | 4 +--- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/spec/controllers/projects/boards/lists_controller_spec.rb b/spec/controllers/projects/boards/lists_controller_spec.rb index afede0191dc..34d6119429d 100644 --- a/spec/controllers/projects/boards/lists_controller_spec.rb +++ b/spec/controllers/projects/boards/lists_controller_spec.rb @@ -1,8 +1,8 @@ require 'spec_helper' describe Projects::Boards::ListsController do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let(:guest) { create(:user) } diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index 470e2bdbb9b..f79b6a77696 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -4,7 +4,8 @@ describe 'Issue Boards', feature: true, js: true do include WaitForAjax include WaitForVueResource - let(:project) { create(:project_with_board, :public) } + let(:project) { create(:empty_project, :public) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } let!(:user2) { create(:user) } @@ -468,7 +469,7 @@ describe 'Issue Boards', feature: true, js: true do it 'removes filtered labels' do wait_for_vue_resource - + page.within '.labels-filter' do click_button('Label') wait_for_ajax diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index c046e6b8d79..1ceb60d5297 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -4,7 +4,8 @@ describe 'Issue Boards new issue', feature: true, js: true do include WaitForAjax include WaitForVueResource - let(:project) { create(:project_with_board, :public) } + let(:project) { create(:empty_project, :public) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } context 'authorized user' do diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index 218a6d2bc2f..d9eb757f986 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Boards::Issues::ListService, services: true do describe '#execute' do let(:user) { create(:user) } - let(:project) { create(:project_with_board) } + let(:project) { create(:empty_project) } let(:board) { create(:board, project: project) } let(:bug) { create(:label, project: project, name: 'Bug') } diff --git a/spec/services/boards/lists/destroy_service_spec.rb b/spec/services/boards/lists/destroy_service_spec.rb index 474c4512471..628caf03476 100644 --- a/spec/services/boards/lists/destroy_service_spec.rb +++ b/spec/services/boards/lists/destroy_service_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' describe Boards::Lists::DestroyService, services: true do describe '#execute' do - let(:project) { create(:project_with_board) } - let(:board) { project.board } + let(:project) { create(:empty_project) } + let(:board) { create(:board, project: project) } let(:user) { create(:user) } context 'when list type is label' do @@ -15,11 +15,11 @@ describe Boards::Lists::DestroyService, services: true do end it 'decrements position of higher lists' do - backlog = project.board.backlog_list + backlog = board.backlog_list development = create(:list, board: board, position: 0) review = create(:list, board: board, position: 1) staging = create(:list, board: board, position: 2) - done = project.board.done_list + done = board.done_list described_class.new(project, user).execute(development) @@ -31,14 +31,14 @@ describe Boards::Lists::DestroyService, services: true do end it 'does not remove list from board when list type is backlog' do - list = project.board.backlog_list + list = board.backlog_list service = described_class.new(project, user) expect { service.execute(list) }.not_to change(board.lists, :count) end it 'does not remove list from board when list type is done' do - list = project.board.done_list + list = board.done_list service = described_class.new(project, user) expect { service.execute(list) }.not_to change(board.lists, :count) diff --git a/spec/services/boards/lists/list_service_spec.rb b/spec/services/boards/lists/list_service_spec.rb index 4464f80f796..334cee3f06d 100644 --- a/spec/services/boards/lists/list_service_spec.rb +++ b/spec/services/boards/lists/list_service_spec.rb @@ -6,13 +6,11 @@ describe Boards::Lists::ListService, services: true do project = create(:empty_project) board = create(:board, project: project) label = create(:label, project: project) - backlog_list = create(:backlog_list, board: board) list = create(:list, board: board, label: label) - done_list = create(:done_list, board: board) service = described_class.new(project, double) - expect(service.execute(board)).to eq [backlog_list, list, done_list] + expect(service.execute(board)).to eq [board.backlog_list, list, board.done_list] end end end -- cgit v1.2.1 From e171c1d80debd88d1aa3a3c74d1937b68a7f0f18 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 6 Oct 2016 17:36:46 -0300 Subject: Update Issue Board API to handle with has_many association --- lib/api/boards.rb | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/lib/api/boards.rb b/lib/api/boards.rb index 4d5d144a02e..9b71d335128 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -7,13 +7,14 @@ module API # Get the project board get ':id/boards' do authorize!(:read_board, user_project) - present [user_project.board], with: Entities::Board + present user_project.boards, with: Entities::Board end segment ':id/boards/:board_id' do helpers do def project_board - board = user_project.board + board = user_project.boards.first + if params[:board_id].to_i == board.id board else @@ -55,8 +56,10 @@ module API authorize!(:admin_list, user_project) - list = ::Boards::Lists::CreateService.new(user_project, current_user, - { label_id: params[:label_id] }).execute + service = ::Boards::Lists::CreateService.new(user_project, current_user, + { label_id: params[:label_id] }) + + list = service.execute(project_board) if list.valid? present list, with: Entities::List @@ -78,10 +81,10 @@ module API authorize!(:admin_list, user_project) - moved = ::Boards::Lists::MoveService.new(user_project, current_user, - { position: params[:position].to_i }).execute(list) + service = ::Boards::Lists::MoveService.new(user_project, current_user, + { position: params[:position].to_i }) - if moved + if service.execute(list) present list, with: Entities::List else render_api_error!({ error: "List could not be moved!" }, 400) @@ -97,16 +100,16 @@ module API # Example Request: # DELETE /projects/:id/boards/:board_id/lists/:list_id delete "/lists/:list_id" do - list = board_lists.find_by(id: params[:list_id]) - authorize!(:admin_list, user_project) - if list - destroyed_list = ::Boards::Lists::DestroyService.new( - user_project, current_user).execute(list) - present destroyed_list, with: Entities::List + list = board_lists.find(params[:list_id]) + + service = ::Boards::Lists::DestroyService.new(user_project, current_user) + + if service.execute(list) + present list, with: Entities::List else - not_found!('List') + render_api_error!({ error: 'List could not be deleted!' }, 400) end end end -- cgit v1.2.1 From 478b3d64f8cc7a47c5d1fc0507036590417b8aa8 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 7 Oct 2016 16:46:32 -0300 Subject: Remove Boards::BaseService --- app/services/boards/base_service.rb | 4 ---- app/services/boards/create_service.rb | 2 +- app/services/boards/issues/create_service.rb | 2 +- app/services/boards/issues/list_service.rb | 2 +- app/services/boards/issues/move_service.rb | 2 +- app/services/boards/list_service.rb | 2 +- app/services/boards/lists/create_service.rb | 2 +- app/services/boards/lists/destroy_service.rb | 2 +- app/services/boards/lists/generate_service.rb | 2 +- app/services/boards/lists/list_service.rb | 2 +- app/services/boards/lists/move_service.rb | 2 +- 11 files changed, 10 insertions(+), 14 deletions(-) delete mode 100644 app/services/boards/base_service.rb diff --git a/app/services/boards/base_service.rb b/app/services/boards/base_service.rb deleted file mode 100644 index 7eacacbaf7e..00000000000 --- a/app/services/boards/base_service.rb +++ /dev/null @@ -1,4 +0,0 @@ -module Boards - class BaseService < ::BaseService - end -end diff --git a/app/services/boards/create_service.rb b/app/services/boards/create_service.rb index a9dbd76a44a..9bdd7b6f0cf 100644 --- a/app/services/boards/create_service.rb +++ b/app/services/boards/create_service.rb @@ -1,5 +1,5 @@ module Boards - class CreateService < Boards::BaseService + class CreateService < BaseService def execute if project.boards.empty? create_board! diff --git a/app/services/boards/issues/create_service.rb b/app/services/boards/issues/create_service.rb index a181e74043b..c0d7ff5b585 100644 --- a/app/services/boards/issues/create_service.rb +++ b/app/services/boards/issues/create_service.rb @@ -1,6 +1,6 @@ module Boards module Issues - class CreateService < Boards::BaseService + class CreateService < BaseService def execute create_issue(params.merge(label_ids: [list.label_id])) end diff --git a/app/services/boards/issues/list_service.rb b/app/services/boards/issues/list_service.rb index 782dbf0db1a..fd4a462c7b2 100644 --- a/app/services/boards/issues/list_service.rb +++ b/app/services/boards/issues/list_service.rb @@ -1,6 +1,6 @@ module Boards module Issues - class ListService < Boards::BaseService + class ListService < BaseService def execute issues = IssuesFinder.new(current_user, filter_params).execute issues = without_board_labels(issues) unless list.movable? diff --git a/app/services/boards/issues/move_service.rb b/app/services/boards/issues/move_service.rb index 9a16609a8b5..96554a92a02 100644 --- a/app/services/boards/issues/move_service.rb +++ b/app/services/boards/issues/move_service.rb @@ -1,6 +1,6 @@ module Boards module Issues - class MoveService < Boards::BaseService + class MoveService < BaseService def execute(issue) return false unless can?(current_user, :update_issue, issue) return false unless valid_move? diff --git a/app/services/boards/list_service.rb b/app/services/boards/list_service.rb index cca4bdd82a2..84f1fc3a4e2 100644 --- a/app/services/boards/list_service.rb +++ b/app/services/boards/list_service.rb @@ -1,5 +1,5 @@ module Boards - class ListService < Boards::BaseService + class ListService < BaseService def execute create_board! if project.boards.empty? project.boards diff --git a/app/services/boards/lists/create_service.rb b/app/services/boards/lists/create_service.rb index da6f59c0399..abc7aeece39 100644 --- a/app/services/boards/lists/create_service.rb +++ b/app/services/boards/lists/create_service.rb @@ -1,6 +1,6 @@ module Boards module Lists - class CreateService < Boards::BaseService + class CreateService < BaseService def execute(board) List.transaction do label = project.labels.find(params[:label_id]) diff --git a/app/services/boards/lists/destroy_service.rb b/app/services/boards/lists/destroy_service.rb index d75c5fd3dc6..f986e05944c 100644 --- a/app/services/boards/lists/destroy_service.rb +++ b/app/services/boards/lists/destroy_service.rb @@ -1,6 +1,6 @@ module Boards module Lists - class DestroyService < Boards::BaseService + class DestroyService < BaseService def execute(list) return false unless list.destroyable? diff --git a/app/services/boards/lists/generate_service.rb b/app/services/boards/lists/generate_service.rb index 686e4e4b336..d8048f1c67e 100644 --- a/app/services/boards/lists/generate_service.rb +++ b/app/services/boards/lists/generate_service.rb @@ -1,6 +1,6 @@ module Boards module Lists - class GenerateService < Boards::BaseService + class GenerateService < BaseService def execute(board) return false unless board.lists.movable.empty? diff --git a/app/services/boards/lists/list_service.rb b/app/services/boards/lists/list_service.rb index ff739bc7d9c..c579ed4c869 100644 --- a/app/services/boards/lists/list_service.rb +++ b/app/services/boards/lists/list_service.rb @@ -1,6 +1,6 @@ module Boards module Lists - class ListService < Boards::BaseService + class ListService < BaseService def execute(board) board.lists end diff --git a/app/services/boards/lists/move_service.rb b/app/services/boards/lists/move_service.rb index 7d0730e8332..f2a68865f7b 100644 --- a/app/services/boards/lists/move_service.rb +++ b/app/services/boards/lists/move_service.rb @@ -1,6 +1,6 @@ module Boards module Lists - class MoveService < Boards::BaseService + class MoveService < BaseService def execute(list) @board = list.board @old_position = list.position -- cgit v1.2.1 From 7be133aa36c4160c31010dc74af002320e9070b8 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 7 Oct 2016 17:06:27 -0300 Subject: Fix typo on Boards::Issues::ListService spec --- spec/services/boards/issues/list_service_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/boards/issues/list_service_spec.rb b/spec/services/boards/issues/list_service_spec.rb index d9eb757f986..7c206cf3ce7 100644 --- a/spec/services/boards/issues/list_service_spec.rb +++ b/spec/services/boards/issues/list_service_spec.rb @@ -70,7 +70,7 @@ describe Boards::Issues::ListService, services: true do end end - context 'with list that does not belongs to the board' do + context 'with list that does not belong to the board' do it 'raises an error' do list = create(:list) service = described_class.new(project, user, board_id: board.id, id: list.id) -- cgit v1.2.1 From c5ea58b63007b404a725f28eaae33f5f11d8d925 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Mon, 10 Oct 2016 14:36:09 +0100 Subject: Update endpoint path for the frontend --- app/views/projects/boards/index.html.haml | 19 +++++++++++++++++++ app/views/projects/boards/show.html.haml | 2 +- 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml index e69de29bb2d..7a1f0ee4428 100644 --- a/app/views/projects/boards/index.html.haml +++ b/app/views/projects/boards/index.html.haml @@ -0,0 +1,19 @@ +- @no_container = true +- @content_class = "issue-boards-content" +- page_title "Boards" + +- content_for :page_specific_javascripts do + = page_specific_javascript_tag('boards/boards_bundle.js') + = page_specific_javascript_tag('boards/test_utils/simulate_drag.js') if Rails.env.test? + += render "projects/issues/head" + += render 'shared/issuable/filter', type: :boards + +.boards-list#board-app{ "v-cloak" => true, + "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project, @boards.first)}", + "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", + "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } + .boards-app-loading.text-center{ "v-if" => "loading" } + = icon("spinner spin") + = render "projects/boards/components/board" diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index 5b441b48caf..7bc476264b1 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -11,7 +11,7 @@ = render 'shared/issuable/filter', type: :boards .boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", + "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project, @board)}", "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } .boards-app-loading.text-center{ "v-if" => "loading" } -- cgit v1.2.1 From 2ad531f5e279bcd278600d1f95ff9d4e4988b034 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 10 Oct 2016 11:22:30 -0300 Subject: Add Project::BoardLimitExcedeed error class --- app/models/project.rb | 4 +++- spec/models/project_spec.rb | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index 30db7ed50b3..758927edd5c 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -16,6 +16,8 @@ class Project < ActiveRecord::Base extend Gitlab::ConfigHelper + class BoardLimitExceeded < StandardError; end + NUMBER_OF_PERMITTED_BOARDS = 1 UNKNOWN_IMPORT_URL = 'http://unknown.git' @@ -1341,6 +1343,6 @@ class Project < ActiveRecord::Base end def validate_board_limit(board) - raise StandardError, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS + raise BoardLimitExceeded, 'Number of permitted boards exceeded' if boards.size >= NUMBER_OF_PERMITTED_BOARDS end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1b13f1be477..308a00db9cd 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -99,7 +99,7 @@ describe Project, models: true do it 'raises an error when attempting to add more than one board to the project' do subject.boards.build - expect { subject.boards.build }.to raise_error(StandardError, 'Number of permitted boards exceeded') + expect { subject.boards.build }.to raise_error(Project::BoardLimitExceeded, 'Number of permitted boards exceeded') expect(subject.boards.size).to eq 1 end end -- cgit v1.2.1 From 4e9783e4ddffdb550c362e7bc93f31c904d638e3 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Tue, 11 Oct 2016 09:42:34 -0500 Subject: Allow scrolling within grouped pipelines --- app/assets/stylesheets/pages/pipelines.scss | 6 +++++- app/views/projects/commit/_pipeline_status_group.html.haml | 11 ++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 28e850767b7..7843355f0ab 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -420,7 +420,11 @@ left: auto; right: -197px; top: -9px; - max-height: 245px; + + ul { + max-height: 245px; + overflow: auto; + } a { color: $gl-text-color; diff --git a/app/views/projects/commit/_pipeline_status_group.html.haml b/app/views/projects/commit/_pipeline_status_group.html.haml index 6ada719e006..5d0d5ba0262 100644 --- a/app/views/projects/commit/_pipeline_status_group.html.haml +++ b/app/views/projects/commit/_pipeline_status_group.html.haml @@ -5,8 +5,9 @@ %span.ci-status-text = name %span.badge= subject.size -%ul.dropdown-menu.grouped-pipeline-dropdown - %li.arrow - - subject.each do |status| - %li - = render "projects/#{status.to_partial_path}_pipeline", subject: status +.dropdown-menu.grouped-pipeline-dropdown + .arrow + %ul + - subject.each do |status| + %li + = render "projects/#{status.to_partial_path}_pipeline", subject: status -- cgit v1.2.1 From e2c97567c1a80d1d4857efabead9021897406913 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 16:47:02 +0200 Subject: Move some CHANGELOG entries to the 8.13.0 part MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Spotted while reviewing https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/789/diffs#diff-1. Signed-off-by: Rémy Coutable --- CHANGELOG | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ffbaefdb5f1..693f5179297 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,10 @@ v 8.13.0 (unreleased) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup - Add an example for testing a phoenix application with Gitlab CI in the docs (Manthan Mallikarjun) + - Updating verbiage on git basics to be more intuitive + - Clarify documentation for Runners API (Gennady Trafimenkov) + - Change user & group landing page routing from /u/:username to /:username + - Prevent running GfmAutocomplete setup for each diff note !6569 - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) @@ -110,7 +114,6 @@ v 8.12.4 - Fix failed project deletion when feature visibility set to private. !6688 - Prevent claiming associated model IDs via import. - Set GitLab project exported file permissions to owner only - - Change user & group landing page routing from /u/:username to /:username v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves @@ -130,7 +133,6 @@ v 8.12.2 - Fix bug where 'Search results' repeated many times when a search in the emoji search form is cleared (Xavier Bick) (@zeiv) - Fix resolve discussion buttons endpoint path - Refactor remnants of CoffeeScript destructured opts and super !6261 - - Prevent running GfmAutocomplete setup for each diff note !6569 v 8.12.1 - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST @@ -334,7 +336,6 @@ v 8.11.7 - Avoid conflict with admin labels when importing GitHub labels. !6158 - Restores `fieldName` to allow only string values in `gl_dropdown.js`. !6234 - Allow the Rails cookie to be used for API authentication. - - Updating verbiage on git basics to be more intuitive v 8.11.6 - Fix unnecessary horizontal scroll area in pipeline visualizations. !6005 @@ -495,7 +496,6 @@ v 8.11.0 - Add pipeline events hook - Bump gitlab_git to speedup DiffCollection iterations - Rewrite description of a blocked user in admin settings. (Elias Werberich) - - Clarify documentation for Runners API (Gennady Trafimenkov) - 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) -- cgit v1.2.1 From 53f50edf4dac5133ff4d67a67ba5071e3eb7e78a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Mon, 10 Oct 2016 23:29:20 -0300 Subject: Fix board relates specs --- app/controllers/projects/boards_controller.rb | 2 +- spec/features/boards/boards_spec.rb | 16 ++++++++-------- spec/features/boards/keyboard_shortcut_spec.rb | 4 +--- spec/features/boards/new_issue_spec.rb | 4 ++-- spec/fixtures/api/schemas/board.json | 3 +-- 5 files changed, 13 insertions(+), 16 deletions(-) diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb index bbb1b1bf1e1..808affa4f98 100644 --- a/app/controllers/projects/boards_controller.rb +++ b/app/controllers/projects/boards_controller.rb @@ -32,6 +32,6 @@ class Projects::BoardsController < Projects::ApplicationController end def serialize_as_json(resource) - resource.as_json(only: [:id, :name]) + resource.as_json(only: [:id]) end end diff --git a/spec/features/boards/boards_spec.rb b/spec/features/boards/boards_spec.rb index f79b6a77696..0fb1608a0a3 100644 --- a/spec/features/boards/boards_spec.rb +++ b/spec/features/boards/boards_spec.rb @@ -18,7 +18,7 @@ describe 'Issue Boards', feature: true, js: true do context 'no lists' do before do - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource expect(page).to have_selector('.board', count: 3) end @@ -61,8 +61,8 @@ describe 'Issue Boards', feature: true, js: true do let!(:done) { create(:label, project: project, name: 'Done') } let!(:accepting) { create(:label, project: project, name: 'Accepting Merge Requests') } - let!(:list1) { create(:list, board: project.board, label: planning, position: 0) } - let!(:list2) { create(:list, board: project.board, label: development, position: 1) } + let!(:list1) { create(:list, board: board, label: planning, position: 0) } + let!(:list2) { create(:list, board: board, label: development, position: 1) } let!(:confidential_issue) { create(:issue, :confidential, project: project, author: user) } let!(:issue1) { create(:issue, project: project, assignee: user) } @@ -76,7 +76,7 @@ describe 'Issue Boards', feature: true, js: true do let!(:issue9) { create(:labeled_issue, project: project, labels: [testing, bug, accepting]) } before do - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource @@ -170,7 +170,7 @@ describe 'Issue Boards', feature: true, js: true do create(:issue, project: project) end - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource page.within(find('.board', match: :first)) do @@ -604,7 +604,7 @@ describe 'Issue Boards', feature: true, js: true do context 'keyboard shortcuts' do before do - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource end @@ -617,7 +617,7 @@ describe 'Issue Boards', feature: true, js: true do context 'signed out user' do before do logout - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource end @@ -633,7 +633,7 @@ describe 'Issue Boards', feature: true, js: true do project.team << [user_guest, :guest] logout login_as(user_guest) - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource end diff --git a/spec/features/boards/keyboard_shortcut_spec.rb b/spec/features/boards/keyboard_shortcut_spec.rb index 7ef68e9eb8d..a5fc766401f 100644 --- a/spec/features/boards/keyboard_shortcut_spec.rb +++ b/spec/features/boards/keyboard_shortcut_spec.rb @@ -6,9 +6,7 @@ describe 'Issue Boards shortcut', feature: true, js: true do let(:project) { create(:empty_project) } before do - project.create_board - project.board.lists.create(list_type: :backlog) - project.board.lists.create(list_type: :done) + create(:board, project: project) login_as :admin diff --git a/spec/features/boards/new_issue_spec.rb b/spec/features/boards/new_issue_spec.rb index 1ceb60d5297..67d6da5f39a 100644 --- a/spec/features/boards/new_issue_spec.rb +++ b/spec/features/boards/new_issue_spec.rb @@ -14,7 +14,7 @@ describe 'Issue Boards new issue', feature: true, js: true do login_as(user) - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource expect(page).to have_selector('.board', count: 3) @@ -70,7 +70,7 @@ describe 'Issue Boards new issue', feature: true, js: true do context 'unauthorized user' do before do - visit namespace_project_board_path(project.namespace, project) + visit namespace_project_board_path(project.namespace, project, board) wait_for_vue_resource end diff --git a/spec/fixtures/api/schemas/board.json b/spec/fixtures/api/schemas/board.json index 6c6e2bee8cb..03aca4a3cc0 100644 --- a/spec/fixtures/api/schemas/board.json +++ b/spec/fixtures/api/schemas/board.json @@ -1,8 +1,7 @@ { "type": "object", "required" : [ - "id", - "name" + "id" ], "properties" : { "id": { "type": "integer" }, -- cgit v1.2.1 From dc49ee6247e041020e96c5d5be292e3f92926c4d Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 11 Oct 2016 09:29:47 +0100 Subject: Updated to pass the board ID with the boards root to save conflicts with EE --- app/assets/javascripts/boards/boards_bundle.js.es6 | 3 ++- app/assets/javascripts/boards/services/board_service.js.es6 | 10 +++++----- app/views/projects/boards/index.html.haml | 3 ++- app/views/projects/boards/show.html.haml | 3 ++- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/boards/boards_bundle.js.es6 b/app/assets/javascripts/boards/boards_bundle.js.es6 index 91c12570e09..d4f8f4b9420 100644 --- a/app/assets/javascripts/boards/boards_bundle.js.es6 +++ b/app/assets/javascripts/boards/boards_bundle.js.es6 @@ -28,12 +28,13 @@ $(() => { state: Store.state, loading: true, endpoint: $boardApp.dataset.endpoint, + boardId: $boardApp.dataset.boardId, disabled: $boardApp.dataset.disabled === 'true', issueLinkBase: $boardApp.dataset.issueLinkBase }, init: Store.create.bind(Store), created () { - gl.boardService = new BoardService(this.endpoint); + gl.boardService = new BoardService(this.endpoint, this.boardId); }, ready () { Store.disabled = this.disabled; diff --git a/app/assets/javascripts/boards/services/board_service.js.es6 b/app/assets/javascripts/boards/services/board_service.js.es6 index 2b825c3949f..b9c91cbf31e 100644 --- a/app/assets/javascripts/boards/services/board_service.js.es6 +++ b/app/assets/javascripts/boards/services/board_service.js.es6 @@ -1,15 +1,15 @@ class BoardService { - constructor (root) { + constructor (root, boardId) { Vue.http.options.root = root; - this.lists = Vue.resource(`${root}/lists{/id}`, {}, { + this.lists = Vue.resource(`${root}/${boardId}/lists{/id}`, {}, { generate: { method: 'POST', - url: `${root}/lists/generate.json` + url: `${root}/${boardId}/lists/generate.json` } }); - this.issue = Vue.resource(`${root}/issues{/id}`, {}); - this.issues = Vue.resource(`${root}/lists{/id}/issues`, {}); + this.issue = Vue.resource(`${root}/${boardId}/issues{/id}`, {}); + this.issues = Vue.resource(`${root}/${boardId}/lists{/id}/issues`, {}); Vue.http.interceptors.push((request, next) => { request.headers['X-CSRF-Token'] = $.rails.csrfToken(); diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml index 7a1f0ee4428..9252bbc08c1 100644 --- a/app/views/projects/boards/index.html.haml +++ b/app/views/projects/boards/index.html.haml @@ -11,7 +11,8 @@ = render 'shared/issuable/filter', type: :boards .boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project, @boards.first)}", + "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", + "data-board-id" => "#{@boards.first.id}", "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } .boards-app-loading.text-center{ "v-if" => "loading" } diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index 7bc476264b1..d6e1ac5f3cf 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -11,7 +11,8 @@ = render 'shared/issuable/filter', type: :boards .boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_board_path(@project.namespace, @project, @board)}", + "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", + "data-board-id" => "#{@board.id}", "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } .boards-app-loading.text-center{ "v-if" => "loading" } -- cgit v1.2.1 From 53b3c62e0f889d077ee5b19703c3e04384c85c03 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 11 Oct 2016 16:07:53 +0100 Subject: Fixed JS tests --- spec/javascripts/boards/boards_store_spec.js.es6 | 2 +- spec/javascripts/boards/issue_spec.js.es6 | 2 +- spec/javascripts/boards/list_spec.js.es6 | 2 +- spec/javascripts/boards/mock_data.js.es6 | 8 ++++---- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/javascripts/boards/boards_store_spec.js.es6 b/spec/javascripts/boards/boards_store_spec.js.es6 index 078e4b00023..15c305ce321 100644 --- a/spec/javascripts/boards/boards_store_spec.js.es6 +++ b/spec/javascripts/boards/boards_store_spec.js.es6 @@ -14,7 +14,7 @@ (() => { beforeEach(() => { - gl.boardService = new BoardService('/test/issue-boards/board'); + gl.boardService = new BoardService('/test/issue-boards/board', '1'); gl.issueBoards.BoardsStore.create(); $.cookie('issue_board_welcome_hidden', 'false'); diff --git a/spec/javascripts/boards/issue_spec.js.es6 b/spec/javascripts/boards/issue_spec.js.es6 index 3569d1b98bd..328c6f82ab5 100644 --- a/spec/javascripts/boards/issue_spec.js.es6 +++ b/spec/javascripts/boards/issue_spec.js.es6 @@ -16,7 +16,7 @@ describe('Issue model', () => { let issue; beforeEach(() => { - gl.boardService = new BoardService('/test/issue-boards/board'); + gl.boardService = new BoardService('/test/issue-boards/board', '1'); gl.issueBoards.BoardsStore.create(); issue = new ListIssue({ diff --git a/spec/javascripts/boards/list_spec.js.es6 b/spec/javascripts/boards/list_spec.js.es6 index 1688b996162..ec78d82e919 100644 --- a/spec/javascripts/boards/list_spec.js.es6 +++ b/spec/javascripts/boards/list_spec.js.es6 @@ -16,7 +16,7 @@ describe('List model', () => { let list; beforeEach(() => { - gl.boardService = new BoardService('/test/issue-boards/board'); + gl.boardService = new BoardService('/test/issue-boards/board', '1'); gl.issueBoards.BoardsStore.create(); list = new List(listObj); diff --git a/spec/javascripts/boards/mock_data.js.es6 b/spec/javascripts/boards/mock_data.js.es6 index f3797ed44d4..052455f2ca6 100644 --- a/spec/javascripts/boards/mock_data.js.es6 +++ b/spec/javascripts/boards/mock_data.js.es6 @@ -26,7 +26,7 @@ const listObjDuplicate = { const BoardsMockData = { 'GET': { - '/test/issue-boards/board/lists{/id}/issues': { + '/test/issue-boards/board/1/lists{/id}/issues': { issues: [{ title: 'Testing', iid: 1, @@ -37,13 +37,13 @@ const BoardsMockData = { } }, 'POST': { - '/test/issue-boards/board/lists{/id}': listObj + '/test/issue-boards/board/1/lists{/id}': listObj }, 'PUT': { - '/test/issue-boards/board/lists{/id}': {} + '/test/issue-boards/board/1/lists{/id}': {} }, 'DELETE': { - '/test/issue-boards/board/lists{/id}': {} + '/test/issue-boards/board/1/lists{/id}': {} } }; -- cgit v1.2.1 From 66af08df281214f59bb21e911949625024b41829 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Tue, 11 Oct 2016 16:10:12 +0100 Subject: Fixed default branch dropdown not converting to select2 Closes #23200 --- app/assets/javascripts/project_new.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/assets/javascripts/project_new.js b/app/assets/javascripts/project_new.js index a787b11f2a9..3cf41505814 100644 --- a/app/assets/javascripts/project_new.js +++ b/app/assets/javascripts/project_new.js @@ -4,7 +4,9 @@ this.ProjectNew = (function() { function ProjectNew() { this.toggleSettings = bind(this.toggleSettings, this); - this.$selects = $('.features select'); + this.$selects = $('.features select').filter(function () { + return $(this).data('field'); + }); $('.project-edit-container').on('ajax:before', (function(_this) { return function() { -- cgit v1.2.1 From 8e70cf25641fc023e146ac1632f89203a55ce6f9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 17:25:57 +0200 Subject: Addresses Robert's feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/api/projects.md | 2 +- doc/api/users.md | 2 +- spec/requests/api/users_spec.rb | 5 +++-- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/doc/api/projects.md b/doc/api/projects.md index 27436a052da..f96bf7f6d63 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -436,7 +436,7 @@ Parameters: ### Get project events Get the events for the specified project. -Sorted from newest to latest +Sorted from newest to oldest ``` GET /projects/:id/events diff --git a/doc/api/users.md b/doc/api/users.md index 15202010b7b..a52b2d51d78 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -630,7 +630,7 @@ Will return `200 OK` on success, `404 User Not Found` is user cannot be found or ### Get user contribution events -Get the contribution events for the specified user, sorted from newest to latest. +Get the contribution events for the specified user, sorted from newest to oldest. ``` GET /users/:id/events diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 56b2ec6271e..91e54af1209 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -898,7 +898,6 @@ describe API::API, api: true do describe 'GET /user/:id/events' do let(:user) { create(:user) } - let(:lambda_user) { create(:user) } let(:project) { create(:empty_project) } let(:note) { create(:note_on_issue, note: 'What an awesome day!', project: project) } @@ -909,7 +908,9 @@ describe API::API, api: true do context "as a user than cannot see the event's project" do it 'returns no events' do - get api("/users/#{user.id}/events", lambda_user) + other_user = create(:user) + + get api("/users/#{user.id}/events", other_user) expect(response).to have_http_status(200) expect(json_response).to be_empty -- cgit v1.2.1 From c90483406ecc2717076391737ff57167b5a03393 Mon Sep 17 00:00:00 2001 From: tiagonbotelho Date: Mon, 3 Oct 2016 13:11:16 +0100 Subject: refactors tests because of gitlab-test repository changes --- CHANGELOG | 1 - app/models/repository.rb | 9 +++------ spec/controllers/projects/commit_controller_spec.rb | 16 ++++++++++------ spec/helpers/search_helper_spec.rb | 2 +- spec/lib/gitlab/data_builder/push_spec.rb | 8 ++++---- spec/mailers/notify_spec.rb | 2 +- spec/models/commit_spec.rb | 8 ++++---- spec/models/merge_request_diff_spec.rb | 6 +++--- spec/models/merge_request_spec.rb | 4 ++-- spec/models/repository_spec.rb | 15 ++++++++++++++- spec/requests/api/commits_spec.rb | 12 +++++++++--- spec/requests/api/repositories_spec.rb | 8 ++++---- spec/requests/git_http_spec.rb | 4 ++-- spec/services/merge_requests/refresh_service_spec.rb | 4 ++-- spec/services/system_note_service_spec.rb | 8 ++++---- spec/workers/emails_on_push_worker_spec.rb | 4 ++-- 16 files changed, 65 insertions(+), 46 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 841861293c8..0116d3ac0a9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -119,7 +119,6 @@ v 8.12.1 - Fix issue with search filter labels not displaying v 8.12.0 -v 8.12.0 (unreleased) - Removes inconsistency regarding tagging immediatelly as merged once you create a new branch. !6408 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 - Only check :can_resolve permission if the note is resolvable diff --git a/app/models/repository.rb b/app/models/repository.rb index 1bf6e58b9db..cf269061ebe 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1013,17 +1013,14 @@ class Repository branch_commit = commit(branch_name) root_ref_commit = commit(root_ref) - if branch_commit && !same_head?(branch_commit.id, root_ref_commit.id) - is_ancestor?(branch_commit.id, root_ref_commit.id) + if branch_commit + same_head = branch_commit.id == root_ref_commit.id + !same_head && is_ancestor?(branch_commit.id, root_ref_commit.id) else nil end end - def same_head?(first_commit_id, second_commit_id) - first_commit_id == second_commit_id - end - def merge_base(first_commit_id, second_commit_id) first_commit_id = commit(first_commit_id).try(:id) || first_commit_id second_commit_id = commit(second_commit_id).try(:id) || second_commit_id diff --git a/spec/controllers/projects/commit_controller_spec.rb b/spec/controllers/projects/commit_controller_spec.rb index 7e440193d7b..646b097d74e 100644 --- a/spec/controllers/projects/commit_controller_spec.rb +++ b/spec/controllers/projects/commit_controller_spec.rb @@ -102,15 +102,16 @@ describe Projects::CommitController do describe "as patch" do include_examples "export as", :patch let(:format) { :patch } + let(:commit2) { project.commit('498214de67004b1da3d820901307bed2a68a8ef6') } it "is a git email patch" do - go(id: commit.id, format: format) + go(id: commit2.id, format: format) - expect(response.body).to start_with("From #{commit.id}") + expect(response.body).to start_with("From #{commit2.id}") end it "contains a git diff" do - go(id: commit.id, format: format) + go(id: commit2.id, format: format) expect(response.body).to match(/^diff --git/) end @@ -135,6 +136,8 @@ describe Projects::CommitController do describe "GET branches" do it "contains branch and tags information" do + commit = project.commit('5937ac0a7beb003549fc5fd26fc247adbce4a52e') + get(:branches, namespace_id: project.namespace.to_param, project_id: project.to_param, @@ -254,16 +257,17 @@ describe Projects::CommitController do end let(:existing_path) { '.gitmodules' } + let(:commit2) { project.commit('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } context 'when the commit exists' do context 'when the user has access to the project' do context 'when the path exists in the diff' do it 'enables diff notes' do - diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path) + diff_for_path(id: commit2.id, old_path: existing_path, new_path: existing_path) expect(assigns(:diff_notes_disabled)).to be_falsey expect(assigns(:comments_target)).to eq(noteable_type: 'Commit', - commit_id: commit.id) + commit_id: commit2.id) end it 'only renders the diffs for the path given' do @@ -272,7 +276,7 @@ describe Projects::CommitController do meth.call(diffs) end - diff_for_path(id: commit.id, old_path: existing_path, new_path: existing_path) + diff_for_path(id: commit2.id, old_path: existing_path, new_path: existing_path) end end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index c5b5aa8c445..64aa41020c9 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -19,7 +19,7 @@ describe SearchHelper do expect(subject.filename).to eq('CHANGELOG') expect(subject.basename).to eq('CHANGELOG') expect(subject.ref).to eq('master') - expect(subject.startline).to eq(186) + expect(subject.startline).to eq(188) expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") end diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb index b73434e8dd7..a379f798a16 100644 --- a/spec/lib/gitlab/data_builder/push_spec.rb +++ b/spec/lib/gitlab/data_builder/push_spec.rb @@ -8,13 +8,13 @@ describe Gitlab::DataBuilder::Push, lib: true do let(:data) { described_class.build_sample(project, user) } it { expect(data).to be_a(Hash) } - it { expect(data[:before]).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } - it { expect(data[:after]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } + it { expect(data[:before]).to eq('1b12f15a11fc6e62177bef08f47bc7b5ce50b141') } + it { expect(data[:after]).to eq('b83d6e391c22777fca1ed3012fce84f633d7fed0') } it { expect(data[:ref]).to eq('refs/heads/master') } it { expect(data[:commits].size).to eq(3) } it { expect(data[:total_commits_count]).to eq(3) } - it { expect(data[:commits].first[:added]).to eq(['gitlab-grack']) } - it { expect(data[:commits].first[:modified]).to eq(['.gitmodules']) } + it { expect(data[:commits].first[:added]).to eq(['bar/branch-test.txt']) } + it { expect(data[:commits].first[:modified]).to eq([]) } it { expect(data[:commits].first[:removed]).to eq([]) } include_examples 'project hook data with deprecateds' diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 0e4130e8a3a..c8207e58e90 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -628,7 +628,7 @@ describe Notify do it_behaves_like 'a user cannot unsubscribe through footer link' it 'has the correct subject' do - is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/ + is_expected.to have_subject /Re: #{project.name} | #{commit.title} \(#{commit.short_id}\)/ end it 'contains a link to the commit' do diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index d3e6a6648cc..51be3f36135 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -164,10 +164,10 @@ eos let(:data) { commit.hook_attrs(with_changed_files: true) } it { expect(data).to be_a(Hash) } - it { expect(data[:message]).to include('Add submodule from gitlab.com') } - it { expect(data[:timestamp]).to eq('2014-02-27T11:01:38+02:00') } - it { expect(data[:added]).to eq(["gitlab-grack"]) } - it { expect(data[:modified]).to eq([".gitmodules"]) } + it { expect(data[:message]).to include('adds bar folder and branch-test text file to check Repository merged_to_root_ref method') } + it { expect(data[:timestamp]).to eq('2016-09-27T14:37:46+00:00') } + it { expect(data[:added]).to eq(["bar/branch-test.txt"]) } + it { expect(data[:modified]).to eq([]) } it { expect(data[:removed]).to eq([]) } end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 530a7def553..a27c2b28326 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -6,9 +6,9 @@ describe MergeRequestDiff, models: true do it { expect(subject).to be_valid } it { expect(subject).to be_persisted } - it { expect(subject.commits.count).to eq(5) } - it { expect(subject.diffs.count).to eq(8) } - it { expect(subject.head_commit_sha).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } + it { expect(subject.commits.count).to eq(29) } + it { expect(subject.diffs.count).to eq(20) } + it { expect(subject.head_commit_sha).to eq('b83d6e391c22777fca1ed3012fce84f633d7fed0') } it { expect(subject.base_commit_sha).to eq('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') } it { expect(subject.start_commit_sha).to eq('0b4bc9a49b562e85de7cc9e834518ea6828729b9') } end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 38b6da50168..5884b4cff8c 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -489,7 +489,7 @@ describe MergeRequest, models: true do subject(:merge_request_with_divergence) { create(:merge_request, :diverged, source_project: project, target_project: project) } it 'counts commits that are on target branch but not on source branch' do - expect(subject.diverged_commits_count).to eq(5) + expect(subject.diverged_commits_count).to eq(29) end end @@ -497,7 +497,7 @@ describe MergeRequest, models: true do subject(:merge_request_fork_with_divergence) { create(:merge_request, :diverged, source_project: fork_project, target_project: project) } it 'counts commits that are on target branch but not on source branch' do - expect(subject.diverged_commits_count).to eq(5) + expect(subject.diverged_commits_count).to eq(29) end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 6dd3f91be17..4df78713a82 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -114,17 +114,30 @@ describe Repository, models: true do end describe '#merged_to_root_ref?' do - context 'merged branch' do + context 'merged branch without ff' do subject { repository.merged_to_root_ref?('branch-merged') } it { is_expected.to be_truthy } end + # If the HEAD was ff then it will be false + context 'merged with ff' do + subject { repository.merged_to_root_ref?('improve/awesome') } + + it { is_expected.to be_truthy } + end + context 'not merged branch' do subject { repository.merged_to_root_ref?('not-merged-branch') } it { is_expected.to be_falsey } end + + context 'default branch' do + subject { repository.merged_to_root_ref?('master') } + + it { is_expected.to be_falsey } + end end describe '#can_be_merged?' do diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index aa610557056..66fa0c0c01f 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -53,7 +53,12 @@ describe API::API, api: true do get api("/projects/#{project.id}/repository/commits?until=#{before.utc.iso8601}", user) - expect(json_response.size).to eq(commits.size - 1) + if commits.size >= 20 + expect(json_response.size).to eq(20) + else + expect(json_response.size).to eq(commits.size - 1) + end + expect(json_response.first["id"]).to eq(commits.second.id) expect(json_response.second["id"]).to eq(commits.third.id) end @@ -447,11 +452,12 @@ describe API::API, api: true do end it 'returns the inline comment' do - 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' + 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: 1, 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.raw_diffs.first.new_path) - expect(json_response['line']).to eq(7) + expect(json_response['line']).to eq(1) expect(json_response['line_type']).to eq('new') end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index 80a856a6e90..c4dc2d9006a 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -21,7 +21,7 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response).to be_an Array - expect(json_response.first['name']).to eq('encoding') + expect(json_response.first['name']).to eq('bar') expect(json_response.first['type']).to eq('tree') expect(json_response.first['mode']).to eq('040000') end @@ -166,9 +166,9 @@ describe API::API, api: true do expect(response).to have_http_status(200) expect(json_response).to be_an Array contributor = json_response.first - expect(contributor['email']).to eq('dmitriy.zaporozhets@gmail.com') - expect(contributor['name']).to eq('Dmitriy Zaporozhets') - expect(contributor['commits']).to eq(13) + expect(contributor['email']).to eq('tiagonbotelho@hotmail.com') + expect(contributor['name']).to eq('tiagonbotelho') + expect(contributor['commits']).to eq(1) expect(contributor['additions']).to eq(0) expect(contributor['deletions']).to eq(0) end diff --git a/spec/requests/git_http_spec.rb b/spec/requests/git_http_spec.rb index c0c1e62e910..27f0fd22ae6 100644 --- a/spec/requests/git_http_spec.rb +++ b/spec/requests/git_http_spec.rb @@ -440,8 +440,8 @@ describe 'Git HTTP requests', lib: true do before do # Provide a dummy file in its place allow_any_instance_of(Repository).to receive(:blob_at).and_call_original - allow_any_instance_of(Repository).to receive(:blob_at).with('5937ac0a7beb003549fc5fd26fc247adbce4a52e', 'info/refs') do - Gitlab::Git::Blob.find(project.repository, 'master', '.gitignore') + allow_any_instance_of(Repository).to receive(:blob_at).with('b83d6e391c22777fca1ed3012fce84f633d7fed0', 'info/refs') do + Gitlab::Git::Blob.find(project.repository, 'master', 'bar/branch-test.txt') end get "/#{project.path_with_namespace}/blob/master/info/refs" diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 59d3912018a..5b4e4908add 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -118,7 +118,7 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request.notes).to be_empty } it { expect(@merge_request).to be_open } - it { expect(@fork_merge_request.notes.last.note).to include('Added 4 commits') } + it { expect(@fork_merge_request.notes.last.note).to include('Added 28 commits') } it { expect(@fork_merge_request).to be_open } it { expect(@build_failed_todo).to be_pending } it { expect(@fork_build_failed_todo).to be_pending } @@ -169,7 +169,7 @@ describe MergeRequests::RefreshService, services: true do notes = @fork_merge_request.notes.reorder(:created_at).map(&:note) expect(notes[0]).to include('Restored source branch `master`') - expect(notes[1]).to include('Added 4 commits') + expect(notes[1]).to include('Added 28 commits') expect(@fork_merge_request).to be_open end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 304d4e62396..b4ba28dfe8e 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -54,7 +54,7 @@ describe SystemNoteService, services: true do it 'adds a message line for each commit' do new_commits.each_with_index do |commit, i| # Skip the header - expect(note_lines[i + 1]).to eq "* #{commit.short_id} - #{commit.title}" + expect(HTMLEntities.new.decode(note_lines[i + 1])).to eq "* #{commit.short_id} - #{commit.title}" end end end @@ -81,7 +81,7 @@ describe SystemNoteService, services: true do end it 'includes a commit count' do - expect(summary_line).to end_with " - 2 commits from branch `feature`" + expect(summary_line).to end_with " - 26 commits from branch `feature`" end end @@ -91,7 +91,7 @@ describe SystemNoteService, services: true do end it 'includes a commit count' do - expect(summary_line).to end_with " - 2 commits from branch `feature`" + expect(summary_line).to end_with " - 26 commits from branch `feature`" end end @@ -537,7 +537,7 @@ describe SystemNoteService, services: true do let(:mergereq) { create(:merge_request, :simple, target_project: project, source_project: project) } let(:jira_issue) { ExternalIssue.new("JIRA-1", project)} let(:jira_tracker) { project.jira_service } - let(:commit) { project.commit } + let(:commit) { project.repository.commits('master').find { |commit| commit.id == '5937ac0a7beb003549fc5fd26fc247adbce4a52e' } } context 'in JIRA issue tracker' do before do diff --git a/spec/workers/emails_on_push_worker_spec.rb b/spec/workers/emails_on_push_worker_spec.rb index 7ca2c29da1c..036d037f3f9 100644 --- a/spec/workers/emails_on_push_worker_spec.rb +++ b/spec/workers/emails_on_push_worker_spec.rb @@ -57,7 +57,7 @@ describe EmailsOnPushWorker do end it "sends a mail with the correct subject" do - expect(email.subject).to include('Change some files') + expect(email.subject).to include('adds bar folder and branch-test text file') end it "mentions force pushing in the body" do @@ -73,7 +73,7 @@ describe EmailsOnPushWorker do before { perform } it "sends a mail with the correct subject" do - expect(email.subject).to include('Change some files') + expect(email.subject).to include('adds bar folder and branch-test text file') end it "does not mention force pushing in the body" do -- cgit v1.2.1 From 8213fc6a744cb5c9fa4c7461dbf1a96f48898662 Mon Sep 17 00:00:00 2001 From: barthc Date: Mon, 10 Oct 2016 16:04:52 +0100 Subject: allow multiple labels commands --- CHANGELOG | 1 + app/services/slash_commands/interpret_service.rb | 21 +++++++++-- .../slash_commands/interpret_service_spec.rb | 43 ++++++++++++++++++++++ 3 files changed, 62 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ba6c3f7c558..850cb16dff1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -91,6 +91,7 @@ v 8.13.0 (unreleased) - Close todos when accepting merge requests via the API !6486 (tonygambone) - Changed Slack service user referencing from full name to username (Sebastian Poxhofer) - Retouch environments list and deployments list + - Add multiple command support for all label related slash commands !6780 (barthc) - Add Container Registry on/off status to Admin Area !6638 (the-undefined) - Allow empty merge requests !6384 (Artem Sidorenko) - Grouped pipeline dropdown is a scrollable container diff --git a/app/services/slash_commands/interpret_service.rb b/app/services/slash_commands/interpret_service.rb index 1725a30fae5..e4ae3dec8aa 100644 --- a/app/services/slash_commands/interpret_service.rb +++ b/app/services/slash_commands/interpret_service.rb @@ -122,7 +122,12 @@ module SlashCommands command :label do |labels_param| label_ids = find_label_ids(labels_param) - @updates[:add_label_ids] = label_ids unless label_ids.empty? + if label_ids.any? + @updates[:add_label_ids] ||= [] + @updates[:add_label_ids] += label_ids + + @updates[:add_label_ids].uniq! + end end desc 'Remove all or specific label(s)' @@ -136,7 +141,12 @@ module SlashCommands if labels_param.present? label_ids = find_label_ids(labels_param) - @updates[:remove_label_ids] = label_ids unless label_ids.empty? + if label_ids.any? + @updates[:remove_label_ids] ||= [] + @updates[:remove_label_ids] += label_ids + + @updates[:remove_label_ids].uniq! + end else @updates[:label_ids] = [] end @@ -152,7 +162,12 @@ module SlashCommands command :relabel do |labels_param| label_ids = find_label_ids(labels_param) - @updates[:label_ids] = label_ids unless label_ids.empty? + if label_ids.any? + @updates[:label_ids] ||= [] + @updates[:label_ids] += label_ids + + @updates[:label_ids].uniq! + end end desc 'Add a todo' diff --git a/spec/services/slash_commands/interpret_service_spec.rb b/spec/services/slash_commands/interpret_service_spec.rb index ae4d286d250..b57e338b782 100644 --- a/spec/services/slash_commands/interpret_service_spec.rb +++ b/spec/services/slash_commands/interpret_service_spec.rb @@ -86,6 +86,25 @@ describe SlashCommands::InterpretService, services: true do end end + shared_examples 'multiple label command' do + it 'fetches label ids and populates add_label_ids if content contains multiple /label' do + bug # populate the label + inprogress # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(add_label_ids: [inprogress.id, bug.id]) + end + end + + shared_examples 'multiple label with same argument' do + it 'prevents duplicate label ids and populates add_label_ids if content contains multiple /label' do + inprogress # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(add_label_ids: [inprogress.id]) + end + end + shared_examples 'unlabel command' do it 'fetches label ids and populates remove_label_ids if content contains /unlabel' do issuable.update(label_ids: [inprogress.id]) # populate the label @@ -95,6 +114,15 @@ describe SlashCommands::InterpretService, services: true do end end + shared_examples 'multiple unlabel command' do + it 'fetches label ids and populates remove_label_ids if content contains mutiple /unlabel' do + issuable.update(label_ids: [inprogress.id, bug.id]) # populate the label + _, updates = service.execute(content, issuable) + + expect(updates).to eq(remove_label_ids: [inprogress.id, bug.id]) + end + end + shared_examples 'unlabel command with no argument' do it 'populates label_ids: [] if content contains /unlabel with no arguments' do issuable.update(label_ids: [inprogress.id]) # populate the label @@ -285,6 +313,16 @@ describe SlashCommands::InterpretService, services: true do let(:issuable) { merge_request } end + it_behaves_like 'multiple label command' do + let(:content) { %(/label ~"#{inprogress.title}" \n/label ~#{bug.title}) } + let(:issuable) { issue } + end + + it_behaves_like 'multiple label with same argument' do + let(:content) { %(/label ~"#{inprogress.title}" \n/label ~#{inprogress.title}) } + let(:issuable) { issue } + end + it_behaves_like 'unlabel command' do let(:content) { %(/unlabel ~"#{inprogress.title}") } let(:issuable) { issue } @@ -295,6 +333,11 @@ describe SlashCommands::InterpretService, services: true do let(:issuable) { merge_request } end + it_behaves_like 'multiple unlabel command' do + let(:content) { %(/unlabel ~"#{inprogress.title}" \n/unlabel ~#{bug.title}) } + let(:issuable) { issue } + end + it_behaves_like 'unlabel command with no argument' do let(:content) { %(/unlabel) } let(:issuable) { issue } -- cgit v1.2.1 From 9db841127bef1509d32ae5f3ff2458923c84a19d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 11 Oct 2016 18:22:41 +0200 Subject: Remove pointless `.vagrant_enabled` file --- .vagrant_enabled | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .vagrant_enabled diff --git a/.vagrant_enabled b/.vagrant_enabled deleted file mode 100644 index e69de29bb2d..00000000000 -- cgit v1.2.1 From ea3bbbdef86ae30fcf76baaba11a3fceb6d2aa03 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Tue, 11 Oct 2016 13:01:57 -0500 Subject: FIx JS bug with select2 because of missing `data-field` attribute in select box. --- app/views/projects/edit.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index d19422c8657..c8f84b96cb7 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -91,7 +91,7 @@ Git Large File Storage = link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') .col-md-3 - = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control' + = f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control', data: { field: 'lfs_enabled' } - if Gitlab.config.registry.enabled .form-group -- cgit v1.2.1 From b0acc0a308848529727e8bcf2a7c6e2bc0f76303 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 20:29:42 +0200 Subject: Add 8.12.5, 8.11.9, and 8.10.12 CHANGELOG entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- CHANGELOG | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 48eaa328f23..37cbfeff01a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,7 +5,6 @@ v 8.13.0 (unreleased) - Truncate long labels with ellipsis in labels page - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - - Improve issue load time performance by avoiding ORDER BY in find_by call - Use gitlab-shell v3.6.2 (GIT TRACE logging) - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) @@ -84,7 +83,6 @@ v 8.13.0 (unreleased) - API: expose pipeline data in builds API (!6502, Guilherme Salazar) - Notify the Merger about merge after successful build (Dimitris Karakasilis) - Reorder issue and merge request titles to show IDs first. !6503 (Greg Laubenstein) - - Add a new gitlab:users:clear_all_authentication_tokens task. !6745 - Reduce queries needed to find users using their SSH keys when pushing commits - Prevent rendering the link to all when the author has no access (Katarzyna Kobierska Ula Budziszewska) - Fix broken repository 500 errors in project list @@ -102,8 +100,13 @@ v 8.13.0 (unreleased) - API: all unknown routing will be handled with 404 Not Found - Make guests unable to view MRs on private projects -v 8.12.5 (unreleased) - - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread +v 8.12.5 + - Switch from request to env in ::API::Helpers. !6615 + - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread. !6714 + - Improve issue load time performance by avoiding ORDER BY in find_by call. !6724 + - Add a new gitlab:users:clear_all_authentication_tokens task. !6745 + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access v 8.12.4 - Fix "Copy to clipboard" tooltip to say "Copied!" when clipboard button is clicked. !6294 (lukehowell) @@ -330,6 +333,10 @@ v 8.12.0 - Fix non-master branch readme display in tree view - Add UX improvements for merge request version diffs +v 8.11.9 + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access + v 8.11.8 - Respect the fork_project permission when forking projects - Set a restrictive CORS policy on the API for credentialed requests @@ -555,6 +562,10 @@ v 8.11.0 - Update gitlab_git gem to 10.4.7 - Simplify SQL queries of marking a todo as done +v 8.10.12 + - Don't send Private-Token (API authentication) headers to Sentry + - Share projects via the API only with groups the authenticated user can access + v 8.10.11 - Respect the fork_project permission when forking projects - Set a restrictive CORS policy on the API for credentialed requests -- cgit v1.2.1 From 670b2eb5c05a721f810a5b248612cadde0eaf2ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 10:20:35 +0000 Subject: Merge branch 'api-fix-project-group-sharing' into 'security' MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API: Share projects only with groups current_user can access Aims to address the issues here: https://gitlab.com/gitlab-org/gitlab-ce/issues/23004 * Projects can be shared with non-existent groups * Projects can be shared with groups that the current user does not have access to read Concerns: The new implementation of the API endpoint allows projects to be shared with a larger range of groups than can be done via the web UI. The form for sharing a project with a group uses the following API endpoint to index the available groups: https://gitlab.com/gitlab-org/gitlab-ce/blob/494269fc92f61098ee6bd635a0426129ce2c5456/lib/api/groups.rb#L17. The groups indexed in the web form will only be those groups that the user is currently a member of. The new implementation allows projects to be shared with any group that the authenticated user has access to view. This widens the range of groups to those that are public and internal. See merge request !2005 Signed-off-by: Rémy Coutable --- app/models/project_group_link.rb | 2 +- lib/api/projects.rb | 6 ++++++ spec/models/project_group_link_spec.rb | 2 +- spec/requests/api/projects_spec.rb | 14 ++++++++++++++ 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/app/models/project_group_link.rb b/app/models/project_group_link.rb index 7613cbdea93..db46def11eb 100644 --- a/app/models/project_group_link.rb +++ b/app/models/project_group_link.rb @@ -10,7 +10,7 @@ class ProjectGroupLink < ActiveRecord::Base belongs_to :group validates :project_id, presence: true - validates :group_id, presence: true + validates :group, presence: true validates :group_id, uniqueness: { scope: [:project_id], message: "already shared with this group" } validates :group_access, presence: true validates :group_access, inclusion: { in: Gitlab::Access.values }, presence: true diff --git a/lib/api/projects.rb b/lib/api/projects.rb index c24e8e8bd9b..da16e24d7ea 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -416,6 +416,12 @@ module API required_attributes! [:group_id, :group_access] attrs = attributes_for_keys [:group_id, :group_access, :expires_at] + group = Group.find_by_id(attrs[:group_id]) + + unless group && can?(current_user, :read_group, group) + not_found!('Group') + end + unless user_project.allowed_to_share_with_group? return render_api_error!("The project sharing with group is disabled", 400) end diff --git a/spec/models/project_group_link_spec.rb b/spec/models/project_group_link_spec.rb index 2fa6715fcaf..c5ff1941378 100644 --- a/spec/models/project_group_link_spec.rb +++ b/spec/models/project_group_link_spec.rb @@ -11,7 +11,7 @@ describe ProjectGroupLink do it { should validate_presence_of(:project_id) } it { should validate_uniqueness_of(:group_id).scoped_to(:project_id).with_message(/already shared/) } - it { should validate_presence_of(:group_id) } + it { should validate_presence_of(:group) } it { should validate_presence_of(:group_access) } end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 5f19638b460..19a2c7a2700 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -819,6 +819,20 @@ describe API::API, api: true do expect(response.status).to eq 400 end + it 'returns a 404 error when user cannot read group' do + private_group = create(:group, :private) + + post api("/projects/#{project.id}/share", user), group_id: private_group.id, group_access: Gitlab::Access::DEVELOPER + + expect(response.status).to eq 404 + end + + it 'returns a 404 error when group does not exist' do + post api("/projects/#{project.id}/share", user), group_id: 1234, group_access: Gitlab::Access::DEVELOPER + + expect(response.status).to eq 404 + end + it "returns a 409 error when wrong params passed" do post api("/projects/#{project.id}/share", user), group_id: group.id, group_access: 1234 expect(response.status).to eq 409 -- cgit v1.2.1 From c7865786572a3a2e12ed67a3f8121c0be8c4ba38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 11 Oct 2016 21:26:49 +0200 Subject: Make spec deterministic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- spec/requests/api/users_spec.rb | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 91e54af1209..684c27f4353 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -926,20 +926,18 @@ describe API::API, api: true do it 'returns the "joined" event' do get api("/users/#{user.id}/events", user) - first_event = json_response.first + comment_event = json_response.find { |e| e['action_name'] == 'commented on' } - expect(first_event['action_name']).to eq('commented on') - expect(first_event['project_id'].to_i).to eq(project.id) - expect(first_event['author_username']).to eq(user.username) - expect(first_event['note']['id']).to eq(note.id) - expect(first_event['note']['body']).to eq('What an awesome day!') + expect(comment_event['project_id'].to_i).to eq(project.id) + expect(comment_event['author_username']).to eq(user.username) + expect(comment_event['note']['id']).to eq(note.id) + expect(comment_event['note']['body']).to eq('What an awesome day!') - last_event = json_response.last + joined_event = json_response.find { |e| e['action_name'] == 'joined' } - expect(last_event['action_name']).to eq('joined') - expect(last_event['project_id'].to_i).to eq(project.id) - expect(last_event['author_username']).to eq(user.username) - expect(last_event['author']['name']).to eq(user.name) + expect(joined_event['project_id'].to_i).to eq(project.id) + expect(joined_event['author_username']).to eq(user.username) + expect(joined_event['author']['name']).to eq(user.name) end end end -- cgit v1.2.1 From 32186b4ab89e751d42590d2cbfbcf0c6a131bdca Mon Sep 17 00:00:00 2001 From: Justin DiPierro Date: Thu, 6 Oct 2016 12:42:17 -0400 Subject: Added 'Download' button to snippet view --- CHANGELOG | 1 + app/controllers/snippets_controller.rb | 14 +++++++++++--- app/views/snippets/show.html.haml | 1 + config/routes/snippets.rb | 1 + 4 files changed, 14 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e2480b66ae4..ad34e7476ce 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -38,6 +38,7 @@ v 8.13.0 (unreleased) - Fix todos page mobile viewport layout (ClemMakesApps) - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) - Remove redundant mixins (ClemMakesApps) + - Added 'Download' button to the Snippets page (Justin DiPierro) - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) - Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska) - Fix that manual jobs would no longer block jobs in the next stage. !6604 diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index d198782138a..dee57e4a388 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -1,10 +1,10 @@ class SnippetsController < ApplicationController include ToggleAwardEmoji - before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] + before_action :snippet, only: [:show, :edit, :destroy, :update, :raw, :download] # Allow read snippet - before_action :authorize_read_snippet!, only: [:show, :raw] + before_action :authorize_read_snippet!, only: [:show, :raw, :download] # Allow modify snippet before_action :authorize_update_snippet!, only: [:edit, :update] @@ -12,7 +12,7 @@ class SnippetsController < ApplicationController # Allow destroy snippet before_action :authorize_admin_snippet!, only: [:destroy] - skip_before_action :authenticate_user!, only: [:index, :show, :raw] + skip_before_action :authenticate_user!, only: [:index, :show, :raw, :download] layout 'snippets' respond_to :html @@ -75,6 +75,14 @@ class SnippetsController < ApplicationController ) end + def download + send_data( + @snippet.content, + type: 'text/plain; charset=utf-8', + filename: @snippet.sanitized_file_name + ) + end + protected def snippet diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index cd89155c616..27d7a6c5bb6 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -9,6 +9,7 @@ .file-actions = clipboard_button(clipboard_target: ".blob-content[data-blob-id='#{@snippet.id}']") = link_to 'Raw', raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank" + = link_to 'Download', download_snippet_path(@snippet), class: "btn btn-sm" = render 'shared/snippets/blob' = render 'award_emoji/awards_block', awardable: @snippet, inline: true \ No newline at end of file diff --git a/config/routes/snippets.rb b/config/routes/snippets.rb index 1949f215c66..3ca096f31ba 100644 --- a/config/routes/snippets.rb +++ b/config/routes/snippets.rb @@ -1,6 +1,7 @@ resources :snippets, concerns: :awardable do member do get 'raw' + get 'download' end end -- cgit v1.2.1 From ba99f0b45157e841550f87c1d4e6879e108d2d6c Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 11 Oct 2016 15:17:56 -0700 Subject: Add CHANGELOG entry for 8.12.6 --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 37cbfeff01a..c8271f55cfb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -100,6 +100,9 @@ v 8.13.0 (unreleased) - API: all unknown routing will be handled with 404 Not Found - Make guests unable to view MRs on private projects +v 8.12.6 + - Update mailroom to 0.8.1 in Gemfile.lock !6814 + v 8.12.5 - Switch from request to env in ::API::Helpers. !6615 - Update the mail_room gem to 0.8.1 to fix a race condition with the mailbox watching thread. !6714 -- cgit v1.2.1 From f0150ef4d9ac869be21079d21c856a1ede72a3bf Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Tue, 20 Sep 2016 15:44:07 -0500 Subject: Add disabled delete button to protected branches --- CHANGELOG | 1 + app/views/projects/branches/_branch.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 24f77442f1a..8b5d95649bb 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -59,6 +59,7 @@ v 8.13.0 (unreleased) - Fix Long commit messages overflow viewport in file tree - Revert avoid touching file system on Build#artifacts? - Stop using a Redis lease when updating the project activity timestamp whenever a new event is created + - Add disabled delete button to protected branches (ClemMakesApps) - Add broadcast messages and alerts below sub-nav - Better empty state for Groups view - Update ruby-prof to 0.16.2. !6026 (Elan Ruusamäe) diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 5217b8bf028..4480b2f22c3 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -30,8 +30,8 @@ = render 'projects/buttons/download', project: @project, ref: branch.name - - if can_remove_branch?(@project, branch.name) - = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-remove remove-row has-tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do + - if can?(current_user, :push_code, @project) + = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: "btn btn-remove remove-row has-tooltip #{can_remove_branch?(@project, branch.name) ? '' : 'disabled'}", title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do = icon("trash-o") - if branch.name != @repository.root_ref -- cgit v1.2.1 From 9c92443ffe90ec58490fb66ebce98e8e19a07c9b Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Wed, 12 Oct 2016 10:46:30 +0500 Subject: Remove unused lets from deploy_key spec --- spec/models/deploy_key_spec.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb index 6a90598a629..93623e8e99b 100644 --- a/spec/models/deploy_key_spec.rb +++ b/spec/models/deploy_key_spec.rb @@ -1,9 +1,6 @@ require 'spec_helper' describe DeployKey, models: true do - let(:project) { create(:project) } - let(:deploy_key) { create(:deploy_key, projects: [project]) } - describe "Associations" do it { is_expected.to have_many(:deploy_keys_projects) } it { is_expected.to have_many(:projects) } -- cgit v1.2.1 From 31c41a8fe1ef67347e5bcfdb97ff15a079976945 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Wed, 12 Oct 2016 10:50:01 +0500 Subject: Use build instead create record in appearance_spec --- spec/models/appearance_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/appearance_spec.rb b/spec/models/appearance_spec.rb index c5658bd26e1..0b72a2f979b 100644 --- a/spec/models/appearance_spec.rb +++ b/spec/models/appearance_spec.rb @@ -1,7 +1,7 @@ require 'rails_helper' RSpec.describe Appearance, type: :model do - subject { create(:appearance) } + subject { build(:appearance) } it { is_expected.to be_valid } -- cgit v1.2.1 From b9b13ea8016077e186374e4ee45dfc8a59ea5a78 Mon Sep 17 00:00:00 2001 From: Thomas Balthazar Date: Mon, 8 Aug 2016 13:42:24 +0200 Subject: Create a new /templates API namespace The /licenses, /gitignores and /gitlab_ci_ymls endpoints are now also available under a new /templates namespace. Old endpoints will be deprecated when GitLab 9.0.0 is released. --- CHANGELOG | 1 + app/assets/javascripts/api.js | 7 +- doc/api/README.md | 4 +- doc/api/licenses.md | 147 ------- doc/api/templates/gitignores.md | 579 ++++++++++++++++++++++++++++ doc/api/templates/gitlab_ci_ymls.md | 120 ++++++ doc/api/templates/licenses.md | 147 +++++++ lib/api/api.rb | 1 - lib/api/license_templates.rb | 58 --- lib/api/templates.rb | 124 ++++-- spec/requests/api/license_templates_spec.rb | 136 ------- spec/requests/api/templates_spec.rb | 204 ++++++++-- 12 files changed, 1129 insertions(+), 399 deletions(-) delete mode 100644 doc/api/licenses.md create mode 100644 doc/api/templates/gitignores.md create mode 100644 doc/api/templates/gitlab_ci_ymls.md create mode 100644 doc/api/templates/licenses.md delete mode 100644 lib/api/license_templates.rb delete mode 100644 spec/requests/api/license_templates_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 201bab610ba..65d890185fa 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -18,6 +18,7 @@ v 8.13.0 (unreleased) - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) - Fix Error 500 when viewing old merge requests with bad diff data + - Create a new /templates namespace for the /licenses, /gitignores and /gitlab_ci_ymls API endpoints. !5717 (tbalthazar) - Speed-up group milestones show page - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index 599331df3f5..56ec1489f89 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -6,11 +6,10 @@ groupProjectsPath: "/api/:version/groups/:id/projects.json", projectsPath: "/api/:version/projects.json?simple=true", labelsPath: "/:namespace_path/:project_path/labels", - licensePath: "/api/:version/licenses/:key", - gitignorePath: "/api/:version/gitignores/:key", - gitlabCiYmlPath: "/api/:version/gitlab_ci_ymls/:key", + licensePath: "/api/:version/templates/licenses/:key", + gitignorePath: "/api/:version/templates/gitignores/:key", + gitlabCiYmlPath: "/api/:version/templates/gitlab_ci_ymls/:key", issuableTemplatePath: "/:namespace_path/:project_path/templates/:type/:key", - group: function(group_id, callback) { var url = Api.buildUrl(Api.groupPath) .replace(':id', group_id); diff --git a/doc/api/README.md b/doc/api/README.md index 9e907689c80..00cb003ed8a 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -17,6 +17,8 @@ following locations: - [Commits](commits.md) - [Deployments](deployments.md) - [Deploy Keys](deploy_keys.md) +- [Gitignores templates](templates/gitignores.md) +- [GitLab CI Config templates](templates/gitlab_ci_ymls.md) - [Groups](groups.md) - [Group Access Requests](access_requests.md) - [Group Members](members.md) @@ -25,7 +27,7 @@ following locations: - [Labels](labels.md) - [Merge Requests](merge_requests.md) - [Milestones](milestones.md) -- [Open source license templates](licenses.md) +- [Open source license templates](templates/licenses.md) - [Namespaces](namespaces.md) - [Notes](notes.md) (comments) - [Notification settings](notification_settings.md) diff --git a/doc/api/licenses.md b/doc/api/licenses.md deleted file mode 100644 index ed26d1fb7fb..00000000000 --- a/doc/api/licenses.md +++ /dev/null @@ -1,147 +0,0 @@ -# Licenses - -## List license templates - -Get all license templates. - -``` -GET /licenses -``` - -| Attribute | Type | Required | Description | -| --------- | ------- | -------- | --------------------- | -| `popular` | boolean | no | If passed, returns only popular licenses | - -```bash -curl https://gitlab.example.com/api/v3/licenses?popular=1 -``` - -Example response: - -```json -[ - { - "key": "apache-2.0", - "name": "Apache License 2.0", - "nickname": null, - "featured": true, - "html_url": "http://choosealicense.com/licenses/apache-2.0/", - "source_url": "http://www.apache.org/licenses/LICENSE-2.0.html", - "description": "A permissive license that also provides an express grant of patent rights from contributors to users.", - "conditions": [ - "include-copyright", - "document-changes" - ], - "permissions": [ - "commercial-use", - "modifications", - "distribution", - "patent-use", - "private-use" - ], - "limitations": [ - "trademark-use", - "no-liability" - ], - "content": " Apache License\n Version 2.0, January 2004\n [...]" - }, - { - "key": "gpl-3.0", - "name": "GNU General Public License v3.0", - "nickname": "GNU GPLv3", - "featured": true, - "html_url": "http://choosealicense.com/licenses/gpl-3.0/", - "source_url": "http://www.gnu.org/licenses/gpl-3.0.txt", - "description": "The GNU GPL is the most widely used free software license and has a strong copyleft requirement. When distributing derived works, the source code of the work must be made available under the same license.", - "conditions": [ - "include-copyright", - "document-changes", - "disclose-source", - "same-license" - ], - "permissions": [ - "commercial-use", - "modifications", - "distribution", - "patent-use", - "private-use" - ], - "limitations": [ - "no-liability" - ], - "content": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n [...]" - }, - { - "key": "mit", - "name": "MIT License", - "nickname": null, - "featured": true, - "html_url": "http://choosealicense.com/licenses/mit/", - "source_url": "http://opensource.org/licenses/MIT", - "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.", - "conditions": [ - "include-copyright" - ], - "permissions": [ - "commercial-use", - "modifications", - "distribution", - "private-use" - ], - "limitations": [ - "no-liability" - ], - "content": "The MIT License (MIT)\n\nCopyright (c) [year] [fullname]\n [...]" - } -] -``` - -## Single license template - -Get a single license template. You can pass parameters to replace the license -placeholder. - -``` -GET /licenses/:key -``` - -| Attribute | Type | Required | Description | -| ---------- | ------ | -------- | ----------- | -| `key` | string | yes | The key of the license template | -| `project` | string | no | The copyrighted project name | -| `fullname` | string | no | The full-name of the copyright holder | - ->**Note:** -If you omit the `fullname` parameter but authenticate your request, the name of -the authenticated user will be used to replace the copyright holder placeholder. - -```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/licenses/mit?project=My+Cool+Project -``` - -Example response: - -```json -{ - "key": "mit", - "name": "MIT License", - "nickname": null, - "featured": true, - "html_url": "http://choosealicense.com/licenses/mit/", - "source_url": "http://opensource.org/licenses/MIT", - "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.", - "conditions": [ - "include-copyright" - ], - "permissions": [ - "commercial-use", - "modifications", - "distribution", - "private-use" - ], - "limitations": [ - "no-liability" - ], - "content": "The MIT License (MIT)\n\nCopyright (c) 2016 John Doe\n [...]" -} -``` diff --git a/doc/api/templates/gitignores.md b/doc/api/templates/gitignores.md new file mode 100644 index 00000000000..8235be92b12 --- /dev/null +++ b/doc/api/templates/gitignores.md @@ -0,0 +1,579 @@ +# Gitignores + +## List gitignore templates + +Get all gitignore templates. + +``` +GET /templates/gitignores +``` + +```bash +curl https://gitlab.example.com/api/v3/templates/gitignores +``` + +Example response: + +```json +[ + { + "name": "AppEngine" + }, + { + "name": "Laravel" + }, + { + "name": "Elisp" + }, + { + "name": "SketchUp" + }, + { + "name": "Ada" + }, + { + "name": "Ruby" + }, + { + "name": "Kohana" + }, + { + "name": "Nanoc" + }, + { + "name": "Erlang" + }, + { + "name": "OCaml" + }, + { + "name": "Lithium" + }, + { + "name": "Fortran" + }, + { + "name": "Scala" + }, + { + "name": "Node" + }, + { + "name": "Fancy" + }, + { + "name": "Perl" + }, + { + "name": "Zephir" + }, + { + "name": "WordPress" + }, + { + "name": "Symfony" + }, + { + "name": "FuelPHP" + }, + { + "name": "DM" + }, + { + "name": "Sdcc" + }, + { + "name": "Rust" + }, + { + "name": "C" + }, + { + "name": "Umbraco" + }, + { + "name": "Actionscript" + }, + { + "name": "Android" + }, + { + "name": "Grails" + }, + { + "name": "Composer" + }, + { + "name": "ExpressionEngine" + }, + { + "name": "Gcov" + }, + { + "name": "Qt" + }, + { + "name": "Phalcon" + }, + { + "name": "ArchLinuxPackages" + }, + { + "name": "TeX" + }, + { + "name": "SCons" + }, + { + "name": "Lilypond" + }, + { + "name": "CommonLisp" + }, + { + "name": "Rails" + }, + { + "name": "Mercury" + }, + { + "name": "Magento" + }, + { + "name": "ChefCookbook" + }, + { + "name": "GitBook" + }, + { + "name": "C++" + }, + { + "name": "Eagle" + }, + { + "name": "Go" + }, + { + "name": "OpenCart" + }, + { + "name": "Scheme" + }, + { + "name": "Typo3" + }, + { + "name": "SeamGen" + }, + { + "name": "Swift" + }, + { + "name": "Elm" + }, + { + "name": "Unity" + }, + { + "name": "Agda" + }, + { + "name": "CUDA" + }, + { + "name": "VVVV" + }, + { + "name": "Finale" + }, + { + "name": "LemonStand" + }, + { + "name": "Textpattern" + }, + { + "name": "Julia" + }, + { + "name": "Packer" + }, + { + "name": "Scrivener" + }, + { + "name": "Dart" + }, + { + "name": "Plone" + }, + { + "name": "Jekyll" + }, + { + "name": "Xojo" + }, + { + "name": "LabVIEW" + }, + { + "name": "Autotools" + }, + { + "name": "KiCad" + }, + { + "name": "Prestashop" + }, + { + "name": "ROS" + }, + { + "name": "Smalltalk" + }, + { + "name": "GWT" + }, + { + "name": "OracleForms" + }, + { + "name": "SugarCRM" + }, + { + "name": "Nim" + }, + { + "name": "SymphonyCMS" + }, + { + "name": "Maven" + }, + { + "name": "CFWheels" + }, + { + "name": "Python" + }, + { + "name": "ZendFramework" + }, + { + "name": "CakePHP" + }, + { + "name": "Concrete5" + }, + { + "name": "PlayFramework" + }, + { + "name": "Terraform" + }, + { + "name": "Elixir" + }, + { + "name": "CMake" + }, + { + "name": "Joomla" + }, + { + "name": "Coq" + }, + { + "name": "Delphi" + }, + { + "name": "Haskell" + }, + { + "name": "Yii" + }, + { + "name": "Java" + }, + { + "name": "UnrealEngine" + }, + { + "name": "AppceleratorTitanium" + }, + { + "name": "CraftCMS" + }, + { + "name": "ForceDotCom" + }, + { + "name": "ExtJs" + }, + { + "name": "MetaProgrammingSystem" + }, + { + "name": "D" + }, + { + "name": "Objective-C" + }, + { + "name": "RhodesRhomobile" + }, + { + "name": "R" + }, + { + "name": "EPiServer" + }, + { + "name": "Yeoman" + }, + { + "name": "VisualStudio" + }, + { + "name": "Processing" + }, + { + "name": "Leiningen" + }, + { + "name": "Stella" + }, + { + "name": "Opa" + }, + { + "name": "Drupal" + }, + { + "name": "TurboGears2" + }, + { + "name": "Idris" + }, + { + "name": "Jboss" + }, + { + "name": "CodeIgniter" + }, + { + "name": "Qooxdoo" + }, + { + "name": "Waf" + }, + { + "name": "Sass" + }, + { + "name": "Lua" + }, + { + "name": "Clojure" + }, + { + "name": "IGORPro" + }, + { + "name": "Gradle" + }, + { + "name": "Archives" + }, + { + "name": "SynopsysVCS" + }, + { + "name": "Ninja" + }, + { + "name": "Tags" + }, + { + "name": "OSX" + }, + { + "name": "Dreamweaver" + }, + { + "name": "CodeKit" + }, + { + "name": "NotepadPP" + }, + { + "name": "VisualStudioCode" + }, + { + "name": "Mercurial" + }, + { + "name": "BricxCC" + }, + { + "name": "DartEditor" + }, + { + "name": "Eclipse" + }, + { + "name": "Cloud9" + }, + { + "name": "TortoiseGit" + }, + { + "name": "NetBeans" + }, + { + "name": "GPG" + }, + { + "name": "Espresso" + }, + { + "name": "Redcar" + }, + { + "name": "Xcode" + }, + { + "name": "Matlab" + }, + { + "name": "LyX" + }, + { + "name": "SlickEdit" + }, + { + "name": "Dropbox" + }, + { + "name": "CVS" + }, + { + "name": "Calabash" + }, + { + "name": "JDeveloper" + }, + { + "name": "Vagrant" + }, + { + "name": "IPythonNotebook" + }, + { + "name": "TextMate" + }, + { + "name": "Ensime" + }, + { + "name": "WebMethods" + }, + { + "name": "VirtualEnv" + }, + { + "name": "Emacs" + }, + { + "name": "Momentics" + }, + { + "name": "JetBrains" + }, + { + "name": "SublimeText" + }, + { + "name": "Kate" + }, + { + "name": "ModelSim" + }, + { + "name": "Redis" + }, + { + "name": "KDevelop4" + }, + { + "name": "Bazaar" + }, + { + "name": "Linux" + }, + { + "name": "Windows" + }, + { + "name": "XilinxISE" + }, + { + "name": "Lazarus" + }, + { + "name": "EiffelStudio" + }, + { + "name": "Anjuta" + }, + { + "name": "Vim" + }, + { + "name": "Otto" + }, + { + "name": "MicrosoftOffice" + }, + { + "name": "LibreOffice" + }, + { + "name": "SBT" + }, + { + "name": "MonoDevelop" + }, + { + "name": "SVN" + }, + { + "name": "FlexBuilder" + } +] +``` + +## Single gitignore template + +Get a single gitignore template. + +``` +GET /templates/gitignores/:key +``` + +| Attribute | Type | Required | Description | +| ---------- | ------ | -------- | ----------- | +| `key` | string | yes | The key of the gitignore template | + +```bash +curl https://gitlab.example.com/api/v3/templates/gitignores/Ruby +``` + +Example response: + +```json +{ + "name": "Ruby", + "content": "*.gem\n*.rbc\n/.config\n/coverage/\n/InstalledFiles\n/pkg/\n/spec/reports/\n/spec/examples.txt\n/test/tmp/\n/test/version_tmp/\n/tmp/\n\n# Used by dotenv library to load environment variables.\n# .env\n\n## Specific to RubyMotion:\n.dat*\n.repl_history\nbuild/\n*.bridgesupport\nbuild-iPhoneOS/\nbuild-iPhoneSimulator/\n\n## Specific to RubyMotion (use of CocoaPods):\n#\n# We recommend against adding the Pods directory to your .gitignore. However\n# you should judge for yourself, the pros and cons are mentioned at:\n# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control\n#\n# vendor/Pods/\n\n## Documentation cache and generated files:\n/.yardoc/\n/_yardoc/\n/doc/\n/rdoc/\n\n## Environment normalization:\n/.bundle/\n/vendor/bundle\n/lib/bundler/man/\n\n# for a library or gem, you might want to ignore these files since the code is\n# intended to run in multiple environments; otherwise, check them in:\n# Gemfile.lock\n# .ruby-version\n# .ruby-gemset\n\n# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:\n.rvmrc\n" +} +``` diff --git a/doc/api/templates/gitlab_ci_ymls.md b/doc/api/templates/gitlab_ci_ymls.md new file mode 100644 index 00000000000..e120016fbe6 --- /dev/null +++ b/doc/api/templates/gitlab_ci_ymls.md @@ -0,0 +1,120 @@ +# GitLab CI YMLs + +## List GitLab CI YML templates + +Get all GitLab CI YML templates. + +``` +GET /templates/gitlab_ci_ymls +``` + +```bash +curl https://gitlab.example.com/api/v3/templates/gitlab_ci_ymls +``` + +Example response: + +```json +[ + { + "name": "C++" + }, + { + "name": "Docker" + }, + { + "name": "Elixir" + }, + { + "name": "LaTeX" + }, + { + "name": "Grails" + }, + { + "name": "Rust" + }, + { + "name": "Nodejs" + }, + { + "name": "Ruby" + }, + { + "name": "Scala" + }, + { + "name": "Maven" + }, + { + "name": "Harp" + }, + { + "name": "Pelican" + }, + { + "name": "Hyde" + }, + { + "name": "Nanoc" + }, + { + "name": "Octopress" + }, + { + "name": "JBake" + }, + { + "name": "HTML" + }, + { + "name": "Hugo" + }, + { + "name": "Metalsmith" + }, + { + "name": "Hexo" + }, + { + "name": "Lektor" + }, + { + "name": "Doxygen" + }, + { + "name": "Brunch" + }, + { + "name": "Jekyll" + }, + { + "name": "Middleman" + } +] +``` + +## Single GitLab CI YML template + +Get a single GitLab CI YML template. + +``` +GET /templates/gitlab_ci_ymls/:key +``` + +| Attribute | Type | Required | Description | +| ---------- | ------ | -------- | ----------- | +| `key` | string | yes | The key of the GitLab CI YML template | + +```bash +curl https://gitlab.example.com/api/v3/templates/gitlab_ci_ymls/Ruby +``` + +Example response: + +```json +{ + "name": "Ruby", + "content": "# This file is a template, and might need editing before it works on your project.\n# Official language image. Look for the different tagged releases at:\n# https://hub.docker.com/r/library/ruby/tags/\nimage: \"ruby:2.3\"\n\n# Pick zero or more services to be used on all builds.\n# Only needed when using a docker container to run your tests in.\n# Check out: http://docs.gitlab.com/ce/ci/docker/using_docker_images.html#what-is-service\nservices:\n - mysql:latest\n - redis:latest\n - postgres:latest\n\nvariables:\n POSTGRES_DB: database_name\n\n# Cache gems in between builds\ncache:\n paths:\n - vendor/ruby\n\n# This is a basic example for a gem or script which doesn't use\n# services such as redis or postgres\nbefore_script:\n - ruby -v # Print out ruby version for debugging\n # Uncomment next line if your rails app needs a JS runtime:\n # - apt-get update -q && apt-get install nodejs -yqq\n - gem install bundler --no-ri --no-rdoc # Bundler is not installed with the image\n - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby\n\n# Optional - Delete if not using `rubocop`\nrubocop:\n script:\n - rubocop\n\nrspec:\n script:\n - rspec spec\n\nrails:\n variables:\n DATABASE_URL: \"postgresql://postgres:postgres@postgres:5432/$POSTGRES_DB\"\n script:\n - bundle exec rake db:migrate\n - bundle exec rake db:seed\n - bundle exec rake test\n" +} +``` diff --git a/doc/api/templates/licenses.md b/doc/api/templates/licenses.md new file mode 100644 index 00000000000..ae7218cf1bd --- /dev/null +++ b/doc/api/templates/licenses.md @@ -0,0 +1,147 @@ +# Licenses + +## List license templates + +Get all license templates. + +``` +GET /templates/licenses +``` + +| Attribute | Type | Required | Description | +| --------- | ------- | -------- | --------------------- | +| `popular` | boolean | no | If passed, returns only popular licenses | + +```bash +curl https://gitlab.example.com/api/v3/templates/licenses?popular=1 +``` + +Example response: + +```json +[ + { + "key": "apache-2.0", + "name": "Apache License 2.0", + "nickname": null, + "featured": true, + "html_url": "http://choosealicense.com/licenses/apache-2.0/", + "source_url": "http://www.apache.org/licenses/LICENSE-2.0.html", + "description": "A permissive license that also provides an express grant of patent rights from contributors to users.", + "conditions": [ + "include-copyright", + "document-changes" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "patent-use", + "private-use" + ], + "limitations": [ + "trademark-use", + "no-liability" + ], + "content": " Apache License\n Version 2.0, January 2004\n [...]" + }, + { + "key": "gpl-3.0", + "name": "GNU General Public License v3.0", + "nickname": "GNU GPLv3", + "featured": true, + "html_url": "http://choosealicense.com/licenses/gpl-3.0/", + "source_url": "http://www.gnu.org/licenses/gpl-3.0.txt", + "description": "The GNU GPL is the most widely used free software license and has a strong copyleft requirement. When distributing derived works, the source code of the work must be made available under the same license.", + "conditions": [ + "include-copyright", + "document-changes", + "disclose-source", + "same-license" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "patent-use", + "private-use" + ], + "limitations": [ + "no-liability" + ], + "content": " GNU GENERAL PUBLIC LICENSE\n Version 3, 29 June 2007\n [...]" + }, + { + "key": "mit", + "name": "MIT License", + "nickname": null, + "featured": true, + "html_url": "http://choosealicense.com/licenses/mit/", + "source_url": "http://opensource.org/licenses/MIT", + "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.", + "conditions": [ + "include-copyright" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "private-use" + ], + "limitations": [ + "no-liability" + ], + "content": "The MIT License (MIT)\n\nCopyright (c) [year] [fullname]\n [...]" + } +] +``` + +## Single license template + +Get a single license template. You can pass parameters to replace the license +placeholder. + +``` +GET /templates/licenses/:key +``` + +| Attribute | Type | Required | Description | +| ---------- | ------ | -------- | ----------- | +| `key` | string | yes | The key of the license template | +| `project` | string | no | The copyrighted project name | +| `fullname` | string | no | The full-name of the copyright holder | + +>**Note:** +If you omit the `fullname` parameter but authenticate your request, the name of +the authenticated user will be used to replace the copyright holder placeholder. + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/templates/licenses/mit?project=My+Cool+Project +``` + +Example response: + +```json +{ + "key": "mit", + "name": "MIT License", + "nickname": null, + "featured": true, + "html_url": "http://choosealicense.com/licenses/mit/", + "source_url": "http://opensource.org/licenses/MIT", + "description": "A permissive license that is short and to the point. It lets people do anything with your code with proper attribution and without warranty.", + "conditions": [ + "include-copyright" + ], + "permissions": [ + "commercial-use", + "modifications", + "distribution", + "private-use" + ], + "limitations": [ + "no-liability" + ], + "content": "The MIT License (MIT)\n\nCopyright (c) 2016 John Doe\n [...]" +} +``` diff --git a/lib/api/api.rb b/lib/api/api.rb index 99722a0a65c..c64d444842d 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -46,7 +46,6 @@ module API mount ::API::Boards mount ::API::Keys mount ::API::Labels - mount ::API::LicenseTemplates mount ::API::Lint mount ::API::Members mount ::API::MergeRequests diff --git a/lib/api/license_templates.rb b/lib/api/license_templates.rb deleted file mode 100644 index d0552299ed0..00000000000 --- a/lib/api/license_templates.rb +++ /dev/null @@ -1,58 +0,0 @@ -module API - # License Templates API - class LicenseTemplates < Grape::API - PROJECT_TEMPLATE_REGEX = - /[\<\{\[] - (project|description| - one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here - [\>\}\]]/xi.freeze - YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze - FULLNAME_TEMPLATE_REGEX = - /[\<\{\[] - (fullname|name\sof\s(author|copyright\sowner)) - [\>\}\]]/xi.freeze - - # Get the list of the available license templates - # - # Parameters: - # popular - Filter licenses to only the popular ones - # - # Example Request: - # GET /licenses - # GET /licenses?popular=1 - get 'licenses' do - options = { - featured: params[:popular].present? ? true : nil - } - present Licensee::License.all(options), with: Entities::RepoLicense - end - - # Get text for specific license - # - # Parameters: - # key (required) - The key of a license - # project - Copyrighted project name - # fullname - Full name of copyright holder - # - # Example Request: - # GET /licenses/mit - # - get 'licenses/:key', requirements: { key: /[\w\.-]+/ } do - required_attributes! [:key] - - not_found!('License') unless Licensee::License.find(params[:key]) - - # We create a fresh Licensee::License object since we'll modify its - # content in place below. - license = Licensee::License.new(params[:key]) - - license.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s) - license.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present? - - fullname = params[:fullname].presence || current_user.try(:name) - license.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname - - present license, with: Entities::RepoLicense - end - end -end diff --git a/lib/api/templates.rb b/lib/api/templates.rb index b9e718147e1..8a53d9c0095 100644 --- a/lib/api/templates.rb +++ b/lib/api/templates.rb @@ -1,39 +1,115 @@ module API class Templates < Grape::API GLOBAL_TEMPLATE_TYPES = { - gitignores: Gitlab::Template::GitignoreTemplate, - gitlab_ci_ymls: Gitlab::Template::GitlabCiYmlTemplate + gitignores: { + klass: Gitlab::Template::GitignoreTemplate, + gitlab_version: 8.8 + }, + gitlab_ci_ymls: { + klass: Gitlab::Template::GitlabCiYmlTemplate, + gitlab_version: 8.9 + } }.freeze + PROJECT_TEMPLATE_REGEX = + /[\<\{\[] + (project|description| + one\sline\s.+\swhat\sit\sdoes\.) # matching the start and end is enough here + [\>\}\]]/xi.freeze + YEAR_TEMPLATE_REGEX = /[<{\[](year|yyyy)[>}\]]/i.freeze + FULLNAME_TEMPLATE_REGEX = + /[\<\{\[] + (fullname|name\sof\s(author|copyright\sowner)) + [\>\}\]]/xi.freeze + DEPRECATION_MESSAGE = ' This endpoint is deprecated and will be removed in GitLab 9.0.'.freeze helpers do + def parsed_license_template + # We create a fresh Licensee::License object since we'll modify its + # content in place below. + template = Licensee::License.new(params[:name]) + + template.content.gsub!(YEAR_TEMPLATE_REGEX, Time.now.year.to_s) + template.content.gsub!(PROJECT_TEMPLATE_REGEX, params[:project]) if params[:project].present? + + fullname = params[:fullname].presence || current_user.try(:name) + template.content.gsub!(FULLNAME_TEMPLATE_REGEX, fullname) if fullname + template + end + def render_response(template_type, template) not_found!(template_type.to_s.singularize) unless template present template, with: Entities::Template end end - GLOBAL_TEMPLATE_TYPES.each do |template_type, klass| - # Get the list of the available template - # - # Example Request: - # GET /gitignores - # GET /gitlab_ci_ymls - get template_type.to_s do - present klass.all, with: Entities::TemplatesList - end - - # Get the text for a specific template present in local filesystem - # - # Parameters: - # name (required) - The name of a template - # - # Example Request: - # GET /gitignores/Elixir - # GET /gitlab_ci_ymls/Ruby - get "#{template_type}/:name" do - required_attributes! [:name] - new_template = klass.find(params[:name]) - render_response(template_type, new_template) + { "licenses" => :deprecated, "templates/licenses" => :ok }.each do |route, status| + desc 'Get the list of the available license template' do + detailed_desc = 'This feature was introduced in GitLab 8.7.' + detailed_desc << DEPRECATION_MESSAGE unless status == :ok + detail detailed_desc + success Entities::RepoLicense + end + params do + optional :popular, type: Boolean, desc: 'If passed, returns only popular licenses' + end + get route do + options = { + featured: declared(params).popular.present? ? true : nil + } + present Licensee::License.all(options), with: Entities::RepoLicense + end + end + + { "licenses/:name" => :deprecated, "templates/licenses/:name" => :ok }.each do |route, status| + desc 'Get the text for a specific license' do + detailed_desc = 'This feature was introduced in GitLab 8.7.' + detailed_desc << DEPRECATION_MESSAGE unless status == :ok + detail detailed_desc + success Entities::RepoLicense + end + params do + requires :name, type: String, desc: 'The name of the template' + end + get route, requirements: { name: /[\w\.-]+/ } do + not_found!('License') unless Licensee::License.find(declared(params).name) + + template = parsed_license_template + + present template, with: Entities::RepoLicense + end + end + + GLOBAL_TEMPLATE_TYPES.each do |template_type, properties| + klass = properties[:klass] + gitlab_version = properties[:gitlab_version] + + { template_type => :deprecated, "templates/#{template_type}" => :ok }.each do |route, status| + desc 'Get the list of the available template' do + detailed_desc = "This feature was introduced in GitLab #{gitlab_version}." + detailed_desc << DEPRECATION_MESSAGE unless status == :ok + detail detailed_desc + success Entities::TemplatesList + end + get route do + present klass.all, with: Entities::TemplatesList + end + end + + { "#{template_type}/:name" => :deprecated, "templates/#{template_type}/:name" => :ok }.each do |route, status| + desc 'Get the text for a specific template present in local filesystem' do + detailed_desc = "This feature was introduced in GitLab #{gitlab_version}." + detailed_desc << DEPRECATION_MESSAGE unless status == :ok + detail detailed_desc + success Entities::Template + end + params do + requires :name, type: String, desc: 'The name of the template' + end + get route do + new_template = klass.find(declared(params).name) + + render_response(template_type, new_template) + end end end end diff --git a/spec/requests/api/license_templates_spec.rb b/spec/requests/api/license_templates_spec.rb deleted file mode 100644 index 9a1894d63a2..00000000000 --- a/spec/requests/api/license_templates_spec.rb +++ /dev/null @@ -1,136 +0,0 @@ -require 'spec_helper' - -describe API::API, api: true do - include ApiHelpers - - describe 'Entity' do - before { get api('/licenses/mit') } - - it { expect(json_response['key']).to eq('mit') } - it { expect(json_response['name']).to eq('MIT License') } - it { expect(json_response['nickname']).to be_nil } - it { expect(json_response['popular']).to be true } - it { expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/') } - it { expect(json_response['source_url']).to eq('https://opensource.org/licenses/MIT') } - it { expect(json_response['description']).to include('A permissive license that is short and to the point.') } - it { expect(json_response['conditions']).to eq(%w[include-copyright]) } - it { expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use]) } - it { expect(json_response['limitations']).to eq(%w[no-liability]) } - it { expect(json_response['content']).to include('The MIT License (MIT)') } - end - - describe 'GET /licenses' do - it 'returns a list of available license templates' do - get api('/licenses') - - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(15) - expect(json_response.map { |l| l['key'] }).to include('agpl-3.0') - end - - describe 'the popular parameter' do - context 'with popular=1' do - it 'returns a list of available popular license templates' do - get api('/licenses?popular=1') - - expect(response).to have_http_status(200) - expect(json_response).to be_an Array - expect(json_response.size).to eq(3) - expect(json_response.map { |l| l['key'] }).to include('apache-2.0') - end - end - end - end - - describe 'GET /licenses/:key' do - context 'with :project and :fullname given' do - before do - get api("/licenses/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}") - end - - context 'for the mit license' do - let(:license_type) { 'mit' } - - it 'returns the license text' do - expect(json_response['content']).to include('The MIT License (MIT)') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include("Copyright (c) #{Time.now.year} Anton") - end - end - - context 'for the agpl-3.0 license' do - let(:license_type) { 'agpl-3.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('GNU AFFERO GENERAL PUBLIC LICENSE') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include('My Awesome Project') - expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") - end - end - - context 'for the gpl-3.0 license' do - let(:license_type) { 'gpl-3.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include('My Awesome Project') - expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") - end - end - - context 'for the gpl-2.0 license' do - let(:license_type) { 'gpl-2.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include('My Awesome Project') - expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") - end - end - - context 'for the apache-2.0 license' do - let(:license_type) { 'apache-2.0' } - - it 'returns the license text' do - expect(json_response['content']).to include('Apache License') - end - - it 'replaces placeholder values' do - expect(json_response['content']).to include("Copyright #{Time.now.year} Anton") - end - end - - context 'for an uknown license' do - let(:license_type) { 'muth-over9000' } - - it 'returns a 404' do - expect(response).to have_http_status(404) - end - end - end - - context 'with no :fullname given' do - context 'with an authenticated user' do - let(:user) { create(:user) } - - it 'replaces the copyright owner placeholder with the name of the current user' do - get api('/licenses/mit', user) - - expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}") - end - end - end - end -end diff --git a/spec/requests/api/templates_spec.rb b/spec/requests/api/templates_spec.rb index 5bd5b861792..d32ba60fc4c 100644 --- a/spec/requests/api/templates_spec.rb +++ b/spec/requests/api/templates_spec.rb @@ -3,53 +3,201 @@ require 'spec_helper' describe API::Templates, api: true do include ApiHelpers - context 'global templates' do - describe 'the Template Entity' do - before { get api('/gitignores/Ruby') } + shared_examples_for 'the Template Entity' do |path| + before { get api(path) } - it { expect(json_response['name']).to eq('Ruby') } - it { expect(json_response['content']).to include('*.gem') } + it { expect(json_response['name']).to eq('Ruby') } + it { expect(json_response['content']).to include('*.gem') } + end + + shared_examples_for 'the TemplateList Entity' do |path| + before { get api(path) } + + it { expect(json_response.first['name']).not_to be_nil } + it { expect(json_response.first['content']).to be_nil } + end + + shared_examples_for 'requesting gitignores' do |path| + it 'returns a list of available gitignore templates' do + get api(path) + + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to be > 15 end + end - describe 'the TemplateList Entity' do - before { get api('/gitignores') } + shared_examples_for 'requesting gitlab-ci-ymls' do |path| + it 'returns a list of available gitlab_ci_ymls' do + get api(path) - it { expect(json_response.first['name']).not_to be_nil } - it { expect(json_response.first['content']).to be_nil } + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).not_to be_nil end + end + + shared_examples_for 'requesting gitlab-ci-yml for Ruby' do |path| + it 'adds a disclaimer on the top' do + get api(path) + + expect(response).to have_http_status(200) + expect(json_response['content']).to start_with("# This file is a template,") + end + end + + shared_examples_for 'the License Template Entity' do |path| + before { get api(path) } + + it 'returns a license template' do + expect(json_response['key']).to eq('mit') + expect(json_response['name']).to eq('MIT License') + expect(json_response['nickname']).to be_nil + expect(json_response['popular']).to be true + expect(json_response['html_url']).to eq('http://choosealicense.com/licenses/mit/') + expect(json_response['source_url']).to eq('https://opensource.org/licenses/MIT') + expect(json_response['description']).to include('A permissive license that is short and to the point.') + expect(json_response['conditions']).to eq(%w[include-copyright]) + expect(json_response['permissions']).to eq(%w[commercial-use modifications distribution private-use]) + expect(json_response['limitations']).to eq(%w[no-liability]) + expect(json_response['content']).to include('The MIT License (MIT)') + end + end - context 'requesting gitignores' do - describe 'GET /gitignores' do - it 'returns a list of available gitignore templates' do - get api('/gitignores') + shared_examples_for 'GET licenses' do |path| + it 'returns a list of available license templates' do + get api(path) - expect(response.status).to eq(200) + expect(response).to have_http_status(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(15) + expect(json_response.map { |l| l['key'] }).to include('agpl-3.0') + end + + describe 'the popular parameter' do + context 'with popular=1' do + it 'returns a list of available popular license templates' do + get api("#{path}?popular=1") + + expect(response).to have_http_status(200) expect(json_response).to be_an Array - expect(json_response.size).to be > 15 + expect(json_response.size).to eq(3) + expect(json_response.map { |l| l['key'] }).to include('apache-2.0') end end end + end - context 'requesting gitlab-ci-ymls' do - describe 'GET /gitlab_ci_ymls' do - it 'returns a list of available gitlab_ci_ymls' do - get api('/gitlab_ci_ymls') + shared_examples_for 'GET licenses/:name' do |path| + context 'with :project and :fullname given' do + before do + get api("#{path}/#{license_type}?project=My+Awesome+Project&fullname=Anton+#{license_type.upcase}") + end - expect(response.status).to eq(200) - expect(json_response).to be_an Array - expect(json_response.first['name']).not_to be_nil + context 'for the mit license' do + let(:license_type) { 'mit' } + + it 'returns the license text' do + expect(json_response['content']).to include('The MIT License (MIT)') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include("Copyright (c) #{Time.now.year} Anton") + end + end + + context 'for the agpl-3.0 license' do + let(:license_type) { 'agpl-3.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('GNU AFFERO GENERAL PUBLIC LICENSE') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include('My Awesome Project') + expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") + end + end + + context 'for the gpl-3.0 license' do + let(:license_type) { 'gpl-3.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include('My Awesome Project') + expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") + end + end + + context 'for the gpl-2.0 license' do + let(:license_type) { 'gpl-2.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('GNU GENERAL PUBLIC LICENSE') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include('My Awesome Project') + expect(json_response['content']).to include("Copyright (C) #{Time.now.year} Anton") + end + end + + context 'for the apache-2.0 license' do + let(:license_type) { 'apache-2.0' } + + it 'returns the license text' do + expect(json_response['content']).to include('Apache License') + end + + it 'replaces placeholder values' do + expect(json_response['content']).to include("Copyright #{Time.now.year} Anton") + end + end + + context 'for an uknown license' do + let(:license_type) { 'muth-over9000' } + + it 'returns a 404' do + expect(response).to have_http_status(404) end end end - describe 'GET /gitlab_ci_ymls/Ruby' do - it 'adds a disclaimer on the top' do - get api('/gitlab_ci_ymls/Ruby') + context 'with no :fullname given' do + context 'with an authenticated user' do + let(:user) { create(:user) } + + it 'replaces the copyright owner placeholder with the name of the current user' do + get api('/templates/licenses/mit', user) - expect(response).to have_http_status(200) - expect(json_response['name']).not_to be_nil - expect(json_response['content']).to start_with("# This file is a template,") + expect(json_response['content']).to include("Copyright (c) #{Time.now.year} #{user.name}") + end end end end + + describe 'with /templates namespace' do + it_behaves_like 'the Template Entity', '/templates/gitignores/Ruby' + it_behaves_like 'the TemplateList Entity', '/templates/gitignores' + it_behaves_like 'requesting gitignores', '/templates/gitignores' + it_behaves_like 'requesting gitlab-ci-ymls', '/templates/gitlab_ci_ymls' + it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/templates/gitlab_ci_ymls/Ruby' + it_behaves_like 'the License Template Entity', '/templates/licenses/mit' + it_behaves_like 'GET licenses', '/templates/licenses' + it_behaves_like 'GET licenses/:name', '/templates/licenses' + end + + describe 'without /templates namespace' do + it_behaves_like 'the Template Entity', '/gitignores/Ruby' + it_behaves_like 'the TemplateList Entity', '/gitignores' + it_behaves_like 'requesting gitignores', '/gitignores' + it_behaves_like 'requesting gitlab-ci-ymls', '/gitlab_ci_ymls' + it_behaves_like 'requesting gitlab-ci-yml for Ruby', '/gitlab_ci_ymls/Ruby' + it_behaves_like 'the License Template Entity', '/licenses/mit' + it_behaves_like 'GET licenses', '/licenses' + it_behaves_like 'GET licenses/:name', '/licenses' + end end -- cgit v1.2.1 From df404f551a29258fa5b97c0e7abe144fd7a0fb31 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 12 Oct 2016 12:07:58 +0200 Subject: Schedule async pipeline success worker after commit --- app/models/ci/pipeline.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index d3a6e0a11f0..957f6755b2e 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -3,6 +3,7 @@ module Ci extend Ci::Model include HasStatus include Importable + include AfterCommitQueue self.table_name = 'ci_commits' @@ -71,7 +72,7 @@ module Ci end after_transition [:created, :pending, :running] => :success do |pipeline| - PipelineSuccessWorker.perform_async(pipeline.id) + pipeline.run_after_commit { PipelineSuccessWorker.perform_async(id) } end after_transition do |pipeline, transition| -- cgit v1.2.1 From 9eedd6f9c7baa5693a24c33c29909bfc7dac1b21 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 12 Oct 2016 12:23:52 +0200 Subject: Improve desc for pipeline integration spec in MWBS --- .../merge_when_build_succeeds_service_spec.rb | 36 ++++++++++++---------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb index 01cdc172136..b80cfd8f450 100644 --- a/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb +++ b/spec/services/merge_requests/merge_when_build_succeeds_service_spec.rb @@ -98,7 +98,26 @@ describe MergeRequests::MergeWhenBuildSucceedsService do service.trigger(unrelated_pipeline) end end + end + + describe "#cancel" do + before do + service.cancel(mr_merge_if_green_enabled) + end + + it "resets all the merge_when_build_succeeds params" do + expect(mr_merge_if_green_enabled.merge_when_build_succeeds).to be_falsey + expect(mr_merge_if_green_enabled.merge_params).to eq({}) + expect(mr_merge_if_green_enabled.merge_user).to be nil + end + + it 'Posts a system note' do + note = mr_merge_if_green_enabled.notes.last + expect(note.note).to include 'Canceled the automatic merge' + end + end + describe 'pipeline integration' do context 'when there are multiple stages in the pipeline' do let(:ref) { mr_merge_if_green_enabled.source_branch } let(:sha) { project.commit(ref).id } @@ -139,21 +158,4 @@ describe MergeRequests::MergeWhenBuildSucceedsService do end end end - - describe "#cancel" do - before do - service.cancel(mr_merge_if_green_enabled) - end - - it "resets all the merge_when_build_succeeds params" do - expect(mr_merge_if_green_enabled.merge_when_build_succeeds).to be_falsey - expect(mr_merge_if_green_enabled.merge_params).to eq({}) - expect(mr_merge_if_green_enabled.merge_user).to be nil - end - - it 'Posts a system note' do - note = mr_merge_if_green_enabled.notes.last - expect(note.note).to include 'Canceled the automatic merge' - end - end end -- cgit v1.2.1 From 2b37f040b612303b714ec8cee0b482427e7c2b3c Mon Sep 17 00:00:00 2001 From: James Lopez Date: Tue, 11 Oct 2016 12:58:56 +0200 Subject: Ignore deployment for statistics in Cycle Analytics, except in staging and production stages Also, updated specs and docs. --- CHANGELOG | 1 + app/models/cycle_analytics.rb | 14 +++-- doc/user/project/cycle_analytics.md | 22 ++++---- spec/models/cycle_analytics/code_spec.rb | 88 +++++++++++++++++++++--------- spec/models/cycle_analytics/issue_spec.rb | 2 - spec/models/cycle_analytics/plan_spec.rb | 2 - spec/models/cycle_analytics/review_spec.rb | 4 +- spec/models/cycle_analytics/test_spec.rb | 5 -- 8 files changed, 83 insertions(+), 55 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ba6c3f7c558..576b755d06b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -75,6 +75,7 @@ v 8.13.0 (unreleased) - Replace bootstrap caret with fontawesome caret (ClemMakesApps) - Fix unnecessary escaping of reserved HTML characters in milestone title. !6533 - Add organization field to user profile + - Ignore deployment for statistics in Cycle Analytics, except in staging and production stages - Fix enter key when navigating search site search dropdown. !6643 (Brennan Roberts) - Fix deploy status responsiveness error !6633 - Make searching for commits case insensitive diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb index be295487fd2..0f3fd995681 100644 --- a/app/models/cycle_analytics.rb +++ b/app/models/cycle_analytics.rb @@ -2,6 +2,8 @@ class CycleAnalytics include Gitlab::Database::Median include Gitlab::Database::DateTime + DEPLOYED_CHECK_METRICS = %i[production staging] + def initialize(project, from:) @project = project @from = from @@ -66,7 +68,7 @@ class CycleAnalytics # cycle analytics stage. interval_query = Arel::Nodes::As.new( cte_table, - subtract_datetimes(base_query, end_time_attrs, start_time_attrs, name.to_s)) + subtract_datetimes(base_query_for(name), end_time_attrs, start_time_attrs, name.to_s)) median_datetime(cte_table, interval_query, name) end @@ -75,7 +77,7 @@ class CycleAnalytics # closes the given issue) with issue and merge request metrics included. The metrics # are loaded with an inner join, so issues / merge requests without metrics are # automatically excluded. - def base_query + def base_query_for(name) arel_table = MergeRequestsClosingIssues.arel_table # Load issues @@ -91,7 +93,11 @@ class CycleAnalytics join(MergeRequest::Metrics.arel_table). on(MergeRequest.arel_table[:id].eq(MergeRequest::Metrics.arel_table[:merge_request_id])) - # Limit to merge requests that have been deployed to production after `@from` - query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from)) + if DEPLOYED_CHECK_METRICS.include?(name) + # Limit to merge requests that have been deployed to production after `@from` + query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from)) + end + + query end end diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md index c16058165d7..1892ccabb70 100644 --- a/doc/user/project/cycle_analytics.md +++ b/doc/user/project/cycle_analytics.md @@ -3,8 +3,8 @@ > [Introduced][ce-5986] in GitLab 8.12. > > **Note:** -This the first iteration of Cycle Analytics, you can follow the following issue -to track the changes that are coming to this feature: [#20975][ce-20975]. +There are more changes coming to Cycle Analytics, you can follow the following +issue to track the changes to this feature: [#20975][ce-20975]. Cycle Analytics measures the time it takes to go from an [idea to production] for each project you have. This is achieved by not only indicating the total time it @@ -48,13 +48,12 @@ You can see that there are seven stages in total: ## How the data is measured -Cycle Analytics records cycle time so only data on the issues that have been -deployed to production are measured. In case you just started a new project and -you have not pushed anything to production, then you will not be able to -properly see the Cycle Analytics of your project. +Cycle Analytics records cycle time and data based on the project issues with the +exception of the staging and production stages, where only data deployed to +production are measured. Specifically, if your CI is not set up and you have not defined a `production` -[environment], then you will not have any data. +[environment], then you will not have any data for those stages. Below you can see in more detail what the various stages of Cycle Analytics mean. @@ -76,9 +75,8 @@ Here's a little explanation of how this works behind the scenes: `` pair, the merge request has the [issue closing pattern] for the corresponding issue. All other issues and merge requests are **not** considered. -1. Then the pairs are filtered out. Any merge request - that has **not** been deployed to production in the last XX days (specified - by the UI - default is 90 days) prohibits these pairs from being considered. +1. Then the pairs are filtered out by last XX days (specified + by the UI - default is 90 days). So it prohibits these pairs from being considered. 1. For the remaining `` pairs, we check the information that we need for the stages, like issue creation date, merge request merge time, etc. @@ -86,8 +84,8 @@ Here's a little explanation of how this works behind the scenes: To sum up, anything that doesn't follow the [GitLab flow] won't be tracked at all. So, if a merge request doesn't close an issue or an issue is not labeled with a label present in the Issue Board or assigned a milestone or a project has no -`production` environment, the Cycle Analytics dashboard won't present any data -at all. +`production` environment (for staging and production stages), the Cycle Analytics +dashboard won't present any data at all. ## Example workflow diff --git a/spec/models/cycle_analytics/code_spec.rb b/spec/models/cycle_analytics/code_spec.rb index b9381e33914..7691d690db0 100644 --- a/spec/models/cycle_analytics/code_spec.rb +++ b/spec/models/cycle_analytics/code_spec.rb @@ -8,35 +8,69 @@ describe 'CycleAnalytics#code', feature: true do let(:user) { create(:user, :admin) } subject { CycleAnalytics.new(project, from: from_date) } - generate_cycle_analytics_spec( - phase: :code, - data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } }, - start_time_conditions: [["issue mentioned in a commit", - -> (context, data) do - context.create_commit_referencing_issue(data[:issue]) - end]], - end_time_conditions: [["merge request that closes issue is created", - -> (context, data) do - context.create_merge_request_closing_issue(data[:issue]) - end]], - post_fn: -> (context, data) do - context.merge_merge_requests_closing_issue(data[:issue]) - context.deploy_master - end) - - context "when a regular merge request (that doesn't close the issue) is created" do - it "returns nil" do - 5.times do - issue = create(:issue, project: project) - - create_commit_referencing_issue(issue) - create_merge_request_closing_issue(issue, message: "Closes nothing") - - merge_merge_requests_closing_issue(issue) - deploy_master + context 'with deployment' do + generate_cycle_analytics_spec( + phase: :code, + data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } }, + start_time_conditions: [["issue mentioned in a commit", + -> (context, data) do + context.create_commit_referencing_issue(data[:issue]) + end]], + end_time_conditions: [["merge request that closes issue is created", + -> (context, data) do + context.create_merge_request_closing_issue(data[:issue]) + end]], + post_fn: -> (context, data) do + context.merge_merge_requests_closing_issue(data[:issue]) + context.deploy_master + end) + + context "when a regular merge request (that doesn't close the issue) is created" do + it "returns nil" do + 5.times do + issue = create(:issue, project: project) + + create_commit_referencing_issue(issue) + create_merge_request_closing_issue(issue, message: "Closes nothing") + + merge_merge_requests_closing_issue(issue) + deploy_master + end + + expect(subject.code).to be_nil end + end + end - expect(subject.code).to be_nil + context 'without deployment' do + generate_cycle_analytics_spec( + phase: :code, + data_fn: -> (context) { { issue: context.create(:issue, project: context.project) } }, + start_time_conditions: [["issue mentioned in a commit", + -> (context, data) do + context.create_commit_referencing_issue(data[:issue]) + end]], + end_time_conditions: [["merge request that closes issue is created", + -> (context, data) do + context.create_merge_request_closing_issue(data[:issue]) + end]], + post_fn: -> (context, data) do + context.merge_merge_requests_closing_issue(data[:issue]) + end) + + context "when a regular merge request (that doesn't close the issue) is created" do + it "returns nil" do + 5.times do + issue = create(:issue, project: project) + + create_commit_referencing_issue(issue) + create_merge_request_closing_issue(issue, message: "Closes nothing") + + merge_merge_requests_closing_issue(issue) + end + + expect(subject.code).to be_nil + end end end end diff --git a/spec/models/cycle_analytics/issue_spec.rb b/spec/models/cycle_analytics/issue_spec.rb index e9cc71254ab..f649b44d367 100644 --- a/spec/models/cycle_analytics/issue_spec.rb +++ b/spec/models/cycle_analytics/issue_spec.rb @@ -28,7 +28,6 @@ describe 'CycleAnalytics#issue', models: true do if data[:issue].persisted? context.create_merge_request_closing_issue(data[:issue].reload) context.merge_merge_requests_closing_issue(data[:issue]) - context.deploy_master end end) @@ -41,7 +40,6 @@ describe 'CycleAnalytics#issue', models: true do create_merge_request_closing_issue(issue) merge_merge_requests_closing_issue(issue) - deploy_master end expect(subject.issue).to be_nil diff --git a/spec/models/cycle_analytics/plan_spec.rb b/spec/models/cycle_analytics/plan_spec.rb index 5b8c96dc992..2cdefbeef21 100644 --- a/spec/models/cycle_analytics/plan_spec.rb +++ b/spec/models/cycle_analytics/plan_spec.rb @@ -31,7 +31,6 @@ describe 'CycleAnalytics#plan', feature: true do post_fn: -> (context, data) do context.create_merge_request_closing_issue(data[:issue], source_branch: data[:branch_name]) context.merge_merge_requests_closing_issue(data[:issue]) - context.deploy_master end) context "when a regular label (instead of a list label) is added to the issue" do @@ -44,7 +43,6 @@ describe 'CycleAnalytics#plan', feature: true do create_merge_request_closing_issue(issue, source_branch: branch_name) merge_merge_requests_closing_issue(issue) - deploy_master expect(subject.issue).to be_nil end diff --git a/spec/models/cycle_analytics/review_spec.rb b/spec/models/cycle_analytics/review_spec.rb index b6e26d8f261..0ed080a42b1 100644 --- a/spec/models/cycle_analytics/review_spec.rb +++ b/spec/models/cycle_analytics/review_spec.rb @@ -19,14 +19,12 @@ describe 'CycleAnalytics#review', feature: true do -> (context, data) do context.merge_merge_requests_closing_issue(data[:issue]) end]], - post_fn: -> (context, data) { context.deploy_master }) + post_fn: nil) context "when a regular merge request (that doesn't close the issue) is created and merged" do it "returns nil" do 5.times do MergeRequests::MergeService.new(project, user).execute(create(:merge_request)) - - deploy_master end expect(subject.review).to be_nil diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index 89ace0b2742..c454e3e0701 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -20,7 +20,6 @@ describe 'CycleAnalytics#test', feature: true do end_time_conditions: [["pipeline is finished", -> (context, data) { data[:pipeline].succeed! }]], post_fn: -> (context, data) do context.merge_merge_requests_closing_issue(data[:issue]) - context.deploy_master end) context "when the pipeline is for a regular merge request (that doesn't close an issue)" do @@ -34,7 +33,6 @@ describe 'CycleAnalytics#test', feature: true do pipeline.succeed! merge_merge_requests_closing_issue(issue) - deploy_master end expect(subject.test).to be_nil @@ -49,7 +47,6 @@ describe 'CycleAnalytics#test', feature: true do pipeline.run! pipeline.succeed! - deploy_master end expect(subject.test).to be_nil @@ -67,7 +64,6 @@ describe 'CycleAnalytics#test', feature: true do pipeline.drop! merge_merge_requests_closing_issue(issue) - deploy_master end expect(subject.test).to be_nil @@ -85,7 +81,6 @@ describe 'CycleAnalytics#test', feature: true do pipeline.cancel! merge_merge_requests_closing_issue(issue) - deploy_master end expect(subject.test).to be_nil -- cgit v1.2.1 From f76f569bb87803b14c763427fc2875cca7dda892 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 12 Oct 2016 12:50:47 +0100 Subject: Moved data attribute values into helper method --- app/helpers/boards_helper.rb | 12 ++++++++++++ app/views/projects/boards/index.html.haml | 6 +----- app/views/projects/boards/show.html.haml | 6 +----- 3 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 app/helpers/boards_helper.rb diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb new file mode 100644 index 00000000000..b7247ffa8b2 --- /dev/null +++ b/app/helpers/boards_helper.rb @@ -0,0 +1,12 @@ +module BoardsHelper + def board_data + board = @board || @boards.first + + { + endpoint: namespace_project_boards_path(@project.namespace, @project), + board_id: board.id, + disabled: !can?(current_user, :admin_list, @project), + issue_link_base: namespace_project_issues_path(@project.namespace, @project) + } + end +end diff --git a/app/views/projects/boards/index.html.haml b/app/views/projects/boards/index.html.haml index 9252bbc08c1..885f8e34b55 100644 --- a/app/views/projects/boards/index.html.haml +++ b/app/views/projects/boards/index.html.haml @@ -10,11 +10,7 @@ = render 'shared/issuable/filter', type: :boards -.boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", - "data-board-id" => "#{@boards.first.id}", - "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", - "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } +.boards-list#board-app{ "v-cloak" => true, data: board_data } .boards-app-loading.text-center{ "v-if" => "loading" } = icon("spinner spin") = render "projects/boards/components/board" diff --git a/app/views/projects/boards/show.html.haml b/app/views/projects/boards/show.html.haml index d6e1ac5f3cf..885f8e34b55 100644 --- a/app/views/projects/boards/show.html.haml +++ b/app/views/projects/boards/show.html.haml @@ -10,11 +10,7 @@ = render 'shared/issuable/filter', type: :boards -.boards-list#board-app{ "v-cloak" => true, - "data-endpoint" => "#{namespace_project_boards_path(@project.namespace, @project)}", - "data-board-id" => "#{@board.id}", - "data-disabled" => "#{!can?(current_user, :admin_list, @project)}", - "data-issue-link-base" => "#{namespace_project_issues_path(@project.namespace, @project)}" } +.boards-list#board-app{ "v-cloak" => true, data: board_data } .boards-app-loading.text-center{ "v-if" => "loading" } = icon("spinner spin") = render "projects/boards/components/board" -- cgit v1.2.1 From 42ba19f339a63a1e49a40fd4df9c75470ec71f25 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 12 Oct 2016 14:30:49 +0200 Subject: fixed newline --- spec/models/cycle_analytics/test_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/models/cycle_analytics/test_spec.rb b/spec/models/cycle_analytics/test_spec.rb index c454e3e0701..02ddfeed9c1 100644 --- a/spec/models/cycle_analytics/test_spec.rb +++ b/spec/models/cycle_analytics/test_spec.rb @@ -46,7 +46,6 @@ describe 'CycleAnalytics#test', feature: true do pipeline.run! pipeline.succeed! - end expect(subject.test).to be_nil -- cgit v1.2.1 From 1102659262749ca184af38aa9ad6fba8b562d51f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 11 Oct 2016 14:39:18 -0700 Subject: Add a bundle check step to ensure dependencies are correct This should help prevent merge issues in the future, which caused !6814 to be needed. --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cb6f691058e..05687d22b68 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -263,6 +263,7 @@ bundler:audit: only: - master script: + - bundle check - "bundle exec bundle-audit check --update --ignore OSVDB-115941" migration paths: -- cgit v1.2.1 From 916210a0d4fb658a190a6da1dad82e8482741148 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 12 Oct 2016 05:49:10 -0700 Subject: Add a separate stage for bundle check --- .gitlab-ci.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 05687d22b68..4130aa4b6e8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -257,13 +257,18 @@ lint-doc: script: - scripts/lint-doc.sh +bundler:check: + stage: test + <<: *ruby-static-analysis + script: + - bundle check + bundler:audit: stage: test <<: *ruby-static-analysis only: - master script: - - bundle check - "bundle exec bundle-audit check --update --ignore OSVDB-115941" migration paths: -- cgit v1.2.1 From 254c8200ef185ad1c45db50c0141785bbb8238ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 12 Oct 2016 14:51:29 +0200 Subject: Use activerecord_sane_schema_dumper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- Gemfile | 2 + Gemfile.lock | 3 + db/schema.rb | 1174 +++++++++++++++++++++++++++++----------------------------- 3 files changed, 592 insertions(+), 587 deletions(-) diff --git a/Gemfile b/Gemfile index c5f1ce26daf..9d98a34a0d5 100644 --- a/Gemfile +++ b/Gemfile @@ -262,6 +262,8 @@ group :development do # thin instead webrick gem 'thin', '~> 1.7.0' + + gem 'activerecord_sane_schema_dumper', '0.2' end group :development, :test do diff --git a/Gemfile.lock b/Gemfile.lock index 2feec4c4eb5..69804c8c533 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -38,6 +38,8 @@ GEM multi_json (~> 1.11, >= 1.11.2) rack (>= 1.5.2, < 3) railties (>= 4.0, < 5.1) + activerecord_sane_schema_dumper (0.2) + rails (>= 4, < 5) activesupport (4.2.7.1) i18n (~> 0.7) json (~> 1.7, >= 1.7.7) @@ -805,6 +807,7 @@ DEPENDENCIES RedCloth (~> 4.3.2) ace-rails-ap (~> 4.1.0) activerecord-session_store (~> 1.0.0) + activerecord_sane_schema_dumper (= 0.2) acts-as-taggable-on (~> 4.0) addressable (~> 2.3.8) after_commit_queue (~> 1.3.0) diff --git a/db/schema.rb b/db/schema.rb index c5ddf9eee32..a362fd8f228 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -18,94 +18,94 @@ ActiveRecord::Schema.define(version: 20161007133303) do enable_extension "pg_trgm" create_table "abuse_reports", force: :cascade do |t| - t.integer "reporter_id" - t.integer "user_id" - t.text "message" + t.integer "reporter_id" + t.integer "user_id" + t.text "message" t.datetime "created_at" t.datetime "updated_at" - t.text "message_html" + t.text "message_html" end create_table "appearances", force: :cascade do |t| - t.string "title" - t.text "description" - t.string "header_logo" - t.string "logo" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.text "description_html" + t.string "title" + t.text "description" + t.string "header_logo" + t.string "logo" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "description_html" end create_table "application_settings", force: :cascade do |t| - t.integer "default_projects_limit" - t.boolean "signup_enabled" - t.boolean "signin_enabled" - t.boolean "gravatar_enabled" - t.text "sign_in_text" + t.integer "default_projects_limit" + t.boolean "signup_enabled" + t.boolean "signin_enabled" + t.boolean "gravatar_enabled" + t.text "sign_in_text" t.datetime "created_at" t.datetime "updated_at" - t.string "home_page_url" - t.integer "default_branch_protection", default: 2 - t.text "restricted_visibility_levels" - t.boolean "version_check_enabled", default: true - t.integer "max_attachment_size", default: 10, null: false - t.integer "default_project_visibility" - t.integer "default_snippet_visibility" - t.text "domain_whitelist" - t.boolean "user_oauth_applications", default: true - t.string "after_sign_out_path" - t.integer "session_expire_delay", default: 10080, null: false - t.text "import_sources" - t.text "help_page_text" - t.string "admin_notification_email" - t.boolean "shared_runners_enabled", default: true, null: false - t.integer "max_artifacts_size", default: 100, null: false - t.string "runners_registration_token" - t.boolean "require_two_factor_authentication", default: false - t.integer "two_factor_grace_period", default: 48 - t.boolean "metrics_enabled", default: false - t.string "metrics_host", default: "localhost" - t.integer "metrics_pool_size", default: 16 - t.integer "metrics_timeout", default: 10 - t.integer "metrics_method_call_threshold", default: 10 - t.boolean "recaptcha_enabled", default: false - t.string "recaptcha_site_key" - t.string "recaptcha_private_key" - t.integer "metrics_port", default: 8089 - t.boolean "akismet_enabled", default: false - t.string "akismet_api_key" - t.integer "metrics_sample_interval", default: 15 - t.boolean "sentry_enabled", default: false - t.string "sentry_dsn" - t.boolean "email_author_in_body", default: false - t.integer "default_group_visibility" - t.boolean "repository_checks_enabled", default: false - t.text "shared_runners_text" - t.integer "metrics_packet_size", default: 1 - t.text "disabled_oauth_sign_in_sources" - t.string "health_check_access_token" - t.boolean "send_user_confirmation_email", default: false - t.integer "container_registry_token_expire_delay", default: 5 - t.text "after_sign_up_text" - t.boolean "user_default_external", default: false, null: false - t.string "repository_storage", default: "default" - t.string "enabled_git_access_protocol" - t.boolean "domain_blacklist_enabled", default: false - t.text "domain_blacklist" - t.boolean "koding_enabled" - t.string "koding_url" - t.text "sign_in_text_html" - t.text "help_page_text_html" - t.text "shared_runners_text_html" - t.text "after_sign_up_text_html" + t.string "home_page_url" + t.integer "default_branch_protection", default: 2 + t.text "restricted_visibility_levels" + t.boolean "version_check_enabled", default: true + t.integer "max_attachment_size", default: 10, null: false + t.integer "default_project_visibility" + t.integer "default_snippet_visibility" + t.text "domain_whitelist" + t.boolean "user_oauth_applications", default: true + t.string "after_sign_out_path" + t.integer "session_expire_delay", default: 10080, null: false + t.text "import_sources" + t.text "help_page_text" + t.string "admin_notification_email" + t.boolean "shared_runners_enabled", default: true, null: false + t.integer "max_artifacts_size", default: 100, null: false + t.string "runners_registration_token" + t.boolean "require_two_factor_authentication", default: false + t.integer "two_factor_grace_period", default: 48 + t.boolean "metrics_enabled", default: false + t.string "metrics_host", default: "localhost" + t.integer "metrics_pool_size", default: 16 + t.integer "metrics_timeout", default: 10 + t.integer "metrics_method_call_threshold", default: 10 + t.boolean "recaptcha_enabled", default: false + t.string "recaptcha_site_key" + t.string "recaptcha_private_key" + t.integer "metrics_port", default: 8089 + t.boolean "akismet_enabled", default: false + t.string "akismet_api_key" + t.integer "metrics_sample_interval", default: 15 + t.boolean "sentry_enabled", default: false + t.string "sentry_dsn" + t.boolean "email_author_in_body", default: false + t.integer "default_group_visibility" + t.boolean "repository_checks_enabled", default: false + t.text "shared_runners_text" + t.integer "metrics_packet_size", default: 1 + t.text "disabled_oauth_sign_in_sources" + t.string "health_check_access_token" + t.boolean "send_user_confirmation_email", default: false + t.integer "container_registry_token_expire_delay", default: 5 + t.text "after_sign_up_text" + t.boolean "user_default_external", default: false, null: false + t.string "repository_storage", default: "default" + t.string "enabled_git_access_protocol" + t.boolean "domain_blacklist_enabled", default: false + t.text "domain_blacklist" + t.boolean "koding_enabled" + t.string "koding_url" + t.text "sign_in_text_html" + t.text "help_page_text_html" + t.text "shared_runners_text_html" + t.text "after_sign_up_text_html" end create_table "audit_events", force: :cascade do |t| - t.integer "author_id", null: false - t.string "type", null: false - t.integer "entity_id", null: false - t.string "entity_type", null: false - t.text "details" + t.integer "author_id", null: false + t.string "type", null: false + t.integer "entity_id", null: false + t.string "entity_type", null: false + t.text "details" t.datetime "created_at" t.datetime "updated_at" end @@ -113,10 +113,10 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree create_table "award_emoji", force: :cascade do |t| - t.string "name" - t.integer "user_id" - t.integer "awardable_id" - t.string "awardable_type" + t.string "name" + t.integer "user_id" + t.integer "awardable_id" + t.string "awardable_type" t.datetime "created_at" t.datetime "updated_at" end @@ -126,7 +126,7 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "award_emoji", ["user_id"], name: "index_award_emoji_on_user_id", using: :btree create_table "boards", force: :cascade do |t| - t.integer "project_id", null: false + t.integer "project_id", null: false t.datetime "created_at", null: false t.datetime "updated_at", null: false end @@ -134,61 +134,61 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "boards", ["project_id"], name: "index_boards_on_project_id", using: :btree create_table "broadcast_messages", force: :cascade do |t| - t.text "message", null: false + t.text "message", null: false t.datetime "starts_at" t.datetime "ends_at" t.datetime "created_at" t.datetime "updated_at" - t.string "color" - t.string "font" - t.text "message_html" + t.string "color" + t.string "font" + t.text "message_html" end create_table "ci_application_settings", force: :cascade do |t| - t.boolean "all_broken_builds" - t.boolean "add_pusher" + t.boolean "all_broken_builds" + t.boolean "add_pusher" t.datetime "created_at" t.datetime "updated_at" end create_table "ci_builds", force: :cascade do |t| - t.integer "project_id" - t.string "status" + t.integer "project_id" + t.string "status" t.datetime "finished_at" - t.text "trace" + t.text "trace" t.datetime "created_at" t.datetime "updated_at" t.datetime "started_at" - t.integer "runner_id" - t.float "coverage" - t.integer "commit_id" - t.text "commands" - t.integer "job_id" - t.string "name" - t.boolean "deploy", default: false - t.text "options" - t.boolean "allow_failure", default: false, null: false - t.string "stage" - t.integer "trigger_request_id" - t.integer "stage_idx" - t.boolean "tag" - t.string "ref" - t.integer "user_id" - t.string "type" - t.string "target_url" - t.string "description" - t.text "artifacts_file" - t.integer "gl_project_id" - t.text "artifacts_metadata" - t.integer "erased_by_id" + t.integer "runner_id" + t.float "coverage" + t.integer "commit_id" + t.text "commands" + t.integer "job_id" + t.string "name" + t.boolean "deploy", default: false + t.text "options" + t.boolean "allow_failure", default: false, null: false + t.string "stage" + t.integer "trigger_request_id" + t.integer "stage_idx" + t.boolean "tag" + t.string "ref" + t.integer "user_id" + t.string "type" + t.string "target_url" + t.string "description" + t.text "artifacts_file" + t.integer "gl_project_id" + t.text "artifacts_metadata" + t.integer "erased_by_id" t.datetime "erased_at" t.datetime "artifacts_expire_at" - t.string "environment" - t.integer "artifacts_size", limit: 8 - t.string "when" - t.text "yaml_variables" + t.string "environment" + t.integer "artifacts_size", limit: 8 + t.string "when" + t.text "yaml_variables" t.datetime "queued_at" - t.string "token" + t.string "token" end add_index "ci_builds", ["commit_id", "stage_idx", "created_at"], name: "index_ci_builds_on_commit_id_and_stage_idx_and_created_at", using: :btree @@ -203,22 +203,22 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "ci_builds", ["token"], name: "index_ci_builds_on_token", unique: true, using: :btree create_table "ci_commits", force: :cascade do |t| - t.integer "project_id" - t.string "ref" - t.string "sha" - t.string "before_sha" - t.text "push_data" + t.integer "project_id" + t.string "ref" + t.string "sha" + t.string "before_sha" + t.text "push_data" t.datetime "created_at" t.datetime "updated_at" - t.boolean "tag", default: false - t.text "yaml_errors" + t.boolean "tag", default: false + t.text "yaml_errors" t.datetime "committed_at" - t.integer "gl_project_id" - t.string "status" + t.integer "gl_project_id" + t.string "status" t.datetime "started_at" t.datetime "finished_at" - t.integer "duration" - t.integer "user_id" + t.integer "duration" + t.integer "user_id" end add_index "ci_commits", ["gl_project_id", "sha"], name: "index_ci_commits_on_gl_project_id_and_sha", using: :btree @@ -228,140 +228,140 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "ci_commits", ["user_id"], name: "index_ci_commits_on_user_id", using: :btree create_table "ci_events", force: :cascade do |t| - t.integer "project_id" - t.integer "user_id" - t.integer "is_admin" - t.text "description" + t.integer "project_id" + t.integer "user_id" + t.integer "is_admin" + t.text "description" t.datetime "created_at" t.datetime "updated_at" end create_table "ci_jobs", force: :cascade do |t| - t.integer "project_id", null: false - t.text "commands" - t.boolean "active", default: true, null: false + t.integer "project_id", null: false + t.text "commands" + t.boolean "active", default: true, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "name" - t.boolean "build_branches", default: true, null: false - t.boolean "build_tags", default: false, null: false - t.string "job_type", default: "parallel" - t.string "refs" + t.string "name" + t.boolean "build_branches", default: true, null: false + t.boolean "build_tags", default: false, null: false + t.string "job_type", default: "parallel" + t.string "refs" t.datetime "deleted_at" end create_table "ci_projects", force: :cascade do |t| - t.string "name" - t.integer "timeout", default: 3600, null: false + t.string "name" + t.integer "timeout", default: 3600, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "token" - t.string "default_ref" - t.string "path" - t.boolean "always_build", default: false, null: false - t.integer "polling_interval" - t.boolean "public", default: false, null: false - t.string "ssh_url_to_repo" - t.integer "gitlab_id" - t.boolean "allow_git_fetch", default: true, null: false - t.string "email_recipients", default: "", null: false - t.boolean "email_add_pusher", default: true, null: false - t.boolean "email_only_broken_builds", default: true, null: false - t.string "skip_refs" - t.string "coverage_regex" - t.boolean "shared_runners_enabled", default: false - t.text "generated_yaml_config" + t.string "token" + t.string "default_ref" + t.string "path" + t.boolean "always_build", default: false, null: false + t.integer "polling_interval" + t.boolean "public", default: false, null: false + t.string "ssh_url_to_repo" + t.integer "gitlab_id" + t.boolean "allow_git_fetch", default: true, null: false + t.string "email_recipients", default: "", null: false + t.boolean "email_add_pusher", default: true, null: false + t.boolean "email_only_broken_builds", default: true, null: false + t.string "skip_refs" + t.string "coverage_regex" + t.boolean "shared_runners_enabled", default: false + t.text "generated_yaml_config" end create_table "ci_runner_projects", force: :cascade do |t| - t.integer "runner_id", null: false - t.integer "project_id" + t.integer "runner_id", null: false + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "gl_project_id" + t.integer "gl_project_id" end add_index "ci_runner_projects", ["gl_project_id"], name: "index_ci_runner_projects_on_gl_project_id", using: :btree add_index "ci_runner_projects", ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree create_table "ci_runners", force: :cascade do |t| - t.string "token" + t.string "token" t.datetime "created_at" t.datetime "updated_at" - t.string "description" + t.string "description" t.datetime "contacted_at" - t.boolean "active", default: true, null: false - t.boolean "is_shared", default: false - t.string "name" - t.string "version" - t.string "revision" - t.string "platform" - t.string "architecture" - t.boolean "run_untagged", default: true, null: false - t.boolean "locked", default: false, null: false + t.boolean "active", default: true, null: false + t.boolean "is_shared", default: false + t.string "name" + t.string "version" + t.string "revision" + t.string "platform" + t.string "architecture" + t.boolean "run_untagged", default: true, null: false + t.boolean "locked", default: false, null: false end add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree create_table "ci_sessions", force: :cascade do |t| - t.string "session_id", null: false - t.text "data" + t.string "session_id", null: false + t.text "data" t.datetime "created_at" t.datetime "updated_at" end create_table "ci_taggings", force: :cascade do |t| - t.integer "tag_id" - t.integer "taggable_id" - t.string "taggable_type" - t.integer "tagger_id" - t.string "tagger_type" - t.string "context", limit: 128 + t.integer "tag_id" + t.integer "taggable_id" + t.string "taggable_type" + t.integer "tagger_id" + t.string "tagger_type" + t.string "context", limit: 128 t.datetime "created_at" end add_index "ci_taggings", ["taggable_id", "taggable_type", "context"], name: "index_ci_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "ci_tags", force: :cascade do |t| - t.string "name" + t.string "name" t.integer "taggings_count", default: 0 end create_table "ci_trigger_requests", force: :cascade do |t| - t.integer "trigger_id", null: false - t.text "variables" + t.integer "trigger_id", null: false + t.text "variables" t.datetime "created_at" t.datetime "updated_at" - t.integer "commit_id" + t.integer "commit_id" end create_table "ci_triggers", force: :cascade do |t| - t.string "token" - t.integer "project_id" + t.string "token" + t.integer "project_id" t.datetime "deleted_at" t.datetime "created_at" t.datetime "updated_at" - t.integer "gl_project_id" + t.integer "gl_project_id" end add_index "ci_triggers", ["gl_project_id"], name: "index_ci_triggers_on_gl_project_id", using: :btree create_table "ci_variables", force: :cascade do |t| t.integer "project_id" - t.string "key" - t.text "value" - t.text "encrypted_value" - t.string "encrypted_value_salt" - t.string "encrypted_value_iv" + t.string "key" + t.text "value" + t.text "encrypted_value" + t.string "encrypted_value_salt" + t.string "encrypted_value_iv" t.integer "gl_project_id" end add_index "ci_variables", ["gl_project_id"], name: "index_ci_variables_on_gl_project_id", using: :btree create_table "deploy_keys_projects", force: :cascade do |t| - t.integer "deploy_key_id", null: false - t.integer "project_id", null: false + t.integer "deploy_key_id", null: false + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -369,15 +369,15 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree create_table "deployments", force: :cascade do |t| - t.integer "iid", null: false - t.integer "project_id", null: false - t.integer "environment_id", null: false - t.string "ref", null: false - t.boolean "tag", null: false - t.string "sha", null: false - t.integer "user_id" - t.integer "deployable_id" - t.string "deployable_type" + t.integer "iid", null: false + t.integer "project_id", null: false + t.integer "environment_id", null: false + t.string "ref", null: false + t.boolean "tag", null: false + t.string "sha", null: false + t.integer "user_id" + t.integer "deployable_id" + t.string "deployable_type" t.datetime "created_at" t.datetime "updated_at" end @@ -388,8 +388,8 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "deployments", ["project_id"], name: "index_deployments_on_project_id", using: :btree create_table "emails", force: :cascade do |t| - t.integer "user_id", null: false - t.string "email", null: false + t.integer "user_id", null: false + t.string "email", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -398,26 +398,26 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree create_table "environments", force: :cascade do |t| - t.integer "project_id" - t.string "name", null: false + t.integer "project_id" + t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" - t.string "external_url" - t.string "environment_type" + t.string "external_url" + t.string "environment_type" end add_index "environments", ["project_id", "name"], name: "index_environments_on_project_id_and_name", using: :btree create_table "events", force: :cascade do |t| - t.string "target_type" - t.integer "target_id" - t.string "title" - t.text "data" - t.integer "project_id" + t.string "target_type" + t.integer "target_id" + t.string "title" + t.text "data" + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "action" - t.integer "author_id" + t.integer "action" + t.integer "author_id" end add_index "events", ["action"], name: "index_events_on_action", using: :btree @@ -428,8 +428,8 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "events", ["target_type"], name: "index_events_on_target_type", using: :btree create_table "forked_project_links", force: :cascade do |t| - t.integer "forked_to_project_id", null: false - t.integer "forked_from_project_id", null: false + t.integer "forked_to_project_id", null: false + t.integer "forked_from_project_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -437,9 +437,9 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree create_table "identities", force: :cascade do |t| - t.string "extern_uid" - t.string "provider" - t.integer "user_id" + t.string "extern_uid" + t.string "provider" + t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" end @@ -447,37 +447,37 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree create_table "issue_metrics", force: :cascade do |t| - t.integer "issue_id", null: false + t.integer "issue_id", null: false t.datetime "first_mentioned_in_commit_at" t.datetime "first_associated_with_milestone_at" t.datetime "first_added_to_board_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "issue_metrics", ["issue_id"], name: "index_issue_metrics", using: :btree create_table "issues", force: :cascade do |t| - t.string "title" - t.integer "assignee_id" - t.integer "author_id" - t.integer "project_id" + t.string "title" + t.integer "assignee_id" + t.integer "author_id" + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "position", default: 0 - t.string "branch_name" - t.text "description" - t.integer "milestone_id" - t.string "state" - t.integer "iid" - t.integer "updated_by_id" - t.boolean "confidential", default: false + t.integer "position", default: 0 + t.string "branch_name" + t.text "description" + t.integer "milestone_id" + t.string "state" + t.integer "iid" + t.integer "updated_by_id" + t.boolean "confidential", default: false t.datetime "deleted_at" - t.date "due_date" - t.integer "moved_to_id" - t.integer "lock_version" - t.text "title_html" - t.text "description_html" + t.date "due_date" + t.integer "moved_to_id" + t.integer "lock_version" + t.text "title_html" + t.text "description_html" end add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree @@ -493,23 +493,23 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "issues", ["title"], name: "index_issues_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "keys", force: :cascade do |t| - t.integer "user_id" + t.integer "user_id" t.datetime "created_at" t.datetime "updated_at" - t.text "key" - t.string "title" - t.string "type" - t.string "fingerprint" - t.boolean "public", default: false, null: false + t.text "key" + t.string "title" + t.string "type" + t.string "fingerprint" + t.boolean "public", default: false, null: false end add_index "keys", ["fingerprint"], name: "index_keys_on_fingerprint", unique: true, using: :btree add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree create_table "label_links", force: :cascade do |t| - t.integer "label_id" - t.integer "target_id" - t.string "target_type" + t.integer "label_id" + t.integer "target_id" + t.string "target_type" t.datetime "created_at" t.datetime "updated_at" end @@ -518,15 +518,15 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "label_links", ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree create_table "labels", force: :cascade do |t| - t.string "title" - t.string "color" - t.integer "project_id" + t.string "title" + t.string "color" + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "template", default: false - t.string "description" - t.integer "priority" - t.text "description_html" + t.boolean "template", default: false + t.string "description" + t.integer "priority" + t.text "description_html" end add_index "labels", ["priority"], name: "index_labels_on_priority", using: :btree @@ -534,18 +534,18 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "labels", ["title"], name: "index_labels_on_title", using: :btree create_table "lfs_objects", force: :cascade do |t| - t.string "oid", null: false - t.integer "size", limit: 8, null: false + t.string "oid", null: false + t.integer "size", limit: 8, null: false t.datetime "created_at" t.datetime "updated_at" - t.string "file" + t.string "file" end add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree create_table "lfs_objects_projects", force: :cascade do |t| - t.integer "lfs_object_id", null: false - t.integer "project_id", null: false + t.integer "lfs_object_id", null: false + t.integer "project_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -553,12 +553,12 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree create_table "lists", force: :cascade do |t| - t.integer "board_id", null: false - t.integer "label_id" - t.integer "list_type", default: 1, null: false - t.integer "position" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "board_id", null: false + t.integer "label_id" + t.integer "list_type", default: 1, null: false + t.integer "position" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "lists", ["board_id", "label_id"], name: "index_lists_on_board_id_and_label_id", unique: true, using: :btree @@ -566,20 +566,20 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "lists", ["label_id"], name: "index_lists_on_label_id", using: :btree create_table "members", force: :cascade do |t| - t.integer "access_level", null: false - t.integer "source_id", null: false - t.string "source_type", null: false - t.integer "user_id" - t.integer "notification_level", null: false - t.string "type" + t.integer "access_level", null: false + t.integer "source_id", null: false + t.string "source_type", null: false + t.integer "user_id" + t.integer "notification_level", null: false + t.string "type" t.datetime "created_at" t.datetime "updated_at" - t.integer "created_by_id" - t.string "invite_email" - t.string "invite_token" + t.integer "created_by_id" + t.string "invite_email" + t.string "invite_token" t.datetime "invite_accepted_at" t.datetime "requested_at" - t.date "expires_at" + t.date "expires_at" end add_index "members", ["access_level"], name: "index_members_on_access_level", using: :btree @@ -589,61 +589,61 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree create_table "merge_request_diffs", force: :cascade do |t| - t.string "state" - t.text "st_commits" - t.text "st_diffs" - t.integer "merge_request_id", null: false + t.string "state" + t.text "st_commits" + t.text "st_diffs" + t.integer "merge_request_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.string "base_commit_sha" - t.string "real_size" - t.string "head_commit_sha" - t.string "start_commit_sha" + t.string "base_commit_sha" + t.string "real_size" + t.string "head_commit_sha" + t.string "start_commit_sha" end add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", using: :btree create_table "merge_request_metrics", force: :cascade do |t| - t.integer "merge_request_id", null: false + t.integer "merge_request_id", null: false t.datetime "latest_build_started_at" t.datetime "latest_build_finished_at" t.datetime "first_deployed_to_production_at" t.datetime "merged_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "merge_request_metrics", ["first_deployed_to_production_at"], name: "index_merge_request_metrics_on_first_deployed_to_production_at", using: :btree add_index "merge_request_metrics", ["merge_request_id"], name: "index_merge_request_metrics", using: :btree create_table "merge_requests", force: :cascade do |t| - t.string "target_branch", null: false - t.string "source_branch", null: false - t.integer "source_project_id", null: false - t.integer "author_id" - t.integer "assignee_id" - t.string "title" + t.string "target_branch", null: false + t.string "source_branch", null: false + t.integer "source_project_id", null: false + t.integer "author_id" + t.integer "assignee_id" + t.string "title" t.datetime "created_at" t.datetime "updated_at" - t.integer "milestone_id" - t.string "state" - t.string "merge_status" - t.integer "target_project_id", null: false - t.integer "iid" - t.text "description" - t.integer "position", default: 0 + t.integer "milestone_id" + t.string "state" + t.string "merge_status" + t.integer "target_project_id", null: false + t.integer "iid" + t.text "description" + t.integer "position", default: 0 t.datetime "locked_at" - t.integer "updated_by_id" - t.text "merge_error" - t.text "merge_params" - t.boolean "merge_when_build_succeeds", default: false, null: false - t.integer "merge_user_id" - t.string "merge_commit_sha" + t.integer "updated_by_id" + t.text "merge_error" + t.text "merge_params" + t.boolean "merge_when_build_succeeds", default: false, null: false + t.integer "merge_user_id" + t.string "merge_commit_sha" t.datetime "deleted_at" - t.string "in_progress_merge_commit_sha" - t.integer "lock_version" - t.text "title_html" - t.text "description_html" + t.string "in_progress_merge_commit_sha" + t.integer "lock_version" + t.text "title_html" + t.text "description_html" end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree @@ -660,26 +660,26 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "merge_requests", ["title"], name: "index_merge_requests_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "merge_requests_closing_issues", force: :cascade do |t| - t.integer "merge_request_id", null: false - t.integer "issue_id", null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.integer "merge_request_id", null: false + t.integer "issue_id", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "merge_requests_closing_issues", ["issue_id"], name: "index_merge_requests_closing_issues_on_issue_id", using: :btree add_index "merge_requests_closing_issues", ["merge_request_id"], name: "index_merge_requests_closing_issues_on_merge_request_id", using: :btree create_table "milestones", force: :cascade do |t| - t.string "title", null: false - t.integer "project_id", null: false - t.text "description" - t.date "due_date" + t.string "title", null: false + t.integer "project_id", null: false + t.text "description" + t.date "due_date" t.datetime "created_at" t.datetime "updated_at" - t.string "state" - t.integer "iid" - t.text "title_html" - t.text "description_html" + t.string "state" + t.integer "iid" + t.text "title_html" + t.text "description_html" end add_index "milestones", ["description"], name: "index_milestones_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} @@ -690,20 +690,20 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "milestones", ["title"], name: "index_milestones_on_title_trigram", using: :gin, opclasses: {"title"=>"gin_trgm_ops"} create_table "namespaces", force: :cascade do |t| - t.string "name", null: false - t.string "path", null: false - t.integer "owner_id" + t.string "name", null: false + t.string "path", null: false + t.integer "owner_id" t.datetime "created_at" t.datetime "updated_at" - t.string "type" - t.string "description", default: "", null: false - t.string "avatar" - t.boolean "share_with_group_lock", default: false - t.integer "visibility_level", default: 20, null: false - t.boolean "request_access_enabled", default: true, null: false + t.string "type" + t.string "description", default: "", null: false + t.string "avatar" + t.boolean "share_with_group_lock", default: false + t.integer "visibility_level", default: 20, null: false + t.boolean "request_access_enabled", default: true, null: false t.datetime "deleted_at" - t.boolean "lfs_enabled" - t.text "description_html" + t.boolean "lfs_enabled" + t.text "description_html" end add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree @@ -716,27 +716,27 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree create_table "notes", force: :cascade do |t| - t.text "note" - t.string "noteable_type" - t.integer "author_id" + t.text "note" + t.string "noteable_type" + t.integer "author_id" t.datetime "created_at" t.datetime "updated_at" - t.integer "project_id" - t.string "attachment" - t.string "line_code" - t.string "commit_id" - t.integer "noteable_id" - t.boolean "system", default: false, null: false - t.text "st_diff" - t.integer "updated_by_id" - t.string "type" - t.text "position" - t.text "original_position" + t.integer "project_id" + t.string "attachment" + t.string "line_code" + t.string "commit_id" + t.integer "noteable_id" + t.boolean "system", default: false, null: false + t.text "st_diff" + t.integer "updated_by_id" + t.string "type" + t.text "position" + t.text "original_position" t.datetime "resolved_at" - t.integer "resolved_by_id" - t.string "discussion_id" - t.string "original_discussion_id" - t.text "note_html" + t.integer "resolved_by_id" + t.string "discussion_id" + t.string "original_discussion_id" + t.text "note_html" end add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree @@ -752,13 +752,13 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree create_table "notification_settings", force: :cascade do |t| - t.integer "user_id", null: false - t.integer "source_id" - t.string "source_type" - t.integer "level", default: 0, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.text "events" + t.integer "user_id", null: false + t.integer "source_id" + t.string "source_type" + t.integer "level", default: 0, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.text "events" end add_index "notification_settings", ["source_id", "source_type"], name: "index_notification_settings_on_source_id_and_source_type", using: :btree @@ -766,27 +766,27 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "notification_settings", ["user_id"], name: "index_notification_settings_on_user_id", using: :btree create_table "oauth_access_grants", force: :cascade do |t| - t.integer "resource_owner_id", null: false - t.integer "application_id", null: false - t.string "token", null: false - t.integer "expires_in", null: false - t.text "redirect_uri", null: false - t.datetime "created_at", null: false + t.integer "resource_owner_id", null: false + t.integer "application_id", null: false + t.string "token", null: false + t.integer "expires_in", null: false + t.text "redirect_uri", null: false + t.datetime "created_at", null: false t.datetime "revoked_at" - t.string "scopes" + t.string "scopes" end add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree create_table "oauth_access_tokens", force: :cascade do |t| - t.integer "resource_owner_id" - t.integer "application_id" - t.string "token", null: false - t.string "refresh_token" - t.integer "expires_in" + t.integer "resource_owner_id" + t.integer "application_id" + t.string "token", null: false + t.string "refresh_token" + t.integer "expires_in" t.datetime "revoked_at" - t.datetime "created_at", null: false - t.string "scopes" + t.datetime "created_at", null: false + t.string "scopes" end add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree @@ -794,40 +794,40 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree create_table "oauth_applications", force: :cascade do |t| - t.string "name", null: false - t.string "uid", null: false - t.string "secret", null: false - t.text "redirect_uri", null: false - t.string "scopes", default: "", null: false + t.string "name", null: false + t.string "uid", null: false + t.string "secret", null: false + t.text "redirect_uri", null: false + t.string "scopes", default: "", null: false t.datetime "created_at" t.datetime "updated_at" - t.integer "owner_id" - t.string "owner_type" + t.integer "owner_id" + t.string "owner_type" end add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree create_table "personal_access_tokens", force: :cascade do |t| - t.integer "user_id", null: false - t.string "token", null: false - t.string "name", null: false - t.boolean "revoked", default: false + t.integer "user_id", null: false + t.string "token", null: false + t.string "name", null: false + t.boolean "revoked", default: false t.datetime "expires_at" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end add_index "personal_access_tokens", ["token"], name: "index_personal_access_tokens_on_token", unique: true, using: :btree add_index "personal_access_tokens", ["user_id"], name: "index_personal_access_tokens_on_user_id", using: :btree create_table "project_features", force: :cascade do |t| - t.integer "project_id" - t.integer "merge_requests_access_level" - t.integer "issues_access_level" - t.integer "wiki_access_level" - t.integer "snippets_access_level" - t.integer "builds_access_level" + t.integer "project_id" + t.integer "merge_requests_access_level" + t.integer "issues_access_level" + t.integer "wiki_access_level" + t.integer "snippets_access_level" + t.integer "builds_access_level" t.datetime "created_at" t.datetime "updated_at" end @@ -835,60 +835,60 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", using: :btree create_table "project_group_links", force: :cascade do |t| - t.integer "project_id", null: false - t.integer "group_id", null: false + t.integer "project_id", null: false + t.integer "group_id", null: false t.datetime "created_at" t.datetime "updated_at" - t.integer "group_access", default: 30, null: false - t.date "expires_at" + t.integer "group_access", default: 30, null: false + t.date "expires_at" end create_table "project_import_data", force: :cascade do |t| t.integer "project_id" - t.text "data" - t.text "encrypted_credentials" - t.string "encrypted_credentials_iv" - t.string "encrypted_credentials_salt" + t.text "data" + t.text "encrypted_credentials" + t.string "encrypted_credentials_iv" + t.string "encrypted_credentials_salt" end create_table "projects", force: :cascade do |t| - t.string "name" - t.string "path" - t.text "description" + t.string "name" + t.string "path" + t.text "description" t.datetime "created_at" t.datetime "updated_at" - t.integer "creator_id" - t.integer "namespace_id" + t.integer "creator_id" + t.integer "namespace_id" t.datetime "last_activity_at" - t.string "import_url" - t.integer "visibility_level", default: 0, null: false - t.boolean "archived", default: false, null: false - t.string "avatar" - t.string "import_status" - t.float "repository_size", default: 0.0 - t.integer "star_count", default: 0, null: false - t.string "import_type" - t.string "import_source" - t.integer "commit_count", default: 0 - t.text "import_error" - t.integer "ci_id" - t.boolean "shared_runners_enabled", default: true, null: false - t.string "runners_token" - t.string "build_coverage_regex" - t.boolean "build_allow_git_fetch", default: true, null: false - t.integer "build_timeout", default: 3600, null: false - t.boolean "pending_delete", default: false - t.boolean "public_builds", default: true, null: false - t.boolean "last_repository_check_failed" + t.string "import_url" + t.integer "visibility_level", default: 0, null: false + t.boolean "archived", default: false, null: false + t.string "avatar" + t.string "import_status" + t.float "repository_size", default: 0.0 + t.integer "star_count", default: 0, null: false + t.string "import_type" + t.string "import_source" + t.integer "commit_count", default: 0 + t.text "import_error" + t.integer "ci_id" + t.boolean "shared_runners_enabled", default: true, null: false + t.string "runners_token" + t.string "build_coverage_regex" + t.boolean "build_allow_git_fetch", default: true, null: false + t.integer "build_timeout", default: 3600, null: false + t.boolean "pending_delete", default: false + t.boolean "public_builds", default: true, null: false + t.boolean "last_repository_check_failed" t.datetime "last_repository_check_at" - t.boolean "container_registry_enabled" - t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false - t.boolean "has_external_issue_tracker" - t.string "repository_storage", default: "default", null: false - t.boolean "request_access_enabled", default: true, null: false - t.boolean "has_external_wiki" - t.boolean "lfs_enabled" - t.text "description_html" + t.boolean "container_registry_enabled" + t.boolean "only_allow_merge_if_build_succeeds", default: false, null: false + t.boolean "has_external_issue_tracker" + t.string "repository_storage", default: "default", null: false + t.boolean "request_access_enabled", default: true, null: false + t.boolean "has_external_wiki" + t.boolean "lfs_enabled" + t.text "description_html" end add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree @@ -907,26 +907,26 @@ ActiveRecord::Schema.define(version: 20161007133303) 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: 40, 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: 40, 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 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" end @@ -934,12 +934,12 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree create_table "releases", force: :cascade do |t| - t.string "tag" - t.text "description" - t.integer "project_id" + t.string "tag" + t.text "description" + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.text "description_html" + t.text "description_html" end add_index "releases", ["project_id", "tag"], name: "index_releases_on_project_id_and_tag", using: :btree @@ -948,54 +948,54 @@ ActiveRecord::Schema.define(version: 20161007133303) do create_table "sent_notifications", force: :cascade do |t| t.integer "project_id" t.integer "noteable_id" - t.string "noteable_type" + t.string "noteable_type" t.integer "recipient_id" - t.string "commit_id" - t.string "reply_key", null: false - t.string "line_code" - t.string "note_type" - t.text "position" + t.string "commit_id" + t.string "reply_key", null: false + t.string "line_code" + t.string "note_type" + t.text "position" end add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree create_table "services", force: :cascade do |t| - t.string "type" - t.string "title" - t.integer "project_id" + t.string "type" + t.string "title" + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "active", default: false, null: false - t.text "properties" - t.boolean "template", default: false - t.boolean "push_events", default: true - t.boolean "issues_events", default: true - t.boolean "merge_requests_events", default: true - t.boolean "tag_push_events", default: true - t.boolean "note_events", default: true, null: false - t.boolean "build_events", default: false, null: false - t.string "category", default: "common", null: false - t.boolean "default", default: false - t.boolean "wiki_page_events", default: true - t.boolean "pipeline_events", default: false, null: false - t.boolean "confidential_issues_events", default: true, null: false + t.boolean "active", default: false, null: false + t.text "properties" + t.boolean "template", default: false + t.boolean "push_events", default: true + t.boolean "issues_events", default: true + t.boolean "merge_requests_events", default: true + t.boolean "tag_push_events", default: true + t.boolean "note_events", default: true, null: false + t.boolean "build_events", default: false, null: false + t.string "category", default: "common", null: false + t.boolean "default", default: false + t.boolean "wiki_page_events", default: true + t.boolean "pipeline_events", default: false, null: false + t.boolean "confidential_issues_events", default: true, null: false end add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree add_index "services", ["template"], name: "index_services_on_template", using: :btree create_table "snippets", force: :cascade do |t| - t.string "title" - t.text "content" - t.integer "author_id", null: false - t.integer "project_id" + t.string "title" + t.text "content" + t.integer "author_id", null: false + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "file_name" - t.string "type" - t.integer "visibility_level", default: 0, null: false - t.text "title_html" - t.text "content_html" + t.string "file_name" + t.string "type" + t.integer "visibility_level", default: 0, null: false + t.text "title_html" + t.text "content_html" end add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree @@ -1006,23 +1006,23 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree create_table "spam_logs", force: :cascade do |t| - t.integer "user_id" - t.string "source_ip" - t.string "user_agent" - t.boolean "via_api" - t.string "noteable_type" - t.string "title" - t.text "description" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.boolean "submitted_as_ham", default: false, null: false + t.integer "user_id" + t.string "source_ip" + t.string "user_agent" + t.boolean "via_api" + t.string "noteable_type" + t.string "title" + t.text "description" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.boolean "submitted_as_ham", default: false, null: false end create_table "subscriptions", force: :cascade do |t| - t.integer "user_id" - t.integer "subscribable_id" - t.string "subscribable_type" - t.boolean "subscribed" + t.integer "user_id" + t.integer "subscribable_id" + t.string "subscribable_type" + t.boolean "subscribed" t.datetime "created_at" t.datetime "updated_at" end @@ -1030,12 +1030,12 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "subscriptions", ["subscribable_id", "subscribable_type", "user_id"], name: "subscriptions_user_id_and_ref_fields", unique: true, using: :btree create_table "taggings", force: :cascade do |t| - t.integer "tag_id" - t.integer "taggable_id" - t.string "taggable_type" - t.integer "tagger_id" - t.string "tagger_type" - t.string "context" + t.integer "tag_id" + t.integer "taggable_id" + t.string "taggable_type" + t.integer "tagger_id" + t.string "tagger_type" + t.string "context" t.datetime "created_at" end @@ -1043,24 +1043,24 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree create_table "tags", force: :cascade do |t| - t.string "name" + t.string "name" t.integer "taggings_count", default: 0 end add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree create_table "todos", force: :cascade do |t| - t.integer "user_id", null: false - t.integer "project_id", null: false - t.integer "target_id" - t.string "target_type", null: false - t.integer "author_id" - t.integer "action", null: false - t.string "state", null: false + t.integer "user_id", null: false + t.integer "project_id", null: false + t.integer "target_id" + t.string "target_type", null: false + t.integer "author_id" + t.integer "action", null: false + t.string "state", null: false t.datetime "created_at" t.datetime "updated_at" - t.integer "note_id" - t.string "commit_id" + t.integer "note_id" + t.string "commit_id" end add_index "todos", ["author_id"], name: "index_todos_on_author_id", using: :btree @@ -1077,88 +1077,88 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "trending_projects", ["project_id"], name: "index_trending_projects_on_project_id", using: :btree create_table "u2f_registrations", force: :cascade do |t| - t.text "certificate" - t.string "key_handle" - t.string "public_key" - t.integer "counter" - t.integer "user_id" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - t.string "name" + t.text "certificate" + t.string "key_handle" + t.string "public_key" + t.integer "counter" + t.integer "user_id" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + t.string "name" end add_index "u2f_registrations", ["key_handle"], name: "index_u2f_registrations_on_key_handle", using: :btree add_index "u2f_registrations", ["user_id"], name: "index_u2f_registrations_on_user_id", using: :btree create_table "user_agent_details", force: :cascade do |t| - t.string "user_agent", null: false - t.string "ip_address", null: false - t.integer "subject_id", null: false - t.string "subject_type", null: false - t.boolean "submitted", default: false, null: false - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.string "user_agent", null: false + t.string "ip_address", null: false + t.integer "subject_id", null: false + t.string "subject_type", null: false + t.boolean "submitted", default: false, null: false + t.datetime "created_at", null: false + t.datetime "updated_at", null: false end create_table "users", force: :cascade do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false - t.string "reset_password_token" + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false + t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" - t.string "current_sign_in_ip" - t.string "last_sign_in_ip" + t.string "current_sign_in_ip" + t.string "last_sign_in_ip" t.datetime "created_at" t.datetime "updated_at" - t.string "name" - t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 - t.string "skype", default: "", null: false - t.string "linkedin", default: "", null: false - t.string "twitter", default: "", null: false - t.string "authentication_token" - t.integer "theme_id", default: 1, null: false - t.string "bio" - t.integer "failed_attempts", default: 0 + t.string "name" + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false + t.string "authentication_token" + t.integer "theme_id", default: 1, null: false + t.string "bio" + t.integer "failed_attempts", default: 0 t.datetime "locked_at" - t.string "username" - t.boolean "can_create_group", default: true, null: false - t.boolean "can_create_team", default: true, null: false - t.string "state" - t.integer "color_scheme_id", default: 1, null: false + t.string "username" + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false + t.string "state" + t.integer "color_scheme_id", default: 1, null: false t.datetime "password_expires_at" - t.integer "created_by_id" + t.integer "created_by_id" t.datetime "last_credential_check_at" - t.string "avatar" - t.string "confirmation_token" + t.string "avatar" + t.string "confirmation_token" t.datetime "confirmed_at" t.datetime "confirmation_sent_at" - t.string "unconfirmed_email" - t.boolean "hide_no_ssh_key", default: false - t.string "website_url", default: "", null: false - t.string "notification_email" - t.boolean "hide_no_password", default: false - t.boolean "password_automatically_set", default: false - t.string "location" - t.string "encrypted_otp_secret" - t.string "encrypted_otp_secret_iv" - t.string "encrypted_otp_secret_salt" - t.boolean "otp_required_for_login", default: false, null: false - t.text "otp_backup_codes" - t.string "public_email", default: "", null: false - t.integer "dashboard", default: 0 - t.integer "project_view", default: 0 - t.integer "consumed_timestep" - t.integer "layout", default: 0 - t.boolean "hide_project_limit", default: false - t.string "unlock_token" + t.string "unconfirmed_email" + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", default: "", null: false + t.string "notification_email" + t.boolean "hide_no_password", default: false + t.boolean "password_automatically_set", default: false + t.string "location" + t.string "encrypted_otp_secret" + t.string "encrypted_otp_secret_iv" + t.string "encrypted_otp_secret_salt" + t.boolean "otp_required_for_login", default: false, null: false + t.text "otp_backup_codes" + t.string "public_email", default: "", null: false + t.integer "dashboard", default: 0 + t.integer "project_view", default: 0 + t.integer "consumed_timestep" + t.integer "layout", default: 0 + t.boolean "hide_project_limit", default: false + t.string "unlock_token" t.datetime "otp_grace_period_started_at" - t.boolean "ldap_email", default: false, null: false - t.boolean "external", default: false - t.string "organization" + t.boolean "ldap_email", default: false, null: false + t.boolean "external", default: false + t.string "organization" end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree @@ -1176,8 +1176,8 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "users", ["username"], name: "index_users_on_username_trigram", using: :gin, opclasses: {"username"=>"gin_trgm_ops"} create_table "users_star_projects", force: :cascade do |t| - t.integer "project_id", null: false - t.integer "user_id", null: false + t.integer "project_id", null: false + t.integer "user_id", null: false t.datetime "created_at" t.datetime "updated_at" end @@ -1187,23 +1187,23 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree create_table "web_hooks", force: :cascade do |t| - t.string "url", limit: 2000 - t.integer "project_id" + t.string "url", limit: 2000 + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.string "type", default: "ProjectHook" - t.integer "service_id" - t.boolean "push_events", default: true, null: false - t.boolean "issues_events", default: false, null: false - t.boolean "merge_requests_events", default: false, null: false - t.boolean "tag_push_events", default: false - t.boolean "note_events", default: false, null: false - t.boolean "enable_ssl_verification", default: true - t.boolean "build_events", default: false, null: false - t.boolean "wiki_page_events", default: false, null: false - t.string "token" - t.boolean "pipeline_events", default: false, null: false - t.boolean "confidential_issues_events", default: false, null: false + t.string "type", default: "ProjectHook" + t.integer "service_id" + t.boolean "push_events", default: true, null: false + t.boolean "issues_events", default: false, null: false + t.boolean "merge_requests_events", default: false, null: false + t.boolean "tag_push_events", default: false + t.boolean "note_events", default: false, null: false + t.boolean "enable_ssl_verification", default: true + t.boolean "build_events", default: false, null: false + t.boolean "wiki_page_events", default: false, null: false + t.string "token" + t.boolean "pipeline_events", default: false, null: false + t.boolean "confidential_issues_events", default: false, null: false end add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree -- cgit v1.2.1 From 2e3bc85421ddfc814e81f75e3adb8a8c1d53093e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 12 Oct 2016 15:21:11 +0200 Subject: Do not return from proc-closure in pipeline transition --- app/models/commit_status.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb index aac5f7a5937..7b554be4f9a 100644 --- a/app/models/commit_status.rb +++ b/app/models/commit_status.rb @@ -86,7 +86,7 @@ class CommitStatus < ActiveRecord::Base end after_transition do |commit_status, transition| - return if transition.loopback? + next if transition.loopback? commit_status.run_after_commit do pipeline.try do |pipeline| -- cgit v1.2.1 From c143003bfbd5cda725451c38ff1eca8ba469409b Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 13:53:18 +0300 Subject: Bump gitlab_git to 10.6.8 Signed-off-by: Dmitriy Zaporozhets --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index c5f1ce26daf..59ff78c8fae 100644 --- a/Gemfile +++ b/Gemfile @@ -51,7 +51,7 @@ gem 'browser', '~> 2.2' # Extracting information from a git repository # Provide access to Gitlab::Git library -gem 'gitlab_git', '~> 10.6.7' +gem 'gitlab_git', '~> 10.6.8' # LDAP Auth # GitLab fork with several improvements to original library. For full list of changes diff --git a/Gemfile.lock b/Gemfile.lock index 2feec4c4eb5..edccf870821 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -280,7 +280,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) - gitlab_git (10.6.7) + gitlab_git (10.6.8) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) @@ -863,7 +863,7 @@ DEPENDENCIES github-linguist (~> 4.7.0) github-markup (~> 1.4) gitlab-flowdock-git-hook (~> 1.0.1) - gitlab_git (~> 10.6.7) + gitlab_git (~> 10.6.8) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) gollum-rugged_adapter (~> 0.4.2) -- cgit v1.2.1 From ac4db38094f4a68a81b0a7570c5835f663c01cfd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 14:04:50 +0300 Subject: Use straight diff approach when compare merge request versions Signed-off-by: Dmitriy Zaporozhets --- app/models/compare.rb | 5 ++++- app/models/merge_request_diff.rb | 7 +++++-- app/services/compare_service.rb | 7 ++++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/app/models/compare.rb b/app/models/compare.rb index 4856510f526..4b568a1d11c 100644 --- a/app/models/compare.rb +++ b/app/models/compare.rb @@ -11,9 +11,10 @@ class Compare end end - def initialize(compare, project) + def initialize(compare, project, straight = false) @compare = compare @project = project + @straight = straight end def commits @@ -36,6 +37,8 @@ class Compare alias_method :commit, :head_commit def base_commit + return start_commit if @straight + return @base_commit if defined?(@base_commit) @base_commit = if start_commit && head_commit diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 3f7e96186a1..0ea9e892be2 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -167,8 +167,11 @@ class MergeRequestDiff < ActiveRecord::Base self == merge_request.merge_request_diff end - def compare_with(sha) - CompareService.new.execute(project, head_commit_sha, project, sha) + def compare_with(sha, straight = true) + # When compare merge request versions we want diff A..B instead of A...B + # so we handle cases when user squash and rebase commits in one of versions. + # For this reason we set straight to true by default. + CompareService.new.execute(project, head_commit_sha, project, sha, straight) end private diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 6d6075628af..6df3b958b8a 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,7 +3,7 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::Git::Compare object that responds to commits and diffs class CompareService - def execute(source_project, source_branch, target_project, target_branch) + def execute(source_project, source_branch, target_project, target_branch, straight = false) source_commit = source_project.commit(source_branch) return unless source_commit @@ -23,9 +23,10 @@ class CompareService raw_compare = Gitlab::Git::Compare.new( target_project.repository.raw_repository, target_branch, - source_sha + source_sha, + straight ) - Compare.new(raw_compare, target_project) + Compare.new(raw_compare, target_project, straight) end end -- cgit v1.2.1 From 1bb64ec99476451d67463e3508b449aeea7e694f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 17:21:19 +0300 Subject: Add spec for compare service Signed-off-by: Dmitriy Zaporozhets --- spec/services/compare_service_spec.rb | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 spec/services/compare_service_spec.rb diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb new file mode 100644 index 00000000000..d809f291b44 --- /dev/null +++ b/spec/services/compare_service_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe CompareService, services: true do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:service) { described_class.new } + + describe '#execute' do + context 'compare with base, like feature...fix' do + subject { service.execute(project, 'feature', project, 'fix', false) } + + it { expect(subject.diffs.size).to eq(1) } + end + + context 'straight compare, like feature..fix' do + subject { service.execute(project, 'feature', project, 'fix', true) } + + it { expect(subject.diffs.size).to eq(3) } + end + end +end -- cgit v1.2.1 From afe28b7be3a3a5a9db6e5a32465c0e3a8e9cb83d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 17:23:36 +0300 Subject: Add mr version improvement to changelog Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index e478e8c3365..51870619fb4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -127,6 +127,9 @@ v 8.12.4 - Fix failed project deletion when feature visibility set to private. !6688 - Prevent claiming associated model IDs via import. - Set GitLab project exported file permissions to owner only + - Improve the way merge request versions are compared with each other + +v 8.12.4 (unreleased) v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves -- cgit v1.2.1 From cdcc11d48fe6d04f6a1cfebc39bcea71d35b3e4a Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 17:46:47 +0300 Subject: Improve tests for merge request diff model Signed-off-by: Dmitriy Zaporozhets --- app/models/merge_request_diff.rb | 2 +- spec/models/merge_request_diff_spec.rb | 40 +++++++++++++++++++++------------- 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 0ea9e892be2..6ad2201573d 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -169,7 +169,7 @@ class MergeRequestDiff < ActiveRecord::Base def compare_with(sha, straight = true) # When compare merge request versions we want diff A..B instead of A...B - # so we handle cases when user squash and rebase commits in one of versions. + # so we handle cases when user does squash and rebase of the commits between versions. # For this reason we set straight to true by default. CompareService.new.execute(project, head_commit_sha, project, sha, straight) end diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index f27de0948ee..69b8afd22a3 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -74,27 +74,37 @@ describe MergeRequestDiff, models: true do end end end + end - describe '#commits_sha' do - shared_examples 'returning all commits SHA' do - it 'returns all commits SHA' do - commits_sha = subject.commits_sha + describe '#commits_sha' do + shared_examples 'returning all commits SHA' do + it 'returns all commits SHA' do + commits_sha = subject.commits_sha - expect(commits_sha).to eq(subject.commits.map(&:sha)) - end + expect(commits_sha).to eq(subject.commits.map(&:sha)) end + end - context 'when commits were loaded' do - before do - subject.commits - end - - it_behaves_like 'returning all commits SHA' + context 'when commits were loaded' do + before do + subject.commits end - context 'when commits were not loaded' do - it_behaves_like 'returning all commits SHA' - end + it_behaves_like 'returning all commits SHA' + end + + context 'when commits were not loaded' do + it_behaves_like 'returning all commits SHA' + end + end + + describe '#compare_with' do + subject { create(:merge_request).merge_request_diff } + + it 'delegates compare to the service' do + expect(CompareService).to receive(:new).and_call_original + + subject.compare_with('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') end end end -- cgit v1.2.1 From 7421c08ace8a129b37c4942eb21a0b5cf1a73a53 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 29 Sep 2016 18:02:06 +0300 Subject: Better tests for MergeRequestDiff#compare_with method Signed-off-by: Dmitriy Zaporozhets --- spec/models/merge_request_diff_spec.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/spec/models/merge_request_diff_spec.rb b/spec/models/merge_request_diff_spec.rb index 69b8afd22a3..e5007424041 100644 --- a/spec/models/merge_request_diff_spec.rb +++ b/spec/models/merge_request_diff_spec.rb @@ -99,12 +99,18 @@ describe MergeRequestDiff, models: true do end describe '#compare_with' do - subject { create(:merge_request).merge_request_diff } + subject { create(:merge_request, source_branch: 'fix').merge_request_diff } it 'delegates compare to the service' do expect(CompareService).to receive(:new).and_call_original - subject.compare_with('ae73cb07c9eeaf35924a10f713b364d32b2dd34f') + subject.compare_with(nil) + end + + it 'uses git diff A..B approach by default' do + diffs = subject.compare_with('0b4bc9a49b562e85de7cc9e834518ea6828729b9').diffs + + expect(diffs.size).to eq(3) end end end -- cgit v1.2.1 From b48c4b2662e7db9d68052392fb34dd2b27d12cf5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 12 Oct 2016 13:27:59 +0300 Subject: Refactor straight compare diff code Signed-off-by: Dmitriy Zaporozhets --- app/models/compare.rb | 22 ++++++++++++++++------ app/models/merge_request_diff.rb | 4 ++-- app/services/compare_service.rb | 4 ++-- spec/services/compare_service_spec.rb | 4 ++-- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/app/models/compare.rb b/app/models/compare.rb index 4b568a1d11c..3a8bbcb1acd 100644 --- a/app/models/compare.rb +++ b/app/models/compare.rb @@ -11,7 +11,7 @@ class Compare end end - def initialize(compare, project, straight = false) + def initialize(compare, project, straight: false) @compare = compare @project = project @straight = straight @@ -37,8 +37,6 @@ class Compare alias_method :commit, :head_commit def base_commit - return start_commit if @straight - return @base_commit if defined?(@base_commit) @base_commit = if start_commit && head_commit @@ -48,6 +46,18 @@ class Compare end end + def start_commit_sha + start_commit.try(:sha) + end + + def base_commit_sha + base_commit.try(:sha) + end + + def head_commit_sha + commit.try(:sha) + end + def raw_diffs(*args) @compare.diffs(*args) end @@ -61,9 +71,9 @@ class Compare def diff_refs Gitlab::Diff::DiffRefs.new( - base_sha: base_commit.try(:sha), - start_sha: start_commit.try(:sha), - head_sha: commit.try(:sha) + base_sha: @straight ? start_commit_sha : base_commit_sha, + start_sha: start_commit_sha, + head_sha: head_commit_sha ) end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 6ad2201573d..b8a10b7968e 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -167,11 +167,11 @@ class MergeRequestDiff < ActiveRecord::Base self == merge_request.merge_request_diff end - def compare_with(sha, straight = true) + def compare_with(sha, straight: true) # When compare merge request versions we want diff A..B instead of A...B # so we handle cases when user does squash and rebase of the commits between versions. # For this reason we set straight to true by default. - CompareService.new.execute(project, head_commit_sha, project, sha, straight) + CompareService.new.execute(project, head_commit_sha, project, sha, straight: straight) end private diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index 6df3b958b8a..5e8fafca98c 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -3,7 +3,7 @@ require 'securerandom' # Compare 2 branches for one repo or between repositories # and return Gitlab::Git::Compare object that responds to commits and diffs class CompareService - def execute(source_project, source_branch, target_project, target_branch, straight = false) + def execute(source_project, source_branch, target_project, target_branch, straight: false) source_commit = source_project.commit(source_branch) return unless source_commit @@ -27,6 +27,6 @@ class CompareService straight ) - Compare.new(raw_compare, target_project, straight) + Compare.new(raw_compare, target_project, straight: straight) end end diff --git a/spec/services/compare_service_spec.rb b/spec/services/compare_service_spec.rb index d809f291b44..3760f19aaa2 100644 --- a/spec/services/compare_service_spec.rb +++ b/spec/services/compare_service_spec.rb @@ -7,13 +7,13 @@ describe CompareService, services: true do describe '#execute' do context 'compare with base, like feature...fix' do - subject { service.execute(project, 'feature', project, 'fix', false) } + subject { service.execute(project, 'feature', project, 'fix', straight: false) } it { expect(subject.diffs.size).to eq(1) } end context 'straight compare, like feature..fix' do - subject { service.execute(project, 'feature', project, 'fix', true) } + subject { service.execute(project, 'feature', project, 'fix', straight: true) } it { expect(subject.diffs.size).to eq(3) } end -- cgit v1.2.1 From c65c800f8e466ffcfb58613fc775c4afa11f91e6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 12 Oct 2016 15:09:34 +0300 Subject: Inform user when comparing 2 version with different base Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/merge_requests.scss | 11 ++++++----- app/views/projects/merge_requests/show/_versions.html.haml | 7 +++++++ 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 1006b3e62e8..c712d6e36b1 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -401,8 +401,13 @@ padding: 16px; } + .content-block { + padding: $gl-padding-top $gl-padding; + } + .comments-disabled-notif { - padding: 10px 16px; + border-top: 1px solid $border-color; + .btn { margin-left: 5px; } @@ -413,10 +418,6 @@ margin: 0 7px; } - .comments-disabled-notif { - border-top: 1px solid $border-color; - } - .dropdown-title { color: $gl-text-color; } diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index 988ac0feae1..c282e3868cc 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -64,6 +64,13 @@ #{@merge_request.target_branch} (base) .monospace #{short_sha(@merge_request_diff.base_commit_sha)} + - if @start_version && @start_version.base_commit_sha != @merge_request_diff.base_commit_sha + .content-block + Selected versions have different base commits. + Changes will include + = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do + new commits + from #{@merge_request.target_branch} - unless @merge_request_diff.latest? && !@start_sha .comments-disabled-notif.content-block = icon('info-circle') -- cgit v1.2.1 From d0fb1835158467ce58c59f489384b13ad0f0ff7c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 12 Oct 2016 15:45:14 +0300 Subject: Fix CHANGELOG Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 2 -- 1 file changed, 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 51870619fb4..445566db95f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -129,8 +129,6 @@ v 8.12.4 - Set GitLab project exported file permissions to owner only - Improve the way merge request versions are compared with each other -v 8.12.4 (unreleased) - v 8.12.3 - Update Gitlab Shell to support low IO priority for storage moves -- cgit v1.2.1 From 45418734311561bb72aa5ab7a89d81bbd5705f7a Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 12 Oct 2016 14:51:56 +0200 Subject: Added documentation chapter for Git attributes As discussed in https://gitlab.com/gitlab-org/gitlab_git/issues/28 we'll need to clearly document the need for .gitattributes files being encoded using UTF-8. [ci skip] --- CHANGELOG | 1 + doc/README.md | 1 + doc/user/project/git_attributes.md | 22 ++++++++++++++++++++++ 3 files changed, 24 insertions(+) create mode 100644 doc/user/project/git_attributes.md diff --git a/CHANGELOG b/CHANGELOG index e478e8c3365..894f4bc9af0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.13.0 (unreleased) - Clarify documentation for Runners API (Gennady Trafimenkov) - Change user & group landing page routing from /u/:username to /:username - Prevent running GfmAutocomplete setup for each diff note !6569 + - Added documentation for .gitattributes files - AbstractReferenceFilter caches project_refs on RequestStore when active - Replaced the check sign to arrow in the show build view. !6501 - Add a /wip slash command to toggle the Work In Progress status of a merge request. !6259 (tbalthazar) diff --git a/doc/README.md b/doc/README.md index 42ee44f83dc..7e3d9b00900 100644 --- a/doc/README.md +++ b/doc/README.md @@ -20,6 +20,7 @@ - [Webhooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. - [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN. - [University](university/README.md) Learn Git and GitLab through videos and courses. +- [Git Attributes](user/project/git_attributes.md) Managing Git attributes using a `.gitattributes` file. ## Administrator documentation diff --git a/doc/user/project/git_attributes.md b/doc/user/project/git_attributes.md new file mode 100644 index 00000000000..21ef94e61f7 --- /dev/null +++ b/doc/user/project/git_attributes.md @@ -0,0 +1,22 @@ +# Git Attributes + +GitLab supports defining custom [Git attributes][gitattributes] such as what +files to treat as binary, and what language to use for syntax highlighting +diffs. + +To define these attributes, create a file called `.gitattributes` in the root +directory of your repository and push it to the default branch of your project. + +## Encoding Requirements + +The `.gitattributes` file _must_ be encoded in UTF-8 and _must not_ contain a +Byte Order Mark. If a different encoding is used, the file's contents will be +ignored. + +## Syntax Highlighting + +The `.gitattributes` file can be used to define which language to use when +syntax highlighting files and diffs. See ["Syntax +Highlighting"](highlighting.md) for more information. + +[gitattributes]: https://git-scm.com/docs/gitattributes -- cgit v1.2.1 From 3dd7ad5064afa49aa74a7d40ba02fff7e67846d2 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 12 Oct 2016 17:42:32 +0300 Subject: Improve mr compare message when base is different Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/merge_requests.scss | 3 +-- app/helpers/merge_requests_helper.rb | 4 ++++ app/views/projects/merge_requests/show/_versions.html.haml | 7 +++++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index c712d6e36b1..7cf69c56d15 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -402,12 +402,11 @@ } .content-block { + border-top: 1px solid $border-color; padding: $gl-padding-top $gl-padding; } .comments-disabled-notif { - border-top: 1px solid $border-color; - .btn { margin-left: 5px; } diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index b0a76765d97..249cb44e9d5 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -123,4 +123,8 @@ module MergeRequestsHelper def version_index(merge_request_diff) @merge_request_diffs.size - @merge_request_diffs.index(merge_request_diff) end + + def different_base?(version1, version2) + version1 && version2 && version1.base_commit_sha != version2.base_commit_sha + end end diff --git a/app/views/projects/merge_requests/show/_versions.html.haml b/app/views/projects/merge_requests/show/_versions.html.haml index c282e3868cc..eab48b78cb3 100644 --- a/app/views/projects/merge_requests/show/_versions.html.haml +++ b/app/views/projects/merge_requests/show/_versions.html.haml @@ -64,13 +64,16 @@ #{@merge_request.target_branch} (base) .monospace #{short_sha(@merge_request_diff.base_commit_sha)} - - if @start_version && @start_version.base_commit_sha != @merge_request_diff.base_commit_sha + - if different_base?(@start_version, @merge_request_diff) .content-block + = icon('info-circle') Selected versions have different base commits. Changes will include = link_to namespace_project_compare_path(@project.namespace, @project, from: @start_version.base_commit_sha, to: @merge_request_diff.base_commit_sha) do new commits - from #{@merge_request.target_branch} + from + %code #{@merge_request.target_branch} + - unless @merge_request_diff.latest? && !@start_sha .comments-disabled-notif.content-block = icon('info-circle') -- cgit v1.2.1 From dd3934f7a2fdcafd6607d893da15c9a195065d54 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 12 Oct 2016 16:44:47 +0200 Subject: Update to gitlab-shell 3.6.6 --- CHANGELOG | 2 +- GITLAB_SHELL_VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 894f4bc9af0..edbd7454548 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,7 +5,7 @@ v 8.13.0 (unreleased) - Truncate long labels with ellipsis in labels page - Update runner version only when updating contacted_at - Add link from system note to compare with previous version - - Use gitlab-shell v3.6.2 (GIT TRACE logging) + - Use gitlab-shell v3.6.6 - Add `/projects/visible` API endpoint (Ben Boeckel) - Fix centering of custom header logos (Ashley Dumaine) - ExpireBuildArtifactsWorker query builds table without ordering enqueuing one job per build to cleanup diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index 0f44168a4d5..4f2c1d15f6d 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -3.6.4 +3.6.6 -- cgit v1.2.1 From 8aa381c9c408baef295e721f4f11964de8db1aa0 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 12 Oct 2016 14:45:26 +0000 Subject: Revert "Merge branch 'tests-use-tmpfs' into 'master'" This reverts merge request !6730 --- .gitlab-ci.yml | 2 -- spec/spec_helper.rb | 5 ----- 2 files changed, 7 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cb6f691058e..8645488335e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -19,8 +19,6 @@ variables: before_script: - source ./scripts/prepare_build.sh - cp config/gitlab.yml.example config/gitlab.yml - - mkdir -p tmp/tests - - mount -t tmpfs tmpfs tmp/tests || echo "tmpfs mount failed, falling back to disc" - bundle --version - '[ "$USE_BUNDLE_INSTALL" != "true" ] || retry bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"' - retry gem install knapsack diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index f313bd4f249..b19f5824236 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -50,11 +50,6 @@ RSpec.configure do |config| example.run Rails.cache = caching_store end - - config.after(:each) do - FileUtils.rm_rf("tmp/tests/repositories") - FileUtils.mkdir_p("tmp/tests/repositories") - end end FactoryGirl::SyntaxRunner.class_eval do -- cgit v1.2.1 From b998479c81512b7c9a2cff28e7aabff3c4be0424 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Wed, 12 Oct 2016 13:32:48 +0200 Subject: API: Version information --- CHANGELOG | 1 + doc/api/README.md | 1 + doc/api/version.md | 23 +++++++++++++++++++++++ lib/api/api.rb | 1 + lib/api/version.rb | 12 ++++++++++++ spec/requests/api/version_spec.rb | 27 +++++++++++++++++++++++++++ 6 files changed, 65 insertions(+) create mode 100644 doc/api/version.md create mode 100644 lib/api/version.rb create mode 100644 spec/requests/api/version_spec.rb diff --git a/CHANGELOG b/CHANGELOG index e478e8c3365..401d0d66de3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 8.13.0 (unreleased) - Cache rendered markdown in the database, rather than Redis - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Simplify Mentionable concern instance methods + - API: Ability to retrieve version information (Robert Schilling) - Fix permission for setting an issue's due date - API: Multi-file commit !6096 (mahcsig) - Revert "Label list shows all issues (opened or closed) with that label" diff --git a/doc/api/README.md b/doc/api/README.md index 9e907689c80..75b098f2eeb 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -46,6 +46,7 @@ following locations: - [Todos](todos.md) - [Users](users.md) - [Validate CI configuration](ci/lint.md) +- [Version](version.md) ### Internal CI API diff --git a/doc/api/version.md b/doc/api/version.md new file mode 100644 index 00000000000..287d17cf97f --- /dev/null +++ b/doc/api/version.md @@ -0,0 +1,23 @@ +# Version API + +>**Note:** This feature was introduced in GitLab 8.13 + +Retrieve version information for this GitLab instance. Responds `200 OK` for +authenticated users. + +``` +GET /version +``` + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v3/version +``` + +Example response: + +```json +{ + "version": "8.13.0-pre", + "revision": "4e963fe" +} +``` diff --git a/lib/api/api.rb b/lib/api/api.rb index 99722a0a65c..eb4e1fc504f 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -73,6 +73,7 @@ module API mount ::API::Triggers mount ::API::Users mount ::API::Variables + mount ::API::Version route :any, '*path' do error!('404 Not Found', 404) diff --git a/lib/api/version.rb b/lib/api/version.rb new file mode 100644 index 00000000000..9ba576bd828 --- /dev/null +++ b/lib/api/version.rb @@ -0,0 +1,12 @@ +module API + class Version < Grape::API + before { authenticate! } + + desc 'Get the version information of the GitLab instance.' do + detail 'This feature was introduced in GitLab 8.13.' + end + get '/version' do + { version: Gitlab::VERSION, revision: Gitlab::REVISION } + end + end +end diff --git a/spec/requests/api/version_spec.rb b/spec/requests/api/version_spec.rb new file mode 100644 index 00000000000..54b69a0cae7 --- /dev/null +++ b/spec/requests/api/version_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + describe 'GET /version' do + context 'when unauthenticated' do + it 'returns authentication error' do + get api('/version') + + expect(response).to have_http_status(401) + end + end + + context 'when authenticated' do + let(:user) { create(:user) } + + it 'returns the version information' do + get api('/version', user) + + expect(response).to have_http_status(200) + expect(json_response['version']).to eq(Gitlab::VERSION) + expect(json_response['revision']).to eq(Gitlab::REVISION) + end + end + end +end -- cgit v1.2.1 From 25c82c6faf067a82e99bad590357ad57618475d9 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Wed, 12 Oct 2016 17:43:20 +0100 Subject: Updated JS to work with issue index & show --- app/assets/javascripts/dispatcher.js | 1 + app/assets/javascripts/labels_select.js | 4 ++-- app/assets/javascripts/milestone_select.js | 2 +- app/assets/javascripts/users_select.js | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/dispatcher.js b/app/assets/javascripts/dispatcher.js index adff73af79c..f3ef13ce20e 100644 --- a/app/assets/javascripts/dispatcher.js +++ b/app/assets/javascripts/dispatcher.js @@ -21,6 +21,7 @@ shortcut_handler = null; switch (page) { case 'projects:boards:show': + case 'projects:boards:index': shortcut_handler = new ShortcutsNavigation(); break; case 'projects:merge_requests:index': diff --git a/app/assets/javascripts/labels_select.js b/app/assets/javascripts/labels_select.js index e356872624a..f1e719937c7 100644 --- a/app/assets/javascripts/labels_select.js +++ b/app/assets/javascripts/labels_select.js @@ -292,7 +292,7 @@ return; } - if (page === 'projects:boards:show') { + if ($('html').hasClass('issue-boards-page')) { return; } if ($dropdown.hasClass('js-multiselect')) { @@ -334,7 +334,7 @@ page = $('body').data('page'); isIssueIndex = page === 'projects:issues:index'; isMRIndex = page === 'projects:merge_requests:index'; - if (page === 'projects:boards:show') { + if ($('html').hasClass('issue-boards-page')) { if (label.isAny) { gl.issueBoards.BoardsStore.state.filters['label_name'] = []; } diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 26cc6eb0e96..cee42633c79 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -110,7 +110,7 @@ e.preventDefault(); return; } - if (page === 'projects:boards:show') { + if ($('html').hasClass('issue-boards-page')) { gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = selected.name; gl.issueBoards.BoardsStore.updateFiltersUrl(); e.preventDefault(); diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index bcabda3ceb2..4c071f334fd 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -160,7 +160,7 @@ selectedId = user.id; return; } - if (page === 'projects:boards:show') { + if ($('html').hasClass('issue-boards-page')) { selectedId = user.id; gl.issueBoards.BoardsStore.state.filters[$dropdown.data('field-name')] = user.id; gl.issueBoards.BoardsStore.updateFiltersUrl(); -- cgit v1.2.1 From f32bc1f52662664048876b27e181a1cacda02580 Mon Sep 17 00:00:00 2001 From: Georg G Date: Wed, 12 Oct 2016 18:48:53 +0200 Subject: Add spec for Projects::GraphsController#languages --- .../controllers/projects/graphs_controller_spec.rb | 43 ++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 spec/controllers/projects/graphs_controller_spec.rb diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb new file mode 100644 index 00000000000..3f4535b7a24 --- /dev/null +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' +require 'ostruct' + +describe Projects::GraphsController do + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + sign_in(user) + project.team << [user, :master] + end + + describe 'GET #languages' do + let(:linguist_repository) do + OpenStruct.new(languages: { + 'Ruby' => 1000, + 'CoffeeScript' => 350, + 'PowerShell' => 15 + }) + end + + let(:expected_values) do + ps_color = "##{Digest::SHA256.hexdigest('PowerShell')[0...6]}" + [ + # colors from Linguist: + { value: 73.26, label: "Ruby", color: "#701516", highlight: "#701516" }, + { value: 25.64, label: "CoffeeScript", color: "#244776", highlight: "#244776" }, + # colors from SHA256 fallback: + { value: 1.1, label: "PowerShell", color: ps_color, highlight: ps_color } + ] + end + + before do + allow(Linguist::Repository).to receive(:new).and_return(linguist_repository) + end + + it 'sets the correct colour according to language' do + get(:languages, namespace_id: project.namespace.path, project_id: project.path, id: 'master') + + expect(assigns(:languages)).to eq(expected_values) + end + end +end -- cgit v1.2.1 From da64f9e544bc5f05818c3f66b278e6e30584a8b4 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 12 Oct 2016 18:54:23 +0200 Subject: Update health_check gem to `~> 2.2.0` Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/22278 --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 9d98a34a0d5..901e60ef9e2 100644 --- a/Gemfile +++ b/Gemfile @@ -343,7 +343,7 @@ gem 'oauth2', '~> 1.2.0' gem 'paranoia', '~> 2.0' # Health check -gem 'health_check', '~> 2.1.0' +gem 'health_check', '~> 2.2.0' # System information gem 'vmstat', '~> 2.2' diff --git a/Gemfile.lock b/Gemfile.lock index 69804c8c533..924e3a6781a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -336,7 +336,7 @@ GEM thor tilt hashie (3.4.4) - health_check (2.1.0) + health_check (2.2.1) rails (>= 4.0) hipchat (1.5.2) httparty @@ -875,7 +875,7 @@ DEPENDENCIES grape-entity (~> 0.4.2) haml_lint (~> 0.18.2) hamlit (~> 2.6.1) - health_check (~> 2.1.0) + health_check (~> 2.2.0) hipchat (~> 1.5.0) html-pipeline (~> 1.11.0) httparty (~> 0.13.3) -- cgit v1.2.1 From abfb4f6e32ac13929678039e324307ee3ef2af43 Mon Sep 17 00:00:00 2001 From: Bryce Johnson Date: Wed, 12 Oct 2016 19:00:38 +0200 Subject: Document Capybara errors from es6 in es5 file. --- doc/development/frontend.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/doc/development/frontend.md b/doc/development/frontend.md index f879cd57e25..56c8516508e 100644 --- a/doc/development/frontend.md +++ b/doc/development/frontend.md @@ -223,3 +223,14 @@ For our currently-supported browsers, see our [requirements][requirements]. [xss]: https://en.wikipedia.org/wiki/Cross-site_scripting [scss-style-guide]: scss_styleguide.md [requirements]: ../install/requirements.md#supported-web-browsers + +## Common Errors + +### Rspec (Capybara/Poltergeist) chokes on general JavaScript errors + +If you see very generic JavaScript errors (e.g. `jQuery is undefined`) being thrown in tests, but +can't reproduce them manually, you may have included `ES6`-style JavaScript in files that don't +have the `.js.es6` file extension. Either use ES5-friendly JavaScript or rename the file you're +working in (`git mv .js> `). + + -- cgit v1.2.1 From ce4dad45557888066733bb20470bf56b87d4c8b7 Mon Sep 17 00:00:00 2001 From: Georg G Date: Thu, 13 Oct 2016 07:01:34 +0200 Subject: Use test double and matchers --- spec/controllers/projects/graphs_controller_spec.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index 3f4535b7a24..dd743356145 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -1,5 +1,4 @@ require 'spec_helper' -require 'ostruct' describe Projects::GraphsController do let(:project) { create(:project) } @@ -12,7 +11,7 @@ describe Projects::GraphsController do describe 'GET #languages' do let(:linguist_repository) do - OpenStruct.new(languages: { + double(languages: { 'Ruby' => 1000, 'CoffeeScript' => 350, 'PowerShell' => 15 @@ -23,10 +22,10 @@ describe Projects::GraphsController do ps_color = "##{Digest::SHA256.hexdigest('PowerShell')[0...6]}" [ # colors from Linguist: - { value: 73.26, label: "Ruby", color: "#701516", highlight: "#701516" }, - { value: 25.64, label: "CoffeeScript", color: "#244776", highlight: "#244776" }, + { label: "Ruby", color: "#701516", highlight: "#701516" }, + { label: "CoffeeScript", color: "#244776", highlight: "#244776" }, # colors from SHA256 fallback: - { value: 1.1, label: "PowerShell", color: ps_color, highlight: ps_color } + { label: "PowerShell", color: ps_color, highlight: ps_color } ] end @@ -37,7 +36,9 @@ describe Projects::GraphsController do it 'sets the correct colour according to language' do get(:languages, namespace_id: project.namespace.path, project_id: project.path, id: 'master') - expect(assigns(:languages)).to eq(expected_values) + expected_values.each do |val| + expect(assigns(:languages)).to include(include(val)) + end end end end -- cgit v1.2.1 From 48015deda12cdff6b814785c97316e5b77c4a03e Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Wed, 12 Oct 2016 11:16:29 +0500 Subject: Union examples in event spec fo speed up --- spec/models/event_spec.rb | 110 +++++++++++++++++++++++++--------------------- 1 file changed, 61 insertions(+), 49 deletions(-) diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 06cac929bbf..733b79079ed 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -27,17 +27,17 @@ describe Event, models: true do end describe "Push event" do - before do - project = create(:project) - @user = project.owner - @event = create_event(project, @user) + let(:project) { create(:project) } + let(:user) { project.owner } + let(:event) { create_event(project, user) } + + it do + expect(event.push?).to be_truthy + expect(event.visible_to_user?).to be_truthy + expect(event.tag?).to be_falsey + expect(event.branch_name).to eq("master") + expect(event.author).to eq(user) end - - it { expect(@event.push?).to be_truthy } - it { expect(@event.visible_to_user?).to be_truthy } - it { expect(@event.tag?).to be_falsey } - it { expect(@event.branch_name).to eq("master") } - it { expect(@event.author).to eq(@user) } end describe '#note?' do @@ -59,8 +59,8 @@ describe Event, models: true do describe '#visible_to_user?' do let(:project) { create(:empty_project, :public) } let(:non_member) { create(:user) } - let(:member) { create(:user) } - let(:guest) { create(:user) } + let(:member) { create(:user) } + let(:guest) { create(:user) } let(:author) { create(:author) } let(:assignee) { create(:user) } let(:admin) { create(:admin) } @@ -79,23 +79,27 @@ describe Event, models: true do context 'for non confidential issues' do let(:target) { issue } - it { expect(event.visible_to_user?(non_member)).to eq true } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq true } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq true + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq true + expect(event.visible_to_user?(admin)).to eq true + end end context 'for confidential issues' do let(:target) { confidential_issue } - it { expect(event.visible_to_user?(non_member)).to eq false } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq false } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq false + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq false + expect(event.visible_to_user?(admin)).to eq true + end end end @@ -103,23 +107,27 @@ describe Event, models: true do context 'on non confidential issues' do let(:target) { note_on_issue } - it { expect(event.visible_to_user?(non_member)).to eq true } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq true } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq true + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq true + expect(event.visible_to_user?(admin)).to eq true + end end context 'on confidential issues' do let(:target) { note_on_confidential_issue } - it { expect(event.visible_to_user?(non_member)).to eq false } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq false } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq false + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq false + expect(event.visible_to_user?(admin)).to eq true + end end end @@ -129,22 +137,26 @@ describe Event, models: true do let(:note_on_merge_request) { create(:legacy_diff_note_on_merge_request, noteable: merge_request, project: project) } let(:target) { note_on_merge_request } - it { expect(event.visible_to_user?(non_member)).to eq true } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq true } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq true + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq true + expect(event.visible_to_user?(admin)).to eq true + end context 'private project' do let(:project) { create(:project, :private) } - it { expect(event.visible_to_user?(non_member)).to eq false } - it { expect(event.visible_to_user?(author)).to eq true } - it { expect(event.visible_to_user?(assignee)).to eq true } - it { expect(event.visible_to_user?(member)).to eq true } - it { expect(event.visible_to_user?(guest)).to eq false } - it { expect(event.visible_to_user?(admin)).to eq true } + it do + expect(event.visible_to_user?(non_member)).to eq false + expect(event.visible_to_user?(author)).to eq true + expect(event.visible_to_user?(assignee)).to eq true + expect(event.visible_to_user?(member)).to eq true + expect(event.visible_to_user?(guest)).to eq false + expect(event.visible_to_user?(admin)).to eq true + end end end end @@ -214,6 +226,6 @@ describe Event, models: true do action: Event::PUSHED, data: data, author_id: user.id - }.merge(attrs)) + }.merge!(attrs)) end end -- cgit v1.2.1 From 9521736ebc062ba6f693da389f895061ac7a8b3a Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 13 Oct 2016 09:19:30 +0200 Subject: updated var name based on feedback --- app/models/cycle_analytics.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/cycle_analytics.rb b/app/models/cycle_analytics.rb index 0f3fd995681..8ed4a56b19b 100644 --- a/app/models/cycle_analytics.rb +++ b/app/models/cycle_analytics.rb @@ -2,7 +2,7 @@ class CycleAnalytics include Gitlab::Database::Median include Gitlab::Database::DateTime - DEPLOYED_CHECK_METRICS = %i[production staging] + DEPLOYMENT_METRIC_STAGES = %i[production staging] def initialize(project, from:) @project = project @@ -93,7 +93,7 @@ class CycleAnalytics join(MergeRequest::Metrics.arel_table). on(MergeRequest.arel_table[:id].eq(MergeRequest::Metrics.arel_table[:merge_request_id])) - if DEPLOYED_CHECK_METRICS.include?(name) + if DEPLOYMENT_METRIC_STAGES.include?(name) # Limit to merge requests that have been deployed to production after `@from` query.where(MergeRequest::Metrics.arel_table[:first_deployed_to_production_at].gteq(@from)) end -- cgit v1.2.1 From 6c88a9dfbbee692e872d6f237cb4ca325ebc4a26 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 13 Oct 2016 09:30:18 +0200 Subject: Add more info on the new CI permissions model [ci skip] --- doc/user/project/new_ci_build_permissions_model.md | 54 ++++++++++++++-------- 1 file changed, 36 insertions(+), 18 deletions(-) diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md index 5253825d507..19385a99d64 100644 --- a/doc/user/project/new_ci_build_permissions_model.md +++ b/doc/user/project/new_ci_build_permissions_model.md @@ -87,20 +87,6 @@ your Runners in the most possible secure way, by avoiding the following: By using an insecure GitLab Runner configuration, you allow the rogue developers to steal the tokens of other builds. -## Debugging problems - -With the new permission model in place, there may be times that your build will -fail. This is most likely because your project tries to access other project's -sources, and you don't have the appropriate permissions. In the build log look -for information about 403 or forbidden access messages - -As an Administrator, you can verify that the user is a member of the group or -project they're trying to have access to, and you can impersonate the user to -retry the failing build in order to verify that everything is correct. - -You need to make sure that your installation has HTTPS cloning enabled. -HTTPS support is required by GitLab CI to clone all sources. - ## Build triggers [Build triggers][triggers] do not support the new permission model. @@ -152,17 +138,46 @@ with GitLab 8.12. ## Making use of the new CI build permissions model -With the new build permission model, there is now an easy way to access all +With the new build permissions model, there is now an easy way to access all dependent source code in a project. That way, we can: 1. Access a project's Git submodules 1. Access private container images 1. Access project's and submodule LFS objects -Let's see how that works with Git submodules and private Docker images hosted on +Below you can see the prerequisites needed to make use of the new permissions +model and how that works with Git submodules and private Docker images hosted on the container registry. -## Git submodules +### Prerequisites to use the new permissions model + +With the new permissions model in place, there may be times that your build will +fail. This is most likely because your project tries to access other project's +sources, and you don't have the appropriate permissions. In the build log look +for information about 403 or forbidden access messages. + +In short here's what you need to do should you encounter any issues. + +As an administrator: + +- **500 errors**: You will need to update [GitLab Workhorse][workhorse] to at + least 0.8.2. This is done automatically for Omnibus installations, you need to + check manually for installations from source. +- **500 errors**: Check if you have another web proxy sitting in front of NGINX (HAProxy, + Apache, etc.). It might be a good idea to let GitLab use the internal NGINX + web server and not disable it completely. See [this comment][comment] for an + example. +- **403 errors**: You need to make sure that your installation has [HTTP(S) + cloning enabled][https]. HTTP(S) support is now a **requirement** by GitLab CI + to clone all sources. + +As a user: + +- Make sure you are a member of the group or project you're trying to have + access to. As an Administrator, you can verify that by impersonating the user + and retry the failing build in order to verify that everything is correct. + +### Git submodules > It often happens that while working on one project, you need to use another @@ -286,7 +301,10 @@ test: - docker run $CI_REGISTRY/group/other-project:latest ``` -[git-scm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules [build permissions]: ../permissions.md#builds-permissions +[comment]: https://gitlab.com/gitlab-org/gitlab-ce/issues/22484#note_16648302 [ext]: ../permissions.md#external-users +[git-scm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules +[https]: ../admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols [triggers]: ../../ci/triggers/README.md +[workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse -- cgit v1.2.1 From 3d6f18cec57fc6c7776bd05558e0d03dd3b141e1 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 12 Oct 2016 20:38:33 +0200 Subject: GrapeDSL for variables --- lib/api/variables.rb | 89 +++++++++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 47 deletions(-) diff --git a/lib/api/variables.rb b/lib/api/variables.rb index f6495071a11..b9fb3c21dbb 100644 --- a/lib/api/variables.rb +++ b/lib/api/variables.rb @@ -4,27 +4,29 @@ module API before { authenticate! } before { authorize! :admin_build, user_project } + params do + requires :id, type: String, desc: 'The ID of a project' + end + resource :projects do - # Get project variables - # - # Parameters: - # id (required) - The ID of a project - # page (optional) - The page number for pagination - # per_page (optional) - The value of items per page to show - # Example Request: - # GET /projects/:id/variables + desc 'Get project variables' do + success Entities::Variable + end + params do + optional :page, type: Integer, desc: 'The page number for pagination' + optional :per_page, type: Integer, desc: 'The value of items per page to show' + end get ':id/variables' do variables = user_project.variables present paginate(variables), with: Entities::Variable end - # Get specific variable of a project - # - # Parameters: - # id (required) - The ID of a project - # key (required) - The `key` of variable - # Example Request: - # GET /projects/:id/variables/:key + desc 'Get a specific variable from a project' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + end get ':id/variables/:key' do key = params[:key] variable = user_project.variables.find_by(key: key.to_s) @@ -34,18 +36,15 @@ module API present variable, with: Entities::Variable end - # Create a new variable in project - # - # Parameters: - # id (required) - The ID of a project - # key (required) - The key of variable - # value (required) - The value of variable - # Example Request: - # POST /projects/:id/variables + desc 'Create a new variable in a project' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + requires :value, type: String, desc: 'The value of the variable' + end post ':id/variables' do - required_attributes! [:key, :value] - - variable = user_project.variables.create(key: params[:key], value: params[:value]) + variable = user_project.variables.create(declared(params, include_parent_namespaces: false).to_h) if variable.valid? present variable, with: Entities::Variable @@ -54,41 +53,37 @@ module API end end - # Update existing variable of a project - # - # Parameters: - # id (required) - The ID of a project - # key (optional) - The `key` of variable - # value (optional) - New value for `value` field of variable - # Example Request: - # PUT /projects/:id/variables/:key + desc 'Update an existing variable from a project' do + success Entities::Variable + end + params do + optional :key, type: String, desc: 'The key of the variable' + optional :value, type: String, desc: 'The value of the variable' + end put ':id/variables/:key' do - variable = user_project.variables.find_by(key: params[:key].to_s) + variable = user_project.variables.find_by(key: params[:key]) return not_found!('Variable') unless variable - attrs = attributes_for_keys [:value] - if variable.update(attrs) + if variable.update(value: params[:value]) present variable, with: Entities::Variable else render_validation_error!(variable) end end - # Delete existing variable of a project - # - # Parameters: - # id (required) - The ID of a project - # key (required) - The ID of a variable - # Example Request: - # DELETE /projects/:id/variables/:key + desc 'Delete an existing variable from a project' do + success Entities::Variable + end + params do + requires :key, type: String, desc: 'The key of the variable' + end delete ':id/variables/:key' do - variable = user_project.variables.find_by(key: params[:key].to_s) + variable = user_project.variables.find_by(key: params[:key]) return not_found!('Variable') unless variable - variable.destroy - present variable, with: Entities::Variable + present variable.destroy, with: Entities::Variable end end end -- cgit v1.2.1 From cfd0d66c8308c0f259e39322193c8ddb34ec28f9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 13 Oct 2016 10:04:58 +0200 Subject: Reassign secret token when regenerating one --- lib/gitlab/backend/shell.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index d0060fbaca1..9cec71a3222 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -47,8 +47,8 @@ module Gitlab unless File.size?(secret_file) # Generate a new token of 16 random hexadecimal characters and store it in secret_file. - token = SecureRandom.hex(16) - File.write(secret_file, token) + @secret_token = SecureRandom.hex(16) + File.write(secret_file, @secret_token) end link_path = File.join(shell_path, '.gitlab_shell_secret') -- cgit v1.2.1 From 3939d4dfbfda5f7bb5133e12573c80f600ce451f Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 13 Oct 2016 09:58:06 +0100 Subject: Fix secret names in HA docs --- doc/administration/high_availability/gitlab.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/administration/high_availability/gitlab.md b/doc/administration/high_availability/gitlab.md index 8a881ce8863..137fed35a73 100644 --- a/doc/administration/high_availability/gitlab.md +++ b/doc/administration/high_availability/gitlab.md @@ -101,9 +101,9 @@ need some additional configuration. ```ruby gitlab_shell['secret_token'] = 'fbfb19c355066a9afb030992231c4a363357f77345edd0f2e772359e5be59b02538e1fa6cae8f93f7d23355341cea2b93600dab6d6c3edcdced558fc6d739860' - gitlab_rails['secret_token'] = 'b719fe119132c7810908bba18315259ed12888d4f5ee5430c42a776d840a396799b0a5ef0a801348c8a357f07aa72bbd58e25a84b8f247a25c72f539c7a6c5fa' - gitlab_ci['secret_key_base'] = '6e657410d57c71b4fc3ed0d694e7842b1895a8b401d812c17fe61caf95b48a6d703cb53c112bc01ebd197a85da81b18e29682040e99b4f26594772a4a2c98c6d' - gitlab_ci['db_key_base'] = 'bf2e47b68d6cafaef1d767e628b619365becf27571e10f196f98dc85e7771042b9203199d39aff91fcb6837c8ed83f2a912b278da50999bb11a2fbc0fba52964' + gitlab_rails['otp_key_base'] = 'b719fe119132c7810908bba18315259ed12888d4f5ee5430c42a776d840a396799b0a5ef0a801348c8a357f07aa72bbd58e25a84b8f247a25c72f539c7a6c5fa' + gitlab_rails['secret_key_base'] = '6e657410d57c71b4fc3ed0d694e7842b1895a8b401d812c17fe61caf95b48a6d703cb53c112bc01ebd197a85da81b18e29682040e99b4f26594772a4a2c98c6d' + gitlab_rails['db_key_base'] = 'bf2e47b68d6cafaef1d767e628b619365becf27571e10f196f98dc85e7771042b9203199d39aff91fcb6837c8ed83f2a912b278da50999bb11a2fbc0fba52964' ``` 1. Run `touch /etc/gitlab/skip-auto-migrations` to prevent database migrations -- cgit v1.2.1 From 3c476ee62512a0642ca3e5e6b228f9abdd867a34 Mon Sep 17 00:00:00 2001 From: Mark Fletcher Date: Tue, 11 Oct 2016 16:52:49 +0100 Subject: Maintain "force_remove_source_branch" options on Merge Request unless specified --- CHANGELOG | 1 + app/services/merge_requests/update_service.rb | 5 ++++- spec/services/merge_requests/update_service_spec.rb | 12 ++++++++++++ 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 5e026b4ec94..279501518c1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -31,6 +31,7 @@ v 8.13.0 (unreleased) - Update Gitlab Shell to fix some problems with moving projects between storages - Cache rendered markdown in the database, rather than Redis - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references + - Do not alter 'force_remove_source_branch' options on MergeRequest unless specified - Simplify Mentionable concern instance methods - API: Ability to retrieve version information (Robert Schilling) - Fix permission for setting an issue's due date diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 9dbec49d163..a37cc3fdf21 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -15,7 +15,10 @@ module MergeRequests params.except!(:target_branch, :force_remove_source_branch) end - merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) + if params[:force_remove_source_branch].present? + merge_request.merge_params['force_remove_source_branch'] = params.delete(:force_remove_source_branch) + end + handle_wip_event(merge_request) update(merge_request) end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index fd5f94047c2..2433a7dad06 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -105,6 +105,18 @@ describe MergeRequests::UpdateService, services: true do expect(note).not_to be_nil expect(note.note).to eq 'Target branch changed from `master` to `target`' end + + context 'when not including source branch removal options' do + before do + opts.delete(:force_remove_source_branch) + end + + it 'maintains the original options' do + update_merge_request(opts) + + expect(@merge_request.merge_params["force_remove_source_branch"]).to eq("1") + end + end end context 'todos' do -- cgit v1.2.1 From 2273b5d6d6f64a14d41bc8d25287d615502d2622 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 13 Oct 2016 12:52:51 +0200 Subject: Sort API mounts --- lib/api/api.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/api/api.rb b/lib/api/api.rb index 99722a0a65c..a6f32a9fc04 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -31,11 +31,12 @@ module API # Keep in alphabetical order mount ::API::AccessRequests mount ::API::AwardEmoji + mount ::API::Boards mount ::API::Branches mount ::API::BroadcastMessages mount ::API::Builds - mount ::API::CommitStatuses mount ::API::Commits + mount ::API::CommitStatuses mount ::API::DeployKeys mount ::API::Deployments mount ::API::Environments @@ -43,22 +44,21 @@ module API mount ::API::Groups mount ::API::Internal mount ::API::Issues - mount ::API::Boards mount ::API::Keys mount ::API::Labels mount ::API::LicenseTemplates mount ::API::Lint mount ::API::Members - mount ::API::MergeRequests mount ::API::MergeRequestDiffs + mount ::API::MergeRequests mount ::API::Milestones mount ::API::Namespaces mount ::API::Notes mount ::API::NotificationSettings mount ::API::Pipelines mount ::API::ProjectHooks - mount ::API::ProjectSnippets mount ::API::Projects + mount ::API::ProjectSnippets mount ::API::Repositories mount ::API::Runners mount ::API::Services -- cgit v1.2.1 From 85324ff8d36165709d5c98569ca0745b32ba9095 Mon Sep 17 00:00:00 2001 From: Georg G Date: Thu, 13 Oct 2016 13:08:19 +0200 Subject: Fix indentation and change inner matcher --- spec/controllers/projects/graphs_controller_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/controllers/projects/graphs_controller_spec.rb b/spec/controllers/projects/graphs_controller_spec.rb index dd743356145..74e6603b0cb 100644 --- a/spec/controllers/projects/graphs_controller_spec.rb +++ b/spec/controllers/projects/graphs_controller_spec.rb @@ -12,10 +12,10 @@ describe Projects::GraphsController do describe 'GET #languages' do let(:linguist_repository) do double(languages: { - 'Ruby' => 1000, - 'CoffeeScript' => 350, - 'PowerShell' => 15 - }) + 'Ruby' => 1000, + 'CoffeeScript' => 350, + 'PowerShell' => 15 + }) end let(:expected_values) do @@ -37,7 +37,7 @@ describe Projects::GraphsController do get(:languages, namespace_id: project.namespace.path, project_id: project.path, id: 'master') expected_values.each do |val| - expect(assigns(:languages)).to include(include(val)) + expect(assigns(:languages)).to include(a_hash_including(val)) end end end -- cgit v1.2.1 From ae95118a4ff3242a210d93e57370f6ef400db886 Mon Sep 17 00:00:00 2001 From: Johan H Date: Tue, 11 Oct 2016 16:27:09 +0200 Subject: Convert UTF-8 Emoji to Gitlab emoji --- CHANGELOG | 1 + lib/banzai/filter/emoji_filter.rb | 39 ++++++++++++++++------------- lib/gitlab/emoji.rb | 7 ++++-- spec/lib/banzai/filter/emoji_filter_spec.rb | 16 ++++++++++++ 4 files changed, 43 insertions(+), 20 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9e193132ac2..16b05d1d972 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,6 +34,7 @@ v 8.13.0 (unreleased) - Simplify Mentionable concern instance methods - Fix permission for setting an issue's due date - API: Multi-file commit !6096 (mahcsig) + - Unicode emoji are now converted to images - Revert "Label list shows all issues (opened or closed) with that label" - Expose expires_at field when sharing project on API - Fix VueJS template tags being rendered in code comments diff --git a/lib/banzai/filter/emoji_filter.rb b/lib/banzai/filter/emoji_filter.rb index 23ae6dfc79a..a8c1ca0c60a 100644 --- a/lib/banzai/filter/emoji_filter.rb +++ b/lib/banzai/filter/emoji_filter.rb @@ -14,15 +14,16 @@ module Banzai search_text_nodes(doc).each do |node| content = node.to_html next if has_ancestor?(node, IGNORED_ANCESTOR_TAGS) - if content.include?(':') || node.text.match(emoji_unicode_pattern) - html = emoji_name_image_filter(content) - html = emoji_unicode_image_filter(html) - next if html == content - node.replace(html) - end - end + next unless content.include?(':') || node.text.match(emoji_unicode_pattern) + + html = emoji_name_image_filter(content) + html = emoji_unicode_image_filter(html) + next if html == content + + node.replace(html) + end doc end @@ -34,31 +35,35 @@ module Banzai def emoji_name_image_filter(text) text.gsub(emoji_pattern) do |match| name = $1 - ":#{name}:" + emoji_image_tag(name, emoji_url(name)) end end - - # Replace unicode emojis with corresponding images if they exist. + # Replace unicode emoji with corresponding images if they exist. # - # text - String text to replace unicode emojis in. + # text - String text to replace unicode emoji in. # - # Returns a String with unicode emojis replaced with images. - + # Returns a String with unicode emoji replaced with images. def emoji_unicode_image_filter(text) text.gsub(emoji_unicode_pattern) do |moji| - ":#{Gitlab::Emoji.emojis_by_moji[moji][" + emoji_image_tag(Gitlab::Emoji.emojis_by_moji[moji]['name'], emoji_unicode_url(moji)) end end + + def emoji_image_tag(emoji_name, emoji_url) + ":#{emoji_name}:" + 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 + # Build a regexp that matches all valid unicode emojis names. def self.emoji_unicode_pattern @emoji_unicode_pattern ||= /(#{Gitlab::Emoji.emojis_unicodes.map { |moji| Regexp.escape(moji) }.join('|')})/ - end + private def emoji_url(name) @@ -80,13 +85,10 @@ module Banzai emoji_unicode_path = emoji_unicode_filename(moji) if context[:asset_host] - # Asset host is specified. url_to_image(emoji_unicode_path) elsif context[:asset_root] - # Gitlab url is specified File.join(context[:asset_root], url_to_image(emoji_unicode_path)) else - # All other cases url_to_image(emoji_unicode_path) end end @@ -102,6 +104,7 @@ module Banzai def emoji_filename(name) "#{Gitlab::Emoji.emoji_filename(name)}.png" end + def emoji_unicode_pattern self.class.emoji_unicode_pattern end diff --git a/lib/gitlab/emoji.rb b/lib/gitlab/emoji.rb index 803b0c2d057..bbbca8acc40 100644 --- a/lib/gitlab/emoji.rb +++ b/lib/gitlab/emoji.rb @@ -9,16 +9,19 @@ module Gitlab def emojis_by_moji Gemojione.index.instance_variable_get(:@emoji_by_moji) end + def emojis_unicodes - emojis_by_moji.keys.sort + emojis_by_moji.keys end + def emojis_names - emojis.keys.sort + emojis.keys end def emoji_filename(name) emojis[name]["unicode"] end + def emoji_unicode_filename(moji) emojis_by_moji[moji]["unicode"] end diff --git a/spec/lib/banzai/filter/emoji_filter_spec.rb b/spec/lib/banzai/filter/emoji_filter_spec.rb index 475160bb5ec..c8e62f528df 100644 --- a/spec/lib/banzai/filter/emoji_filter_spec.rb +++ b/spec/lib/banzai/filter/emoji_filter_spec.rb @@ -16,10 +16,12 @@ describe Banzai::Filter::EmojiFilter, lib: true do doc = filter('

:heart:

') expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' end + it 'replaces supported unicode emoji' do doc = filter('

❤️

') expect(doc.css('img').first.attr('src')).to eq 'https://foo.com/assets/2764.png' end + it 'ignores unsupported emoji' do exp = act = '

:foo:

' doc = filter(act) @@ -162,4 +164,18 @@ describe Banzai::Filter::EmojiFilter, lib: true do doc = filter(':frowning:', asset_host: 'https://this-is-ignored-i-guess?') expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com') end + + it 'uses a custom asset_root context' do + root = Gitlab.config.gitlab.url + 'gitlab/root' + + doc = filter("'🎱'", asset_root: root) + expect(doc.css('img').first.attr('src')).to start_with(root) + end + + it 'uses a custom asset_host context' do + ActionController::Base.asset_host = 'https://cdn.example.com' + + doc = filter("'🎱'", asset_host: 'https://this-is-ignored-i-guess?') + expect(doc.css('img').first.attr('src')).to start_with('https://cdn.example.com') + end end -- cgit v1.2.1 From 0eb5b0a08af7ebe68d515a68e2a94b3d0213393f Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Mon, 3 Oct 2016 05:44:58 +0200 Subject: Use forked github-markup gem to enable python3 support with omnibus --- Gemfile | 2 +- Gemfile.lock | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 230561f90d3..7323776d24c 100644 --- a/Gemfile +++ b/Gemfile @@ -101,7 +101,7 @@ gem 'seed-fu', '~> 2.3.5' # Markdown and HTML processing gem 'html-pipeline', '~> 1.11.0' gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie' -gem 'github-markup', '~> 1.4' +gem 'github-markup', '~> 1.4', git: 'https://github.com/brodock/github-markup.git', branch: 'python3-fix' gem 'redcarpet', '~> 3.3.3' gem 'RedCloth', '~> 4.3.2' gem 'rdoc', '~>3.6' diff --git a/Gemfile.lock b/Gemfile.lock index 067908af3fb..dde92fff044 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,3 +1,10 @@ +GIT + remote: https://github.com/brodock/github-markup.git + revision: 62ff5993081920ad75016f2b87019dd14e3af137 + branch: python3-fix + specs: + github-markup (1.4.0) + GEM remote: https://rubygems.org/ specs: @@ -272,7 +279,6 @@ GEM escape_utils (~> 1.1.0) mime-types (>= 1.19) rugged (>= 0.23.0b) - github-markup (1.4.0) gitlab-flowdock-git-hook (1.0.1) flowdock (~> 0.7) gitlab-grit (>= 2.4.1) @@ -864,7 +870,7 @@ DEPENDENCIES gemnasium-gitlab-service (~> 0.2) gemojione (~> 3.0) github-linguist (~> 4.7.0) - github-markup (~> 1.4) + github-markup (~> 1.4)! gitlab-flowdock-git-hook (~> 1.0.1) gitlab_git (~> 10.6.8) gitlab_omniauth-ldap (~> 1.2.1) -- cgit v1.2.1 From 2549db7aaed7cad60d5ddf129a70da25b09aded2 Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Thu, 13 Oct 2016 04:44:23 +0200 Subject: Updated gitlab-markup forked gem to download from rubygems --- CHANGELOG | 3 +++ Gemfile | 2 +- Gemfile.lock | 11 +++-------- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e13e0eef87a..b2a406b0def 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -109,6 +109,9 @@ v 8.13.0 (unreleased) - API: all unknown routing will be handled with 404 Not Found - Make guests unable to view MRs on private projects +v 8.12.7 + - Use gitlab-markup gem instead of github-markup to fix `.rst` file rendering. !6659 + v 8.12.6 - Update mailroom to 0.8.1 in Gemfile.lock !6814 diff --git a/Gemfile b/Gemfile index 7323776d24c..1204a546983 100644 --- a/Gemfile +++ b/Gemfile @@ -101,7 +101,7 @@ gem 'seed-fu', '~> 2.3.5' # Markdown and HTML processing gem 'html-pipeline', '~> 1.11.0' gem 'deckar01-task_list', '1.0.5', require: 'task_list/railtie' -gem 'github-markup', '~> 1.4', git: 'https://github.com/brodock/github-markup.git', branch: 'python3-fix' +gem 'gitlab-markup', '~> 1.5.0' gem 'redcarpet', '~> 3.3.3' gem 'RedCloth', '~> 4.3.2' gem 'rdoc', '~>3.6' diff --git a/Gemfile.lock b/Gemfile.lock index dde92fff044..25a176da8fd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,10 +1,3 @@ -GIT - remote: https://github.com/brodock/github-markup.git - revision: 62ff5993081920ad75016f2b87019dd14e3af137 - branch: python3-fix - specs: - github-markup (1.4.0) - GEM remote: https://rubygems.org/ specs: @@ -279,6 +272,7 @@ GEM escape_utils (~> 1.1.0) mime-types (>= 1.19) rugged (>= 0.23.0b) + github-markup (1.4.0) gitlab-flowdock-git-hook (1.0.1) flowdock (~> 0.7) gitlab-grit (>= 2.4.1) @@ -288,6 +282,7 @@ GEM diff-lcs (~> 1.1) mime-types (>= 1.16, < 3) posix-spawn (~> 0.3) + gitlab-markup (1.5.0) gitlab_git (10.6.8) activesupport (~> 4.0) charlock_holmes (~> 0.7.3) @@ -870,8 +865,8 @@ DEPENDENCIES gemnasium-gitlab-service (~> 0.2) gemojione (~> 3.0) github-linguist (~> 4.7.0) - github-markup (~> 1.4)! gitlab-flowdock-git-hook (~> 1.0.1) + gitlab-markup (~> 1.5.0) gitlab_git (~> 10.6.8) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) -- cgit v1.2.1 From 1290df050bc338776749f8f1965b44aae6baf726 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 13 Oct 2016 14:00:56 +0200 Subject: Add job to trigger a docs.gitlab.com build --- .gitlab-ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 441f77740a8..62e06683124 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -299,6 +299,14 @@ coverage: - coverage/index.html - coverage/assets/ +# Trigger docs build +trigger_docs: + stage: post-test + script: + - "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master https://gitlab.com/api/v3/projects/38069/trigger/builds" + only: + - master + # Notify slack in the end notify:slack: -- cgit v1.2.1 From 2709f679d07a5fc08f5cc31a416e00e61a04d2c1 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 13 Oct 2016 15:02:05 +0200 Subject: Loosen requirement on request_store version This gem follows semantic versioning and will not introduce breaking changes in a minor version. See https://github.com/steveklabnik/request_store#semantic-versioning Closes https://gitlab.com/gitlab-org/gitlab-ce/issues/2868 --- Gemfile | 2 +- Gemfile.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 230561f90d3..2601c33a5b4 100644 --- a/Gemfile +++ b/Gemfile @@ -225,7 +225,7 @@ 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' -gem 'request_store', '~> 1.3.0' +gem 'request_store', '~> 1.3' gem 'select2-rails', '~> 3.5.9' gem 'virtus', '~> 1.0.1' gem 'net-ssh', '~> 3.0.1' diff --git a/Gemfile.lock b/Gemfile.lock index 067908af3fb..77370e8bdaf 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -937,7 +937,7 @@ DEPENDENCIES redis (~> 3.2) redis-namespace (~> 1.5.2) redis-rails (~> 4.0.0) - request_store (~> 1.3.0) + request_store (~> 1.3) rerun (~> 0.11.0) responders (~> 2.0) rouge (~> 2.0) -- cgit v1.2.1 From 776cea4c00d883cafc2bc5381f3b61b146a93976 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 13 Oct 2016 14:19:52 +0100 Subject: Handle case where deployment ref no longer exists Keep-around refs for deployments were only introduced in 8.10, so any deployment created in 8.9 could have a SHA pointing to a commit that no longer exists in the repository. We can't do anything useful with those deployments, so make `#includes_commit?` always return false for those. --- app/models/deployment.rb | 9 ++++++++- spec/models/deployment_spec.rb | 9 +++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/app/models/deployment.rb b/app/models/deployment.rb index 82b27b78229..f63cc179b9e 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -40,7 +40,14 @@ class Deployment < ActiveRecord::Base def includes_commit?(commit) return false unless commit - project.repository.is_ancestor?(commit.id, sha) + # Before 8.10, deployments didn't have keep-around refs. Any deployment + # created before then could have a `sha` referring to a commit that no + # longer exists in the repository, so just ignore those. + begin + project.repository.is_ancestor?(commit.id, sha) + rescue Rugged::OdbError + false + end end def update_merge_request_metrics! diff --git a/spec/models/deployment_spec.rb b/spec/models/deployment_spec.rb index bfff639ad78..01a4a53a264 100644 --- a/spec/models/deployment_spec.rb +++ b/spec/models/deployment_spec.rb @@ -38,5 +38,14 @@ describe Deployment, models: true do expect(deployment.includes_commit?(commit)).to be true end end + + context 'when the SHA for the deployment does not exist in the repo' do + it 'returns false' do + deployment.update(sha: Gitlab::Git::BLANK_SHA) + commit = project.commit + + expect(deployment.includes_commit?(commit)).to be false + end + end end end -- cgit v1.2.1 From 0f3960cffaf80eccc2b85e4f5d2d8558dfc633f6 Mon Sep 17 00:00:00 2001 From: Clement Ho Date: Thu, 29 Sep 2016 14:28:33 -0500 Subject: Replace unique keyframes mixin with specific keyframe animation names --- CHANGELOG | 1 + app/assets/stylesheets/framework/logo.scss | 78 +++++++++++++--------------- app/assets/stylesheets/framework/mixins.scss | 7 +++ 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1cb9b8acf51..cb17a856d66 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 8.13.0 (unreleased) - AbstractReferenceFilter caches project_refs on RequestStore when active - Speed-up group milestones show page - Log LDAP lookup errors and don't swallow unrelated exceptions. !6103 (Markus Koller) + - Replace unique keyframes mixin with keyframe mixin with specific names (ClemMakesApps) - Add more tests for calendar contribution (ClemMakesApps) - Avoid database queries on Banzai::ReferenceParser::BaseParser for nodes without references - Fix permission for setting an issue's due date diff --git a/app/assets/stylesheets/framework/logo.scss b/app/assets/stylesheets/framework/logo.scss index 3ee3fb4cee5..c214eabcad7 100644 --- a/app/assets/stylesheets/framework/logo.scss +++ b/app/assets/stylesheets/framework/logo.scss @@ -1,15 +1,3 @@ -@mixin unique-keyframes { - $animation-name: unique-id(); - @include webkit-prefix(animation-name, $animation-name); - - @-webkit-keyframes #{$animation-name} { - @content; - } - @keyframes #{$animation-name} { - @content; - } -} - @mixin tanuki-logo-colors($path-color) { fill: $path-color; transition: all 0.8s; @@ -20,28 +8,6 @@ } } -@mixin tanuki-second-highlight-animations($tanuki-color) { - @include unique-keyframes { - 10%, 80% { - fill: #{$tanuki-color} - } - 20%, 90% { - fill: lighten($tanuki-color, 25%); - } - } -} - -@mixin tanuki-forth-highlight-animations($tanuki-color) { - @include unique-keyframes { - 30%, 60% { - fill: #{$tanuki-color}; - } - 40%, 70% { - fill: lighten($tanuki-color, 25%); - } - } -} - .tanuki-logo { .tanuki-left-ear, @@ -67,7 +33,7 @@ } .tanuki-left-cheek { - @include unique-keyframes { + @include include-keyframes(animate-tanuki-left-cheek) { 0%, 10%, 100% { fill: lighten($tanuki-yellow, 25%); } @@ -78,15 +44,29 @@ } .tanuki-left-eye { - @include tanuki-second-highlight-animations($tanuki-orange); + @include include-keyframes(animate-tanuki-left-eye) { + 10%, 80% { + fill: $tanuki-orange; + } + 20%, 90% { + fill: lighten($tanuki-orange, 25%); + } + } } .tanuki-left-ear { - @include tanuki-second-highlight-animations($tanuki-red); + @include include-keyframes(animate-tanuki-left-ear) { + 10%, 80% { + fill: $tanuki-red; + } + 20%, 90% { + fill: lighten($tanuki-red, 25%); + } + } } .tanuki-nose { - @include unique-keyframes { + @include include-keyframes(animate-tanuki-nose) { 20%, 70% { fill: $tanuki-red; } @@ -97,15 +77,29 @@ } .tanuki-right-eye { - @include tanuki-forth-highlight-animations($tanuki-orange); + @include include-keyframes(animate-tanuki-right-eye) { + 30%, 60% { + fill: $tanuki-orange; + } + 40%, 70% { + fill: lighten($tanuki-orange, 25%); + } + } } .tanuki-right-ear { - @include tanuki-forth-highlight-animations($tanuki-red); + @include include-keyframes(animate-tanuki-right-ear) { + 30%, 60% { + fill: $tanuki-red; + } + 40%, 70% { + fill: lighten($tanuki-red, 25%); + } + } } .tanuki-right-cheek { - @include unique-keyframes { + @include include-keyframes(animate-tanuki-right-cheek) { 40% { fill: $tanuki-yellow; } @@ -115,4 +109,4 @@ } } } -} \ No newline at end of file +} diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 1ec08cdef23..5d9f341aa5e 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -95,3 +95,10 @@ @content; } } + +@mixin include-keyframes($animation-name) { + @include webkit-prefix(animation-name, $animation-name); + @include keyframes($animation-name) { + @content; + } +} -- cgit v1.2.1 From bba47886264d0ca7650d2b4b202d69984650b0ae Mon Sep 17 00:00:00 2001 From: Paco Guzman Date: Mon, 10 Oct 2016 09:40:14 +0200 Subject: Extract project#update_merge_requests and SystemHooks to its own worker from GitPushService --- CHANGELOG | 1 + app/models/project.rb | 5 --- app/services/git_push_service.rb | 15 ++------- app/workers/update_merge_requests_worker.rb | 16 +++++++++ spec/models/project_spec.rb | 21 ------------ spec/services/git_push_service_spec.rb | 4 +-- .../merge_requests/refresh_service_spec.rb | 3 +- spec/workers/post_receive_spec.rb | 8 ++++- spec/workers/update_merge_requests_worker_spec.rb | 38 ++++++++++++++++++++++ 9 files changed, 68 insertions(+), 43 deletions(-) create mode 100644 app/workers/update_merge_requests_worker.rb create mode 100644 spec/workers/update_merge_requests_worker_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 4b1ac9d2c3c..472601fa336 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,6 +24,7 @@ v 8.13.0 (unreleased) - Create a new /templates namespace for the /licenses, /gitignores and /gitlab_ci_ymls API endpoints. !5717 (tbalthazar) - Speed-up group milestones show page - Fix inconsistent options dropdown caret on mobile viewports (ClemMakesApps) + - Extract project#update_merge_requests and SystemHooks to its own worker from GitPushService - Don't include archived projects when creating group milestones. !4940 (Jeroen Jacobs) - Add tag shortcut from the Commit page. !6543 - Keep refs for each deployment diff --git a/app/models/project.rb b/app/models/project.rb index 74d54e69648..8c488f4aeef 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -827,11 +827,6 @@ class Project < ActiveRecord::Base end end - def update_merge_requests(oldrev, newrev, ref, user) - MergeRequests::RefreshService.new(self, user). - execute(oldrev, newrev, ref) - end - def valid_repo? repository.exists? rescue diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index c499427605a..e8415862de5 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -63,13 +63,12 @@ class GitPushService < BaseService protected def update_merge_requests - @project.update_merge_requests(params[:oldrev], params[:newrev], params[:ref], current_user) + UpdateMergeRequestsWorker.perform_async(@project.id, current_user.id, params[:oldrev], params[:newrev], params[:ref]) EventCreateService.new.push(@project, current_user, build_push_data) - SystemHooksService.new.execute_hooks(build_push_data_system_hook.dup, :push_hooks) @project.execute_hooks(build_push_data.dup, :push_hooks) @project.execute_services(build_push_data.dup, :push_hooks) - Ci::CreatePipelineService.new(project, current_user, build_push_data).execute + Ci::CreatePipelineService.new(@project, current_user, build_push_data).execute ProjectCacheWorker.perform_async(@project.id) end @@ -148,16 +147,6 @@ class GitPushService < BaseService push_commits) end - def build_push_data_system_hook - @push_data_system ||= Gitlab::DataBuilder::Push.build( - @project, - current_user, - params[:oldrev], - params[:newrev], - params[:ref], - []) - end - def push_to_existing_branch? # Return if this is not a push to a branch (e.g. new commits) Gitlab::Git.branch_ref?(params[:ref]) && !Gitlab::Git.blank_ref?(params[:oldrev]) diff --git a/app/workers/update_merge_requests_worker.rb b/app/workers/update_merge_requests_worker.rb new file mode 100644 index 00000000000..03f0528cdae --- /dev/null +++ b/app/workers/update_merge_requests_worker.rb @@ -0,0 +1,16 @@ +class UpdateMergeRequestsWorker + include Sidekiq::Worker + + def perform(project_id, user_id, oldrev, newrev, ref) + project = Project.find_by(id: project_id) + return unless project + + user = User.find_by(id: user_id) + return unless user + + MergeRequests::RefreshService.new(project, user).execute(oldrev, newrev, ref) + + push_data = Gitlab::DataBuilder::Push.build(project, user, oldrev, newrev, ref, []) + SystemHooksService.new.execute_hooks(push_data, :push_hooks) + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index dae546a0cdc..7435713d4c0 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -219,7 +219,6 @@ describe Project, models: true do describe 'Respond to' do it { is_expected.to respond_to(:url_to_repo) } it { is_expected.to respond_to(:repo_exists?) } - it { is_expected.to respond_to(:update_merge_requests) } it { is_expected.to respond_to(:execute_hooks) } it { is_expected.to respond_to(:owner) } it { is_expected.to respond_to(:path_with_namespace) } @@ -380,26 +379,6 @@ describe Project, models: true do end end - describe '#update_merge_requests' do - let(:project) { create(:project) } - let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } - let(:key) { create(:key, user_id: project.owner.id) } - let(:prev_commit_id) { merge_request.commits.last.id } - let(:commit_id) { merge_request.commits.first.id } - - it 'closes merge request if last commit from source branch was pushed to target branch' do - project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.target_branch}", key.user) - merge_request.reload - expect(merge_request.merged?).to be_truthy - end - - it 'updates merge request commits with new one if pushed to source branch' do - project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.source_branch}", key.user) - merge_request.reload - expect(merge_request.diff_head_sha).to eq(commit_id) - end - end - describe '.find_with_namespace' do context 'with namespace' do before do diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index 8e3e12114f2..dd2a9e9903a 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -184,8 +184,8 @@ describe GitPushService, services: true do context "Updates merge requests" do it "when pushing a new branch for the first time" do - expect(project).to receive(:update_merge_requests). - with(@blankrev, 'newrev', 'refs/heads/master', user) + expect(UpdateMergeRequestsWorker).to receive(:perform_async). + with(project.id, user.id, @blankrev, 'newrev', 'refs/heads/master') execute_service(project, user, @blankrev, 'newrev', 'refs/heads/master' ) end end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb index 5b4e4908add..e515bc9f89c 100644 --- a/spec/services/merge_requests/refresh_service_spec.rb +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -62,7 +62,8 @@ describe MergeRequests::RefreshService, services: true do it { expect(@merge_request.notes).not_to be_empty } it { expect(@merge_request).to be_open } - it { expect(@merge_request.merge_when_build_succeeds).to be_falsey} + it { expect(@merge_request.merge_when_build_succeeds).to be_falsey } + it { expect(@merge_request.diff_head_sha).to eq(@newrev) } it { expect(@fork_merge_request).to be_open } it { expect(@fork_merge_request.notes).to be_empty } it { expect(@build_failed_todo).to be_done } diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index ffeaafe654a..984acdade36 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -92,7 +92,13 @@ describe PostReceive do allow(Project).to receive(:find_with_namespace).and_return(project) expect(project).to receive(:execute_hooks).twice expect(project).to receive(:execute_services).twice - expect(project).to receive(:update_merge_requests) + + PostReceive.new.perform(pwd(project), key_id, base64_changes) + end + + it "enqueues a UpdateMergeRequestsWorker job" do + allow(Project).to receive(:find_with_namespace).and_return(project) + expect(UpdateMergeRequestsWorker).to receive(:perform_async).with(project.id, project.owner.id, any_args) PostReceive.new.perform(pwd(project), key_id, base64_changes) end diff --git a/spec/workers/update_merge_requests_worker_spec.rb b/spec/workers/update_merge_requests_worker_spec.rb new file mode 100644 index 00000000000..c78a69eda67 --- /dev/null +++ b/spec/workers/update_merge_requests_worker_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' + +describe UpdateMergeRequestsWorker do + include RepoHelpers + + let(:project) { create(:project) } + let(:user) { create(:user) } + + subject { described_class.new } + + describe '#perform' do + let(:oldrev) { "123456" } + let(:newrev) { "789012" } + let(:ref) { "refs/heads/test" } + + def perform + subject.perform(project.id, user.id, oldrev, newrev, ref) + end + + it 'executes MergeRequests::RefreshService with expected values' do + expect(MergeRequests::RefreshService).to receive(:new).with(project, user).and_call_original + expect_any_instance_of(MergeRequests::RefreshService).to receive(:execute).with(oldrev, newrev, ref) + + perform + end + + it 'executes SystemHooksService with expected values' do + push_data = double('push_data') + expect(Gitlab::DataBuilder::Push).to receive(:build).with(project, user, oldrev, newrev, ref, []).and_return(push_data) + + system_hook_service = double('system_hook_service') + expect(SystemHooksService).to receive(:new).and_return(system_hook_service) + expect(system_hook_service).to receive(:execute_hooks).with(push_data, :push_hooks) + + perform + end + end +end -- cgit v1.2.1 From 069f2d347585a0f79ab8e3ddfb194ebbc86176c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 29 Sep 2016 19:31:14 +0200 Subject: Draft a quick CE->EE merge check rake task MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .gitlab-ci.yml | 7 +++ lib/tasks/ce_to_ee_merge_check.rake | 6 +++ lib/tasks/gitlab/dev.rake | 96 +++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 lib/tasks/ce_to_ee_merge_check.rake create mode 100644 lib/tasks/gitlab/dev.rake diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7d19f55aca3..7e2d705a888 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -210,6 +210,13 @@ rake brakeman: *exec rake flay: *exec license_finder: *exec rake downtime_check: *exec +rake ce_to_ee_merge_check: + <<: *exec + only: + - branches + except: + - tags + - master rake db:migrate:reset: stage: test diff --git a/lib/tasks/ce_to_ee_merge_check.rake b/lib/tasks/ce_to_ee_merge_check.rake new file mode 100644 index 00000000000..bb87f85cd9c --- /dev/null +++ b/lib/tasks/ce_to_ee_merge_check.rake @@ -0,0 +1,6 @@ +desc 'Checks if the branch would apply cleanly to EE' +task ce_to_ee_merge_check: :environment do + return if defined?(Gitlab::License) + + Rake::Task['gitlab:dev:ce_to_ee_merge_check'].invoke +end diff --git a/lib/tasks/gitlab/dev.rake b/lib/tasks/gitlab/dev.rake new file mode 100644 index 00000000000..bf17ba499bc --- /dev/null +++ b/lib/tasks/gitlab/dev.rake @@ -0,0 +1,96 @@ +namespace :gitlab do + namespace :dev do + desc 'Checks if the branch would apply cleanly to EE' + task ce_to_ee_merge_check: :environment do + ce_repo = ENV['CI_BUILD_REPO'] + ce_branch = ENV['CI_BUILD_REF_NAME'] + + ee_repo = 'https://gitlab.com/gitlab-org/gitlab-ee.git' + ee_branch = "#{ce_branch}-ee" + ee_dir = 'gitlab-ee-merge-check' + + puts "\n=> Cloning #{ee_repo} into #{ee_dir}\n" + `git clone #{ee_repo} #{ee_dir} --depth 1` + Dir.chdir(ee_dir) do + puts "\n => Fetching #{ce_repo}/#{ce_branch}\n" + `git fetch #{ce_repo} #{ce_branch} --depth 1` + + # Try to merge the current tested branch to EE/master... + puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n" + `git merge --ff-only FETCH_HEAD` + + exit 0 if $?.success? + + # Try to merge a possible -ee branch to EE/master... + puts "\n => Merging #{ee_repo}/#{ee_branch} into #{ee_repo}/master\n" + `git merge --ff-only #{ee_branch}` + + # The -ee doesn't exist + if $?.exitstatus == 1 + puts <<-MSG.strip_heredoc + \n================================================================= + The #{ce_branch} branch cannot be merged without conflicts to the + current EE/master, and no #{ee_branch} branch was detected in + the EE repository. + + Please create a #{ee_branch} branch that includes changes + #{ce_branch} but also specific changes than can be applied cleanly + to EE/master. + + You can create this branch as follow: + + 1. In the EE repo: + $ git fetch origin + $ git fetch #{ce_repo} #{ce_branch} + $ git checkout -b #{ee_branch} FETCH_HEAD + $ git rebase origin/master + 2. At this point you will likely have conflicts, solve them, and + continue/finish the rebase. + 3. You can squash all the original #{ce_branch} commits into a + single "Port of #{ce_branch} to EE". + 4. Push your branch to #{ee_repo}: + $ git push origin #{ee_branch} + =================================================================\n + MSG + + exit 1 + end + + # The -ee cannot be merged cleanly to EE/master... + unless $?.success? + puts <<-MSG.strip_heredoc + \n================================================================= + The #{ce_branch} branch cannot be merged without conflicts to + EE/master, and even though the #{ee_branch} branch exists in the EE + repository, it cannot be merged without conflicts to EE/master. + + Please update the #{ee_branch}, push it again to #{ee_repo}, and + retry this job. + =================================================================\n + MSG + + exit 2 + end + + puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n" + `git merge --ff-only FETCH_HEAD` + exit 0 if $?.success? + + # The -ee can be merged cleanly to EE/master, but still + # cannot be merged cleanly to EE/master... + puts <<-MSG.strip_heredoc + \n================================================================= + The #{ce_branch} branch cannot be merged without conflicts to EE, and + even though the #{ee_branch} branch exists in the EE repository and + applies cleanly to EE/master, it doesn't prevent conflicts when + merging #{ce_branch} into EE. + + We may be in a complex situation here. + =================================================================\n + MSG + + exit 3 + end + end + end +end -- cgit v1.2.1 From 2650d5f895c6f7e56f7ba76e5fa448d38fd15d7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 7 Oct 2016 17:21:51 +0200 Subject: Improve the branch existence and merge checks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Also add a safeguard for non-CI env. Signed-off-by: Rémy Coutable --- .gitlab-ci.yml | 2 +- lib/tasks/ce_to_ee_merge_check.rake | 2 -- lib/tasks/gitlab/dev.rake | 35 +++++++++++++++++++++++------------ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7e2d705a888..8708eae1b2a 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -216,7 +216,7 @@ rake ce_to_ee_merge_check: - branches except: - tags - - master + allow_failure: yes rake db:migrate:reset: stage: test diff --git a/lib/tasks/ce_to_ee_merge_check.rake b/lib/tasks/ce_to_ee_merge_check.rake index bb87f85cd9c..424e7883060 100644 --- a/lib/tasks/ce_to_ee_merge_check.rake +++ b/lib/tasks/ce_to_ee_merge_check.rake @@ -1,6 +1,4 @@ desc 'Checks if the branch would apply cleanly to EE' task ce_to_ee_merge_check: :environment do - return if defined?(Gitlab::License) - Rake::Task['gitlab:dev:ce_to_ee_merge_check'].invoke end diff --git a/lib/tasks/gitlab/dev.rake b/lib/tasks/gitlab/dev.rake index bf17ba499bc..47bdb2d32d2 100644 --- a/lib/tasks/gitlab/dev.rake +++ b/lib/tasks/gitlab/dev.rake @@ -2,6 +2,9 @@ namespace :gitlab do namespace :dev do desc 'Checks if the branch would apply cleanly to EE' task ce_to_ee_merge_check: :environment do + return if defined?(Gitlab::License) + return unless ENV['CI'] + ce_repo = ENV['CI_BUILD_REPO'] ce_branch = ENV['CI_BUILD_REF_NAME'] @@ -17,27 +20,28 @@ namespace :gitlab do # Try to merge the current tested branch to EE/master... puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n" - `git merge --ff-only FETCH_HEAD` + `git merge FETCH_HEAD` exit 0 if $?.success? - # Try to merge a possible -ee branch to EE/master... - puts "\n => Merging #{ee_repo}/#{ee_branch} into #{ee_repo}/master\n" - `git merge --ff-only #{ee_branch}` + # Check if the -ee branch exists... + puts "\n => Check if #{ee_repo}/#{ee_branch} exists\n" + `git rev-parse --verify #{ee_branch}` # The -ee doesn't exist - if $?.exitstatus == 1 + unless $?.success? + puts puts <<-MSG.strip_heredoc - \n================================================================= + ================================================================= The #{ce_branch} branch cannot be merged without conflicts to the current EE/master, and no #{ee_branch} branch was detected in the EE repository. - Please create a #{ee_branch} branch that includes changes + Please create a #{ee_branch} branch that includes changes from #{ce_branch} but also specific changes than can be applied cleanly to EE/master. - You can create this branch as follow: + You can create this branch as follows: 1. In the EE repo: $ git fetch origin @@ -45,7 +49,8 @@ namespace :gitlab do $ git checkout -b #{ee_branch} FETCH_HEAD $ git rebase origin/master 2. At this point you will likely have conflicts, solve them, and - continue/finish the rebase. + continue/finish the rebase. Note: You can squash the CE commits + before rebasing. 3. You can squash all the original #{ce_branch} commits into a single "Port of #{ce_branch} to EE". 4. Push your branch to #{ee_repo}: @@ -56,10 +61,15 @@ namespace :gitlab do exit 1 end + # Try to merge the -ee branch to EE/master... + puts "\n => Merging #{ee_repo}/#{ee_branch} into #{ee_repo}/master\n" + `git merge #{ee_branch} master` + # The -ee cannot be merged cleanly to EE/master... unless $?.success? + puts puts <<-MSG.strip_heredoc - \n================================================================= + ================================================================= The #{ce_branch} branch cannot be merged without conflicts to EE/master, and even though the #{ee_branch} branch exists in the EE repository, it cannot be merged without conflicts to EE/master. @@ -73,13 +83,14 @@ namespace :gitlab do end puts "\n => Merging #{ce_repo}/#{ce_branch} into #{ee_repo}/master\n" - `git merge --ff-only FETCH_HEAD` + `git merge FETCH_HEAD` exit 0 if $?.success? # The -ee can be merged cleanly to EE/master, but still # cannot be merged cleanly to EE/master... + puts puts <<-MSG.strip_heredoc - \n================================================================= + ================================================================= The #{ce_branch} branch cannot be merged without conflicts to EE, and even though the #{ee_branch} branch exists in the EE repository and applies cleanly to EE/master, it doesn't prevent conflicts when -- cgit v1.2.1 From e836b904b77e857ad2e1a0bc65129380d5f0e6dc Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Thu, 13 Oct 2016 17:52:21 +0200 Subject: Grapify system hooks API --- lib/api/system_hooks.rb | 60 ++++++++++++++++++++++++------------------------- 1 file changed, 29 insertions(+), 31 deletions(-) diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb index 22b8f90dc5c..2e76b91051f 100644 --- a/lib/api/system_hooks.rb +++ b/lib/api/system_hooks.rb @@ -7,38 +7,36 @@ module API end resource :hooks do - # Get the list of system hooks - # - # Example Request: - # GET /hooks + desc 'Get the list of system hooks' do + success Entities::Hook + end get do - @hooks = SystemHook.all - present @hooks, with: Entities::Hook + hooks = SystemHook.all + present hooks, with: Entities::Hook end - # Create new system hook - # - # Parameters: - # url (required) - url for system hook - # Example Request - # POST /hooks + desc 'Create a new system hook' do + success Entities::Hook + end + params do + requires :url, type: String, desc: 'The URL for the system hook' + end post do - attrs = attributes_for_keys [:url] - required_attributes! [:url] - @hook = SystemHook.new attrs - if @hook.save - present @hook, with: Entities::Hook + hook = SystemHook.new declared(params).to_h + + if hook.save + present hook, with: Entities::Hook else not_found! end end - # Test a hook - # - # Example Request - # GET /hooks/:id + desc 'Test a hook' + params do + requires :id, type: Integer, desc: 'The ID of the system hook' + end get ":id" do - @hook = SystemHook.find(params[:id]) + hook = SystemHook.find(params[:id]) data = { event_name: "project_create", name: "Ruby", @@ -47,20 +45,20 @@ module API owner_name: "Someone", owner_email: "example@gitlabhq.com" } - @hook.execute(data, 'system_hooks') + hook.execute(data, 'system_hooks') data end - # Delete a hook. This is an idempotent function. - # - # Parameters: - # id (required) - ID of the hook - # Example Request: - # DELETE /hooks/:id + desc 'Delete a hook' do + success Entities::Hook + end + params do + requires :id, type: Integer, desc: 'The ID of the system hook' + end delete ":id" do begin - @hook = SystemHook.find(params[:id]) - @hook.destroy + hook = SystemHook.find(params[:id]) + present hook.destroy, with: Entities::Hook rescue # SystemHook raises an Error if no hook with id found end -- cgit v1.2.1 From 36309374df8feeea99c4eef5b0fd1e27d41ff6c9 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Thu, 13 Oct 2016 19:16:45 +0200 Subject: Do not run before_script, artifacts, cache in trigger_docs job --- .gitlab-ci.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 7d19f55aca3..6b54d07d1d8 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -302,6 +302,9 @@ coverage: # Trigger docs build trigger_docs: stage: post-test + before_script: [] + cache: {} + artifacts: {} script: - "curl -X POST -F token=${DOCS_TRIGGER_TOKEN} -F ref=master https://gitlab.com/api/v3/projects/38069/trigger/builds" only: -- cgit v1.2.1 From d00f17c0108c73b521b533bad7d2d7706cb25a5c Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Thu, 13 Oct 2016 21:56:28 +0000 Subject: fix grafana_configuration.md move link --- doc/monitoring/performance/grafana_configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/monitoring/performance/grafana_configuration.md b/doc/monitoring/performance/grafana_configuration.md index 93320b40174..0d4be02ff5f 100644 --- a/doc/monitoring/performance/grafana_configuration.md +++ b/doc/monitoring/performance/grafana_configuration.md @@ -1 +1 @@ -This document was moved to [administration/monitoring/performance/grafana_configuration](../administration/monitoring/performance/grafana_configuration.md). +This document was moved to [administration/monitoring/performance/grafana_configuration](../../administration/monitoring/performance/grafana_configuration.md). -- cgit v1.2.1 From 8ebc0919ea7a0647b5b5a0951cd3ce2070d78bb2 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 14 Oct 2016 08:26:40 +0200 Subject: Link to review apps example from docs [ci skip] --- doc/ci/examples/README.md | 1 + doc/ci/yaml/README.md | 2 ++ 2 files changed, 3 insertions(+) diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 08fbd9afa2f..ffc310ec8c7 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -13,6 +13,7 @@ Apart from those, here is an collection of tutorials and guides on setting up yo - [Test a Scala application](test-scala-application.md) - [Test a Phoenix application](test-phoenix-application.md) - [Using `dpl` as deployment tool](deployment/README.md) +- [Example project that shows how to use Review Apps](https://gitlab.com/gitlab-examples/review-apps-nginx/) - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) - [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples) - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index cdf5ecc7a84..59399861a97 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -594,6 +594,8 @@ create the `review-apps/branch-name` environment. This environment should be accessible under `https://branch-name.review.example.com/`. +You can see a simple example at https://gitlab.com/gitlab-examples/review-apps-nginx/. + ### artifacts >**Notes:** -- cgit v1.2.1 From b927473c45e42e99adbbe69b71653f1b2981df01 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 14 Oct 2016 09:16:55 +0200 Subject: Grapify todos API --- lib/api/todos.rb | 45 +++++++++++++++++++-------------------------- 1 file changed, 19 insertions(+), 26 deletions(-) diff --git a/lib/api/todos.rb b/lib/api/todos.rb index 19df13d8aac..832b04a3bb1 100644 --- a/lib/api/todos.rb +++ b/lib/api/todos.rb @@ -8,18 +8,19 @@ module API 'issues' => ->(id) { find_project_issue(id) } } + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do ISSUABLE_TYPES.each do |type, finder| type_id_str = "#{type.singularize}_id".to_sym - # Create a todo on an issuable - # - # Parameters: - # id (required) - The ID of a project - # issuable_id (required) - The ID of an issuable - # Example Request: - # POST /projects/:id/issues/:issuable_id/todo - # POST /projects/:id/merge_requests/:issuable_id/todo + desc 'Create a todo on an issuable' do + success Entities::Todo + end + params do + requires type_id_str, type: Integer, desc: 'The ID of an issuable' + end post ":id/#{type}/:#{type_id_str}/todo" do issuable = instance_exec(params[type_id_str], &finder) todo = TodoService.new.mark_todo(issuable, current_user).first @@ -40,25 +41,21 @@ module API end end - # Get a todo list - # - # Example Request: - # GET /todos - # + desc 'Get a todo list' do + success Entities::Todo + end get do todos = find_todos present paginate(todos), with: Entities::Todo, current_user: current_user end - # Mark a todo as done - # - # Parameters: - # id: (required) - The ID of the todo being marked as done - # - # Example Request: - # DELETE /todos/:id - # + desc 'Mark a todo as done' do + success Entities::Todo + end + params do + requires :id, type: Integer, desc: 'The ID of the todo being marked as done' + end delete ':id' do todo = current_user.todos.find(params[:id]) TodoService.new.mark_todos_as_done([todo], current_user) @@ -66,11 +63,7 @@ module API present todo.reload, with: Entities::Todo, current_user: current_user end - # Mark all todos as done - # - # Example Request: - # DELETE /todos - # + desc 'Mark all todos as done' delete do todos = find_todos TodoService.new.mark_todos_as_done(todos, current_user) -- cgit v1.2.1 From c850651b9174a40376dc5b712ce696406adedeab Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 14 Oct 2016 10:07:36 +0200 Subject: Add link to update docs for source installations --- doc/user/project/new_ci_build_permissions_model.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/user/project/new_ci_build_permissions_model.md b/doc/user/project/new_ci_build_permissions_model.md index 19385a99d64..8827b501901 100644 --- a/doc/user/project/new_ci_build_permissions_model.md +++ b/doc/user/project/new_ci_build_permissions_model.md @@ -162,7 +162,7 @@ As an administrator: - **500 errors**: You will need to update [GitLab Workhorse][workhorse] to at least 0.8.2. This is done automatically for Omnibus installations, you need to - check manually for installations from source. + [check manually][update-docs] for installations from source. - **500 errors**: Check if you have another web proxy sitting in front of NGINX (HAProxy, Apache, etc.). It might be a good idea to let GitLab use the internal NGINX web server and not disable it completely. See [this comment][comment] for an @@ -307,4 +307,5 @@ test: [git-scm]: https://git-scm.com/book/en/v2/Git-Tools-Submodules [https]: ../admin_area/settings/visibility_and_access_controls.md#enabled-git-access-protocols [triggers]: ../../ci/triggers/README.md +[update-docs]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update [workhorse]: https://gitlab.com/gitlab-org/gitlab-workhorse -- cgit v1.2.1 From e8c9ccc2d8f85e22bb4969cb544f9de25f4c08f5 Mon Sep 17 00:00:00 2001 From: Achilleas Pipinellis Date: Fri, 14 Oct 2016 10:25:11 +0200 Subject: Refactor merge requests revisions - A system note now appears on every push - Replace dashes with underscores in images [ci skip] --- .../merge_requests/img/versions-compare.png | Bin 68722 -> 0 bytes .../merge_requests/img/versions-dropdown.png | Bin 60587 -> 0 bytes .../merge_requests/img/versions_compare.png | Bin 0 -> 68722 bytes .../merge_requests/img/versions_dropdown.png | Bin 0 -> 60587 bytes .../merge_requests/img/versions_system_note.png | Bin 0 -> 18731 bytes doc/user/project/merge_requests/versions.md | 30 ++++++++++++++------- 6 files changed, 20 insertions(+), 10 deletions(-) delete mode 100644 doc/user/project/merge_requests/img/versions-compare.png delete mode 100644 doc/user/project/merge_requests/img/versions-dropdown.png create mode 100644 doc/user/project/merge_requests/img/versions_compare.png create mode 100644 doc/user/project/merge_requests/img/versions_dropdown.png create mode 100644 doc/user/project/merge_requests/img/versions_system_note.png diff --git a/doc/user/project/merge_requests/img/versions-compare.png b/doc/user/project/merge_requests/img/versions-compare.png deleted file mode 100644 index 890cae7768c..00000000000 Binary files a/doc/user/project/merge_requests/img/versions-compare.png and /dev/null differ diff --git a/doc/user/project/merge_requests/img/versions-dropdown.png b/doc/user/project/merge_requests/img/versions-dropdown.png deleted file mode 100644 index 9bab9304e14..00000000000 Binary files a/doc/user/project/merge_requests/img/versions-dropdown.png and /dev/null differ diff --git a/doc/user/project/merge_requests/img/versions_compare.png b/doc/user/project/merge_requests/img/versions_compare.png new file mode 100644 index 00000000000..890cae7768c Binary files /dev/null and b/doc/user/project/merge_requests/img/versions_compare.png differ diff --git a/doc/user/project/merge_requests/img/versions_dropdown.png b/doc/user/project/merge_requests/img/versions_dropdown.png new file mode 100644 index 00000000000..9bab9304e14 Binary files /dev/null and b/doc/user/project/merge_requests/img/versions_dropdown.png differ diff --git a/doc/user/project/merge_requests/img/versions_system_note.png b/doc/user/project/merge_requests/img/versions_system_note.png new file mode 100644 index 00000000000..7c9d7715745 Binary files /dev/null and b/doc/user/project/merge_requests/img/versions_system_note.png differ diff --git a/doc/user/project/merge_requests/versions.md b/doc/user/project/merge_requests/versions.md index 2805fdf635c..77eab7ba5e3 100644 --- a/doc/user/project/merge_requests/versions.md +++ b/doc/user/project/merge_requests/versions.md @@ -7,26 +7,36 @@ of merge request diff is created. When you visit a merge request that contains more than one pushes, you can select and compare the versions of those merge request diffs. -![Merge Request Versions](img/versions.png) +![Merge request versions](img/versions.png) + +--- By default, the latest version of changes is shown. However, you can select an older one from version dropdown. -![Merge Request Versions](img/versions-dropdown.png) +![Merge request versions dropdown](img/versions_dropdown.png) + +--- -You can also compare the merge request version with older one to see what is +You can also compare the merge request version with an older one to see what has changed since then. -![Merge Request Versions](img/versions-compare.png) +![Merge request versions compare](img/versions_compare.png) + +--- + +Every time you push new changes to the branch, a link to compare the last +changes appears as a system note. -Please note that comments are disabled while viewing outdated merge versions -or comparing to versions other than base. +![Merge request versions system note](img/versions_system_note.png) --- ->**Note:** -Merge request versions are based on push not on commit. So, if you pushed 5 -commits in a single push, it will be a single option in the dropdown. If you -pushed 5 times, that will count for 5 options. +>**Notes:** +- Comments are disabled while viewing outdated merge versions or comparing to + versions other than base. +- Merge request versions are based on push not on commit. So, if you pushed 5 + commits in a single push, it will be a single option in the dropdown. If you + pushed 5 times, that will count for 5 options. [ce-5467]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/5467 -- cgit v1.2.1 From 04147f8773992b50c372160ed73ce8e7674d77d9 Mon Sep 17 00:00:00 2001 From: Sean Packham Date: Fri, 14 Oct 2016 09:56:47 +0100 Subject: Fixed missing links --- doc/university/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/university/README.md b/doc/university/README.md index 8b3538d5616..d5bb7299ecf 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -67,18 +67,18 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project #### 1.7 Community and Support -1. [Getting Help](/getting-help/) +1. [Getting Help](https://about.gitlab.com/getting-help/) - Proposing Features and Reporting and Tracking bugs for GitLab - The GitLab IRC channel, Gitter Chat Room, Community Forum and Mailing List - Getting Technical Support - Being part of our Great Community and Contributing to GitLab 1. [Getting Started with the GitLab Development Kit (GDK)](https://about.gitlab.com/2016/06/08/getting-started-with-gitlab-development-kit/) 1. [Contributing Technical Articles to the GitLab Blog](https://about.gitlab.com/2016/01/26/call-for-writers/) -1. [GitLab Training Workshops](/training) +1. [GitLab Training Workshops](https://about.gitlab.com/getting-help/training) #### 1.8 GitLab Training Material -1. [Git and GitLab Terminology](/glossary/) +1. [Git and GitLab Terminology](glossary/README.md) 1. [Git and GitLab Workshop - Slides](https://docs.google.com/presentation/d/1JzTYD8ij9slejV2-TO-NzjCvlvj6mVn9BORePXNJoMI/edit?usp=drive_web) 1. [Git and GitLab Revision](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/university/training/end-user) @@ -209,7 +209,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project *Some content can only be accessed by GitLab team members* -1. [Support Path](/support/) +1. [Support Path](support/README.md) 1. [Sales Path (redirect to sales handbook)](https://about.gitlab.com/handbook/sales-onboarding/) 1. [GitLab architecture for noobs](https://dev.gitlab.org/gitlab/gitlabhq/blob/master/doc/development/architecture.md) 1. [Client Assessment of GitLab versus GitHub](https://docs.google.com/a/gitlab.com/spreadsheets/d/18cRF9Y5I6I7Z_ab6qhBEW55YpEMyU4PitZYjomVHM-M/edit?usp=sharing) -- cgit v1.2.1 From 75e12ca9880e900c8d0b29c973bdef40c6757c6f Mon Sep 17 00:00:00 2001 From: Sean Packham Date: Fri, 14 Oct 2016 10:00:02 +0100 Subject: Fixed missing links --- doc/university/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/university/README.md b/doc/university/README.md index d5bb7299ecf..e71e49c33c8 100644 --- a/doc/university/README.md +++ b/doc/university/README.md @@ -74,7 +74,7 @@ The curriculum is composed of GitLab videos, screencasts, presentations, project - Being part of our Great Community and Contributing to GitLab 1. [Getting Started with the GitLab Development Kit (GDK)](https://about.gitlab.com/2016/06/08/getting-started-with-gitlab-development-kit/) 1. [Contributing Technical Articles to the GitLab Blog](https://about.gitlab.com/2016/01/26/call-for-writers/) -1. [GitLab Training Workshops](https://about.gitlab.com/getting-help/training) +1. [GitLab Training Workshops](https://about.gitlab.com/training) #### 1.8 GitLab Training Material -- cgit v1.2.1 From 6a4f71008390752e6b5574a9e9bdf277732d852d Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 4 Oct 2016 16:03:13 +0200 Subject: Show what time ago a MR was deployed --- CHANGELOG | 1 + app/assets/javascripts/merge_request_widget.js | 193 ----------------- app/assets/javascripts/merge_request_widget.js.es6 | 240 +++++++++++++++++++++ app/assets/stylesheets/pages/merge_requests.scss | 4 + .../projects/merge_requests_controller.rb | 31 ++- app/models/environment.rb | 8 + app/models/merge_request.rb | 9 + app/models/repository.rb | 8 + .../merge_requests/widget/_heading.html.haml | 16 +- .../projects/merge_requests/widget/_show.html.haml | 2 +- .../merge_requests/widget_deployments_spec.rb | 26 +++ spec/javascripts/merge_request_widget_spec.js | 27 ++- spec/models/environment_spec.rb | 17 ++ spec/models/repository_spec.rb | 23 ++ .../merge_requests/_heading.html.haml_spec.rb | 28 --- 15 files changed, 391 insertions(+), 242 deletions(-) delete mode 100644 app/assets/javascripts/merge_request_widget.js create mode 100644 app/assets/javascripts/merge_request_widget.js.es6 create mode 100644 spec/features/merge_requests/widget_deployments_spec.rb delete mode 100644 spec/views/projects/merge_requests/_heading.html.haml_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 99bbd99726d..348ac41a534 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -50,6 +50,7 @@ v 8.13.0 (unreleased) - Add new issue button to each list on Issues Board - Added soft wrap button to repository file/blob editor - Update namespace validation to forbid reserved names (.git and .atom) (Will Starms) + - Show the time ago a merge request was deployed to an environment - Add word-wrap to issue title on issue and milestone boards (ClemMakesApps) - Fix todos page mobile viewport layout (ClemMakesApps) - Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps) diff --git a/app/assets/javascripts/merge_request_widget.js b/app/assets/javascripts/merge_request_widget.js deleted file mode 100644 index 7bbcdf59838..00000000000 --- a/app/assets/javascripts/merge_request_widget.js +++ /dev/null @@ -1,193 +0,0 @@ -(function() { - var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; - - this.MergeRequestWidget = (function() { - function MergeRequestWidget(opts) { - // Initialize MergeRequestWidget behavior - // - // check_enable - Boolean, whether to check automerge status - // merge_check_url - String, URL to use to check automerge status - // ci_status_url - String, URL to use to check CI status - // - this.opts = opts; - $('#modal_merge_info').modal({ - show: false - }); - this.firstCICheck = true; - this.readyForCICheck = false; - this.cancel = false; - clearInterval(this.fetchBuildStatusInterval); - this.clearEventListeners(); - this.addEventListeners(); - this.getCIStatus(false); - this.pollCIStatus(); - notifyPermissions(); - } - - MergeRequestWidget.prototype.clearEventListeners = function() { - return $(document).off('page:change.merge_request'); - }; - - MergeRequestWidget.prototype.cancelPolling = function() { - return this.cancel = true; - }; - - MergeRequestWidget.prototype.addEventListeners = function() { - var allowedPages; - allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes']; - return $(document).on('page:change.merge_request', (function(_this) { - return function() { - var page; - page = $('body').data('page').split(':').last(); - if (allowedPages.indexOf(page) < 0) { - clearInterval(_this.fetchBuildStatusInterval); - _this.cancelPolling(); - return _this.clearEventListeners(); - } - }; - })(this)); - }; - - MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) { - if (deleteSourceBranch == null) { - deleteSourceBranch = false; - } - return $.ajax({ - type: 'GET', - url: $('.merge-request').data('url'), - success: (function(_this) { - return function(data) { - var callback, urlSuffix; - if (data.state === "merged") { - urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : ''; - return window.location.href = window.location.pathname + urlSuffix; - } else if (data.merge_error) { - return $('.mr-widget-body').html("

" + data.merge_error + "

"); - } else { - callback = function() { - return merge_request_widget.mergeInProgress(deleteSourceBranch); - }; - return setTimeout(callback, 2000); - } - }; - })(this), - dataType: 'json' - }); - }; - - MergeRequestWidget.prototype.getMergeStatus = function() { - return $.get(this.opts.merge_check_url, function(data) { - return $('.mr-state-widget').replaceWith(data); - }); - }; - - MergeRequestWidget.prototype.ciLabelForStatus = function(status) { - switch (status) { - case 'success': - return 'passed'; - case 'success_with_warnings': - return 'passed with warnings'; - default: - return status; - } - }; - - MergeRequestWidget.prototype.pollCIStatus = function() { - return this.fetchBuildStatusInterval = setInterval(((function(_this) { - return function() { - if (!_this.readyForCICheck) { - return; - } - _this.getCIStatus(true); - return _this.readyForCICheck = false; - }; - })(this)), 10000); - }; - - MergeRequestWidget.prototype.getCIStatus = function(showNotification) { - var _this; - _this = this; - $('.ci-widget-fetching').show(); - return $.getJSON(this.opts.ci_status_url, (function(_this) { - return function(data) { - var message, status, title; - if (_this.cancel) { - return; - } - _this.readyForCICheck = true; - if (data.status === '') { - return; - } - if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) { - _this.opts.ci_status = data.status; - _this.showCIStatus(data.status); - if (data.coverage) { - _this.showCICoverage(data.coverage); - } - // The first check should only update the UI, a notification - // should only be displayed on status changes - if (showNotification && !_this.firstCICheck) { - status = _this.ciLabelForStatus(data.status); - if (status === "preparing") { - title = _this.opts.ci_title.preparing; - status = status.charAt(0).toUpperCase() + status.slice(1); - message = _this.opts.ci_message.preparing.replace('{{status}}', status); - } else { - title = _this.opts.ci_title.normal; - message = _this.opts.ci_message.normal.replace('{{status}}', status); - } - title = title.replace('{{status}}', status); - message = message.replace('{{sha}}', data.sha); - message = message.replace('{{title}}', data.title); - notify(title, message, _this.opts.gitlab_icon, function() { - this.close(); - return Turbolinks.visit(_this.opts.builds_path); - }); - } - return _this.firstCICheck = false; - } - }; - })(this)); - }; - - MergeRequestWidget.prototype.showCIStatus = function(state) { - var allowed_states; - if (state == null) { - return; - } - $('.ci_widget').hide(); - allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"]; - if (indexOf.call(allowed_states, state) >= 0) { - $('.ci_widget.ci-' + state).show(); - switch (state) { - case "failed": - case "canceled": - case "not_found": - return this.setMergeButtonClass('btn-danger'); - case "running": - return this.setMergeButtonClass('btn-warning'); - case "success": - case "success_with_warnings": - return this.setMergeButtonClass('btn-create'); - } - } else { - $('.ci_widget.ci-error').show(); - return this.setMergeButtonClass('btn-danger'); - } - }; - - MergeRequestWidget.prototype.showCICoverage = function(coverage) { - var text; - text = 'Coverage ' + coverage + '%'; - return $('.ci_widget:visible .ci-coverage').text(text); - }; - - MergeRequestWidget.prototype.setMergeButtonClass = function(css_class) { - return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-warning btn-create').addClass(css_class); - }; - - return MergeRequestWidget; - - })(); - -}).call(this); diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 new file mode 100644 index 00000000000..7c727cf214f --- /dev/null +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -0,0 +1,240 @@ + ((global) => { + var indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }; + + const DEPLOYMENT_TEMPLATE = `
+
+ <%= ci_success_icon %> + + Deployed to + + <%- name %> + + + <%- deployed_at %> + + + + View on <%- external_url_formatted %> + + +
+
`; + + global.MergeRequestWidget = (function() { + function MergeRequestWidget(opts) { + // Initialize MergeRequestWidget behavior + // + // check_enable - Boolean, whether to check automerge status + // merge_check_url - String, URL to use to check automerge status + // ci_status_url - String, URL to use to check CI status + // + this.opts = opts; + this.$widgetBody = $('.mr-widget-body'); + $('#modal_merge_info').modal({ + show: false + }); + this.firstCICheck = true; + this.readyForCICheck = false; + this.cancel = false; + clearInterval(this.fetchBuildStatusInterval); + this.clearEventListeners(); + this.addEventListeners(); + this.getCIStatus(false); + this.retrieveSuccessIcon(); + this.pollCIStatus(); + notifyPermissions(); + } + + MergeRequestWidget.prototype.clearEventListeners = function() { + return $(document).off('page:change.merge_request'); + }; + + MergeRequestWidget.prototype.cancelPolling = function() { + return this.cancel = true; + }; + + MergeRequestWidget.prototype.addEventListeners = function() { + var allowedPages; + allowedPages = ['show', 'commits', 'builds', 'pipelines', 'changes']; + return $(document).on('page:change.merge_request', (function(_this) { + return function() { + var page; + page = $('body').data('page').split(':').last(); + if (allowedPages.indexOf(page) < 0) { + clearInterval(_this.fetchBuildStatusInterval); + _this.cancelPolling(); + return _this.clearEventListeners(); + } + }; + })(this)); + }; + + MergeRequestWidget.prototype.retrieveSuccessIcon = function() { + const $ciSuccessIcon = $('.js-success-icon'); + this.$ciSuccessIcon = $ciSuccessIcon.html(); + $ciSuccessIcon.remove(); + } + + MergeRequestWidget.prototype.mergeInProgress = function(deleteSourceBranch) { + if (deleteSourceBranch == null) { + deleteSourceBranch = false; + } + return $.ajax({ + type: 'GET', + url: $('.merge-request').data('url'), + success: (function(_this) { + return function(data) { + var callback, urlSuffix; + if (data.state === "merged") { + urlSuffix = deleteSourceBranch ? '?deleted_source_branch=true' : ''; + return window.location.href = window.location.pathname + urlSuffix; + } else if (data.merge_error) { + return this.$widgetBody.html("

" + data.merge_error + "

"); + } else { + callback = function() { + return merge_request_widget.mergeInProgress(deleteSourceBranch); + }; + return setTimeout(callback, 2000); + } + }; + })(this), + dataType: 'json' + }); + }; + + MergeRequestWidget.prototype.getMergeStatus = function() { + return $.get(this.opts.merge_check_url, function(data) { + return $('.mr-state-widget').replaceWith(data); + }); + }; + + MergeRequestWidget.prototype.ciLabelForStatus = function(status) { + switch (status) { + case 'success': + return 'passed'; + case 'success_with_warnings': + return 'passed with warnings'; + default: + return status; + } + }; + + MergeRequestWidget.prototype.pollCIStatus = function() { + return this.fetchBuildStatusInterval = setInterval(((function(_this) { + return function() { + if (!_this.readyForCICheck) { + return; + } + _this.getCIStatus(true); + return _this.readyForCICheck = false; + }; + })(this)), 10000); + }; + + MergeRequestWidget.prototype.getCIStatus = function(showNotification) { + var _this; + _this = this; + $('.ci-widget-fetching').show(); + return $.getJSON(this.opts.ci_status_url, (function(_this) { + return function(data) { + var message, status, title; + if (_this.cancel) { + return; + } + _this.readyForCICheck = true; + if (data.status === '') { + return; + } + if (data.environments && data.environments.length) _this.renderEnvironments(data.environments); + if (_this.firstCICheck || data.status !== _this.opts.ci_status && (data.status != null)) { + _this.opts.ci_status = data.status; + _this.showCIStatus(data.status); + if (data.coverage) { + _this.showCICoverage(data.coverage); + } + // The first check should only update the UI, a notification + // should only be displayed on status changes + if (showNotification && !_this.firstCICheck) { + status = _this.ciLabelForStatus(data.status); + if (status === "preparing") { + title = _this.opts.ci_title.preparing; + status = status.charAt(0).toUpperCase() + status.slice(1); + message = _this.opts.ci_message.preparing.replace('{{status}}', status); + } else { + title = _this.opts.ci_title.normal; + message = _this.opts.ci_message.normal.replace('{{status}}', status); + } + title = title.replace('{{status}}', status); + message = message.replace('{{sha}}', data.sha); + message = message.replace('{{title}}', data.title); + notify(title, message, _this.opts.gitlab_icon, function() { + this.close(); + return Turbolinks.visit(_this.opts.builds_path); + }); + } + return _this.firstCICheck = false; + } + }; + })(this)); + }; + + MergeRequestWidget.prototype.renderEnvironments = function(environments) { + for (let i = 0; i < environments.length; i++) { + const environment = environments[i]; + if ($(`.mr-state-widget #${ environment.id }`).length) return; + const $template = $(DEPLOYMENT_TEMPLATE); + if (!environment.external_url) $('.js-environment-link', $template).remove(); + if (environment.deployed_at) { + environment.deployed_at = $.timeago(environment.deployed_at) + '.'; + } else { + $('.js-environment-timeago', $template).remove(); + environment.name += '.'; + } + environment.ci_success_icon = this.$ciSuccessIcon; + const templateString = _.unescape($template[0].outerHTML); + const template = _.template(templateString)(environment) + this.$widgetBody.before(template); + } + }; + + MergeRequestWidget.prototype.showCIStatus = function(state) { + var allowed_states; + if (state == null) { + return; + } + $('.ci_widget').hide(); + allowed_states = ["failed", "canceled", "running", "pending", "success", "success_with_warnings", "skipped", "not_found"]; + if (indexOf.call(allowed_states, state) >= 0) { + $('.ci_widget.ci-' + state).show(); + switch (state) { + case "failed": + case "canceled": + case "not_found": + return this.setMergeButtonClass('btn-danger'); + case "running": + return this.setMergeButtonClass('btn-warning'); + case "success": + case "success_with_warnings": + return this.setMergeButtonClass('btn-create'); + } + } else { + $('.ci_widget.ci-error').show(); + return this.setMergeButtonClass('btn-danger'); + } + }; + + MergeRequestWidget.prototype.showCICoverage = function(coverage) { + var text; + text = 'Coverage ' + coverage + '%'; + return $('.ci_widget:visible .ci-coverage').text(text); + }; + + MergeRequestWidget.prototype.setMergeButtonClass = function(css_class) { + return $('.js-merge-button,.accept-action .dropdown-toggle').removeClass('btn-danger btn-warning btn-create').addClass(css_class); + }; + + return MergeRequestWidget; + + })(); + + })(window.gl || (window.gl = {})); diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 7cf69c56d15..96d5547154d 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -121,6 +121,10 @@ color: #5c5d5e; } + .js-deployment-link { + display: inline-block; + } + .mr-widget-body { h4 { font-weight: 600; diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 869d96b86f4..28225fbb762 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -393,11 +393,40 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end + environments = @merge_request.environments + deployments = @merge_request.deployments + + if environments.present? + environments = environments.select { |e| can?(current_user, :read_environment, e) }.map do |environment| + project = environment.project + deployment = deployments.find { |d| d.environment == environment } + + environment = { + name: environment.name, + id: environment.id, + url: namespace_project_environment_path(project.namespace, project, environment), + external_url: environment.external_url, + deployed_at: deployment ? deployment.created_at : nil + } + + if environment[:external_url] + environment[:external_url_formatted] = environment[:external_url].gsub(/\A.*?:\/\//, '') + end + + if environment[:deployed_at] + environment[:deployed_at_formatted] = environment[:deployed_at].to_time.in_time_zone.to_s(:medium) + end + + environment + end + end + response = { title: merge_request.title, sha: merge_request.diff_head_commit.short_id, status: status, - coverage: coverage + coverage: coverage, + environments: environments } render json: response diff --git a/app/models/environment.rb b/app/models/environment.rb index f0f3ee23223..1c7d06906f3 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -48,6 +48,14 @@ class Environment < ActiveRecord::Base self.name == "production" end + def deployment_id_for(commit) + ref = project.repository.ref_name_for_sha(ref_path, commit.sha) + + return nil unless ref + + ref.split('/').last.to_i + end + def ref_path "refs/environments/#{Shellwords.shellescape(name)}" end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index a743bf313ae..ec8c09f83db 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -685,6 +685,15 @@ class MergeRequest < ActiveRecord::Base !pipeline || pipeline.success? end + def deployments + deployment_ids = + environments.map do |environment| + environment.deployment_id_for(diff_head_commit) + end.compact + + Deployments.find(deployment_ids) + end + def environments return [] unless diff_head_commit diff --git a/app/models/repository.rb b/app/models/repository.rb index 608c99eed46..37833cf004f 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -719,6 +719,14 @@ class Repository end end + def ref_name_for_sha(environment_ref_path, sha) + args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{environment_ref_path} --contains #{sha}) + + # Not found -> ["", 0] + # Found -> ["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0] + Gitlab::Popen.popen(args, path_to_repo).first.split.last + end + def refs_contains_sha(ref_type, sha) args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha}) names = Gitlab::Popen.popen(args, path_to_repo).first diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index 5b7f83c344f..cda8f0b7de6 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -44,17 +44,5 @@ = icon("times-circle") Could not connect to the CI server. Please check your settings and try again. -- @merge_request.environments.sort_by(&:name).each do |environment| - - if can?(current_user, :read_environment, environment) - .mr-widget-heading - .ci_widget.ci-success - = ci_icon_for_status("success") - %span - Deployed to - = succeed '.' do - = link_to environment.name, environment_path(environment), class: 'environment' - - external_url = environment.external_url - - if external_url - = link_to external_url, target: '_blank' do - %span.hidden-xs View on #{external_url.gsub(/\A.*?:\/\//, '')} - = icon('external-link', right: true) + .js-success-icon.hidden + = ci_icon_for_status('success') diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index ea618263a4a..856ec1e0bee 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -33,4 +33,4 @@ merge_request_widget.clearEventListeners(); } - merge_request_widget = new MergeRequestWidget(opts); + merge_request_widget = new window.gl.MergeRequestWidget(opts); diff --git a/spec/features/merge_requests/widget_deployments_spec.rb b/spec/features/merge_requests/widget_deployments_spec.rb new file mode 100644 index 00000000000..8e23ec50d4a --- /dev/null +++ b/spec/features/merge_requests/widget_deployments_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +feature 'Widget Deployments Header', feature: true, js: true do + include WaitForAjax + + describe 'when deployed to an environment' do + let(:project) { merge_request.target_project } + let(:merge_request) { create(:merge_request, :merged) } + let(:environment) { create(:environment, project: project) } + let!(:deployment) do + create(:deployment, environment: environment, sha: project.commit('master').id) + end + + before do + login_as :admin + visit namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + it 'displays that the environment is deployed' do + wait_for_ajax + + expect(page).to have_content("Deployed to #{environment.name}") + expect(find('.ci_widget > span > span')['data-title']).to eq(deployment.created_at.to_time.in_time_zone.to_s(:medium)) + end + end +end diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js index 17b32914ec3..75ef10939de 100644 --- a/spec/javascripts/merge_request_widget_spec.js +++ b/spec/javascripts/merge_request_widget_spec.js @@ -1,5 +1,5 @@ - /*= require merge_request_widget */ +/*= require lib/utils/jquery.timeago.js */ (function() { describe('MergeRequestWidget', function() { @@ -20,7 +20,7 @@ gitlab_icon: "gitlab_logo.png", builds_path: "http://sampledomain.local/sampleBuildsPath" }; - this["class"] = new MergeRequestWidget(this.opts); + this["class"] = new window.gl.MergeRequestWidget(this.opts); return this.ciStatusData = { "title": "Sample MR title", "sha": "12a34bc5", @@ -30,7 +30,7 @@ }); return describe('getCIStatus', function() { beforeEach(function() { - return spyOn(jQuery, 'getJSON').and.callFake((function(_this) { + spyOn(jQuery, 'getJSON').and.callFake((function(_this) { return function(req, cb) { return cb(_this.ciStatusData); }; @@ -61,13 +61,30 @@ this["class"].getCIStatus(false); return expect(spy).not.toHaveBeenCalled(); }); - return it('should not display a notification on the first check after the widget has been created', function() { + it('should not display a notification on the first check after the widget has been created', function() { var spy; spy = spyOn(window, 'notify'); - this["class"] = new MergeRequestWidget(this.opts); + this["class"] = new window.gl.MergeRequestWidget(this.opts); this["class"].getCIStatus(true); return expect(spy).not.toHaveBeenCalled(); }); + it('should call renderEnvironments when the environments property is set', function() { + this.ciStatusData.environments = [{ + created_at: '2016-09-12T13:38:30.636Z', + environment_id: 1, + environment_name: 'env1', + external_url: 'https://test-url.com', + external_url_formatted: 'test-url.com' + }]; + var spy = spyOn(this['class'], 'renderEnvironments').and.stub(); + this['class'].getCIStatus(false); + expect(spy).toHaveBeenCalledWith(this.ciStatusData.environments); + }); + it('should not call renderEnvironments when the environments property is not set', function() { + var spy = spyOn(this['class'], 'renderEnvironments').and.stub(); + this['class'].getCIStatus(false); + expect(spy).not.toHaveBeenCalled(); + }); }); }); diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index 6b1867a44e1..fb9629ac47a 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -64,6 +64,23 @@ describe Environment, models: true do end end + describe '#deployment_id_for' do + let(:project) { create(:project) } + let!(:environment) { create(:environment, project: project) } + let!(:deployment) { create(:deployment, environment: environment, ref: commit.parent.id) } + let!(:deployment1) { create(:deployment, environment: environment, ref: commit.id) } + let(:head_commit) { project.commit } + let(:commit) { project.commit.parent } + + it 'returns deployment id for the environment' do + expect(environment.deployment_id_for(commit)).to eq deployment1.id + end + + it 'return nil when no deployment is found' do + expect(environment.deployment_id_for(head_commit)).to eq nil + end + end + describe '#environment_type' do subject { environment.environment_type } diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 4b80efbe12b..f977cf73673 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -7,15 +7,18 @@ describe Repository, models: true do let(:project) { create(:project) } let(:repository) { project.repository } let(:user) { create(:user) } + let(:commit_options) do author = repository.user_to_committer(user) { message: 'Test message', committer: author, author: author } end + let(:merge_commit) do merge_request = create(:merge_request, source_branch: 'feature', target_branch: 'master', source_project: project) merge_commit_id = repository.merge(user, merge_request, commit_options) repository.commit(merge_commit_id) end + let(:author_email) { FFaker::Internet.email } # I have to remove periods from the end of the name @@ -90,6 +93,26 @@ describe Repository, models: true do end end + describe '#ref_name_for_sha' do + context 'ref found' do + it 'returns the ref' do + allow_any_instance_of(Gitlab::Popen).to receive(:popen). + and_return(["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0]) + + expect(repository.ref_name_for_sha('bla', '0' * 40)).to eq 'refs/environments/production/77' + end + end + + context 'ref not found' do + it 'returns nil' do + allow_any_instance_of(Gitlab::Popen).to receive(:popen). + and_return(["", 0]) + + expect(repository.ref_name_for_sha('bla', '0' * 40)).to eq nil + end + end + end + describe '#last_commit_for_path' do subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id } diff --git a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb b/spec/views/projects/merge_requests/_heading.html.haml_spec.rb deleted file mode 100644 index 86980f59cd8..00000000000 --- a/spec/views/projects/merge_requests/_heading.html.haml_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'spec_helper' - -describe 'projects/merge_requests/widget/_heading' do - include Devise::Test::ControllerHelpers - - context 'when released to an environment' do - let(:project) { merge_request.target_project } - let(:merge_request) { create(:merge_request, :merged) } - let(:environment) { create(:environment, project: project) } - let!(:deployment) do - create(:deployment, environment: environment, sha: project.commit('master').id) - end - - before do - assign(:merge_request, merge_request) - assign(:project, project) - - allow(view).to receive(:can?).and_return(true) - - render - end - - it 'displays that the environment is deployed' do - expect(rendered).to match("Deployed to") - expect(rendered).to match("#{environment.name}") - end - end -end -- cgit v1.2.1 From 8a1064d76252e16b64b6e4f03d819b6dc20e1d6d Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Tue, 4 Oct 2016 18:50:25 +0200 Subject: Fix indenting error in HAML --- app/models/merge_request.rb | 2 +- app/views/projects/merge_requests/widget/_heading.html.haml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index ec8c09f83db..0e427378bac 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -691,7 +691,7 @@ class MergeRequest < ActiveRecord::Base environment.deployment_id_for(diff_head_commit) end.compact - Deployments.find(deployment_ids) + Deployment.find(deployment_ids) end def environments diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml index cda8f0b7de6..a82c846baa7 100644 --- a/app/views/projects/merge_requests/widget/_heading.html.haml +++ b/app/views/projects/merge_requests/widget/_heading.html.haml @@ -44,5 +44,5 @@ = icon("times-circle") Could not connect to the CI server. Please check your settings and try again. - .js-success-icon.hidden - = ci_icon_for_status('success') +.js-success-icon.hidden + = ci_icon_for_status('success') -- cgit v1.2.1 From fa58068b2894b900d4b2519825411e0710557fc6 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 5 Oct 2016 13:52:15 +0200 Subject: Refactor ci_status on MergeRequestController --- app/assets/stylesheets/pages/merge_requests.scss | 4 +- .../projects/merge_requests_controller.rb | 45 ++++++++++------------ app/models/environment.rb | 5 ++- app/models/merge_request.rb | 22 ++++------- app/models/repository.rb | 4 +- 5 files changed, 35 insertions(+), 45 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 96d5547154d..6a0fae8a3f9 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -122,8 +122,8 @@ } .js-deployment-link { - display: inline-block; - } + display: inline-block; + } .mr-widget-body { h4 { diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 28225fbb762..00043c5a4c0 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -393,33 +393,28 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end - environments = @merge_request.environments - deployments = @merge_request.deployments - - if environments.present? - environments = environments.select { |e| can?(current_user, :read_environment, e) }.map do |environment| - project = environment.project - deployment = deployments.find { |d| d.environment == environment } - - environment = { - name: environment.name, - id: environment.id, - url: namespace_project_environment_path(project.namespace, project, environment), - external_url: environment.external_url, - deployed_at: deployment ? deployment.created_at : nil - } - - if environment[:external_url] - environment[:external_url_formatted] = environment[:external_url].gsub(/\A.*?:\/\//, '') - end - - if environment[:deployed_at] - environment[:deployed_at_formatted] = environment[:deployed_at].to_time.in_time_zone.to_s(:medium) - end + environments = @merge_request.environments.map do |environment| + next unless can?(current_user, :read_environment, environment) + + deployment = environment.first_deployment_for(@merge_request.diff_head_commit) + environment = { + name: environment.name, + id: environment.id, + url: namespace_project_environment_path(@project.namespace, @project, environment), + external_url: environment.external_url, + deployed_at: deployment ? deployment.created_at : nil + } + + if environment[:external_url] + environment[:external_url_formatted] = environment[:external_url].gsub(/\A.*?:\/\//, '') + end - environment + if environment[:deployed_at] + environment[:deployed_at_formatted] = environment[:deployed_at].to_time.in_time_zone.to_s(:medium) end - end + + environment + end.compact response = { title: merge_request.title, diff --git a/app/models/environment.rb b/app/models/environment.rb index 1c7d06906f3..c6cae81ce6a 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -48,12 +48,13 @@ class Environment < ActiveRecord::Base self.name == "production" end - def deployment_id_for(commit) + def first_deployment_for(commit) ref = project.repository.ref_name_for_sha(ref_path, commit.sha) return nil unless ref - ref.split('/').last.to_i + deployment_id = ref.split('/').last.to_i + deployments.find(deployment_id) end def ref_path diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 0e427378bac..5ccfe11a2a2 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -685,24 +685,18 @@ class MergeRequest < ActiveRecord::Base !pipeline || pipeline.success? end - def deployments - deployment_ids = - environments.map do |environment| - environment.deployment_id_for(diff_head_commit) - end.compact - - Deployment.find(deployment_ids) - end - def environments return [] unless diff_head_commit - environments = source_project.environments_for( - source_branch, diff_head_commit) - environments += target_project.environments_for( - target_branch, diff_head_commit, with_tags: true) + @environments ||= + begin + environments = source_project.environments_for( + source_branch, diff_head_commit) + environments += target_project.environments_for( + target_branch, diff_head_commit, with_tags: true) - environments.uniq + environments.uniq + end end def state_human_name diff --git a/app/models/repository.rb b/app/models/repository.rb index 37833cf004f..72e473871fa 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -719,8 +719,8 @@ class Repository end end - def ref_name_for_sha(environment_ref_path, sha) - args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{environment_ref_path} --contains #{sha}) + def ref_name_for_sha(ref_path, sha) + args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{ref_path} --contains #{sha}) # Not found -> ["", 0] # Found -> ["b8d95eb4969eefacb0a58f6a28f6803f8070e7b9 commit\trefs/environments/production/77\n", 0] -- cgit v1.2.1 From 58368fbc53bfe7c2a9b425626819eae576afff09 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 7 Oct 2016 23:10:06 +0100 Subject: Moved ci_status environments logic to new action ci_envrionments_status and set up frontend polling --- app/assets/javascripts/merge_request_widget.js.es6 | 53 +++++++++++++------ .../projects/merge_requests_controller.rb | 38 +++++++------- .../projects/merge_requests/widget/_show.html.haml | 1 + config/routes.rb | 5 -- config/routes/project.rb | 1 + spec/javascripts/merge_request_widget_spec.js | 60 +++++++++++++--------- 6 files changed, 96 insertions(+), 62 deletions(-) diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index 7c727cf214f..8354f7ab8a4 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -35,13 +35,17 @@ }); this.firstCICheck = true; this.readyForCICheck = false; + this.readyForCIEnvironmentCheck = false; this.cancel = false; clearInterval(this.fetchBuildStatusInterval); + clearInterval(this.fetchBuildEnvironmentStatusInterval); this.clearEventListeners(); this.addEventListeners(); this.getCIStatus(false); + this.getCIEnvironmentsStatus(); this.retrieveSuccessIcon(); this.pollCIStatus(); + this.pollCIEnvironmentsStatus(); notifyPermissions(); } @@ -62,6 +66,7 @@ page = $('body').data('page').split(':').last(); if (allowedPages.indexOf(page) < 0) { clearInterval(_this.fetchBuildStatusInterval); + clearInterval(_this.fetchBuildEnvironmentStatusInterval); _this.cancelPolling(); return _this.clearEventListeners(); } @@ -178,23 +183,39 @@ })(this)); }; + MergeRequestWidget.prototype.pollCIEnvironmentsStatus = function() { + this.fetchBuildEnvironmentStatusInterval = setInterval(() => { + if (!this.readyForCIEnvironmentCheck) return; + this.getCIEnvironmentsStatus(); + this.readyForCIEnvironmentCheck = false; + }, 300000); + }; + + MergeRequestWidget.prototype.getCIEnvironmentsStatus = function() { + $.getJSON(this.opts.ci_environments_status_url, (environments) => { + if (this.cancel) return; + this.readyForCIEnvironmentCheck = true; + if (environments && environments.length) this.renderEnvironments(environments); + }); + }; + MergeRequestWidget.prototype.renderEnvironments = function(environments) { - for (let i = 0; i < environments.length; i++) { - const environment = environments[i]; - if ($(`.mr-state-widget #${ environment.id }`).length) return; - const $template = $(DEPLOYMENT_TEMPLATE); - if (!environment.external_url) $('.js-environment-link', $template).remove(); - if (environment.deployed_at) { - environment.deployed_at = $.timeago(environment.deployed_at) + '.'; - } else { - $('.js-environment-timeago', $template).remove(); - environment.name += '.'; - } - environment.ci_success_icon = this.$ciSuccessIcon; - const templateString = _.unescape($template[0].outerHTML); - const template = _.template(templateString)(environment) - this.$widgetBody.before(template); - } + for (let i = 0; i < environments.length; i++) { + const environment = environments[i]; + if ($(`.mr-state-widget #${ environment.id }`).length) return; + const $template = $(DEPLOYMENT_TEMPLATE); + if (!environment.external_url) $('.js-environment-link', $template).remove(); + if (environment.deployed_at) { + environment.deployed_at = $.timeago(environment.deployed_at) + '.'; + } else { + $('.js-environment-timeago', $template).remove(); + environment.name += '.'; + } + environment.ci_success_icon = this.$ciSuccessIcon; + const templateString = _.unescape($template[0].outerHTML); + const template = _.template(templateString)(environment) + this.$widgetBody.before(template); + } }; MergeRequestWidget.prototype.showCIStatus = function(state) { diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 00043c5a4c0..e17d560138f 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -10,7 +10,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled before_action :merge_request, only: [ :edit, :update, :show, :diffs, :commits, :conflicts, :builds, :pipelines, :merge, :merge_check, - :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues + :ci_status, :ci_environments_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip, :resolve_conflicts, :assign_related_issues ] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds, :pipelines] before_action :define_show_vars, only: [:show, :diffs, :commits, :conflicts, :builds, :pipelines] @@ -393,11 +393,23 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end - environments = @merge_request.environments.map do |environment| + response = { + title: merge_request.title, + sha: merge_request.diff_head_commit.short_id, + status: status, + coverage: coverage + } + + render json: response + end + + def ci_environments_status + render json: @merge_request.environments.map do |environment| next unless can?(current_user, :read_environment, environment) deployment = environment.first_deployment_for(@merge_request.diff_head_commit) - environment = { + + environment_data = { name: environment.name, id: environment.id, url: namespace_project_environment_path(@project.namespace, @project, environment), @@ -405,26 +417,16 @@ class Projects::MergeRequestsController < Projects::ApplicationController deployed_at: deployment ? deployment.created_at : nil } - if environment[:external_url] - environment[:external_url_formatted] = environment[:external_url].gsub(/\A.*?:\/\//, '') + if environment_data[:external_url] + environment_data[:external_url_formatted] = environment_data[:external_url].gsub(/\A.*?:\/\//, '') end - if environment[:deployed_at] - environment[:deployed_at_formatted] = environment[:deployed_at].to_time.in_time_zone.to_s(:medium) + if environment_data[:deployed_at] + environment_data[:deployed_at_formatted] = environment_data[:deployed_at].to_time.in_time_zone.to_s(:medium) end - environment + environment_data end.compact - - response = { - title: merge_request.title, - sha: merge_request.diff_head_commit.short_id, - status: status, - coverage: coverage, - environments: environments - } - - render json: response end protected diff --git a/app/views/projects/merge_requests/widget/_show.html.haml b/app/views/projects/merge_requests/widget/_show.html.haml index 856ec1e0bee..608fdf1c5f5 100644 --- a/app/views/projects/merge_requests/widget/_show.html.haml +++ b/app/views/projects/merge_requests/widget/_show.html.haml @@ -12,6 +12,7 @@ merge_check_url: "#{merge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", check_enable: #{@merge_request.unchecked? ? "true" : "false"}, ci_status_url: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", + ci_environments_status_url: "#{ci_environments_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", gitlab_icon: "#{asset_path 'gitlab_logo.png'}", ci_status: "#{@merge_request.pipeline ? @merge_request.pipeline.status : ''}", ci_message: { diff --git a/config/routes.rb b/config/routes.rb index 83c3a42c19f..68dc84d9c9e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -83,9 +83,4 @@ Rails.application.routes.draw do draw :group draw :user draw :project - - # Get all keys of user - get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } - - root to: "root#index" end diff --git a/config/routes/project.rb b/config/routes/project.rb index f9d58f5d5b2..200922b74db 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -273,6 +273,7 @@ resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: post :merge post :cancel_merge_when_build_succeeds get :ci_status + get :ci_environments_status post :toggle_subscription post :remove_wip get :diff_for_path diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js index 75ef10939de..7b20572742c 100644 --- a/spec/javascripts/merge_request_widget_spec.js +++ b/spec/javascripts/merge_request_widget_spec.js @@ -8,6 +8,7 @@ window.notify = function() {}; this.opts = { ci_status_url: "http://sampledomain.local/ci/getstatus", + ci_environments_status_url: "http://sampledomain.local/ci/getenvironmentsstatus", ci_status: "", ci_message: { normal: "Build {{status}} for \"{{title}}\"", @@ -21,15 +22,45 @@ builds_path: "http://sampledomain.local/sampleBuildsPath" }; this["class"] = new window.gl.MergeRequestWidget(this.opts); - return this.ciStatusData = { - "title": "Sample MR title", - "sha": "12a34bc5", - "status": "success", - "coverage": 98 - }; }); + + describe('getCIEnvironmentsStatus', function() { + beforeEach(function() { + this.ciEnvironmentsStatusData = { + created_at: '2016-09-12T13:38:30.636Z', + environment_id: 1, + environment_name: 'env1', + external_url: 'https://test-url.com', + external_url_formatted: 'test-url.com' + }; + + spyOn(jQuery, 'getJSON').and.callFake((req, cb) => { + cb(this.ciEnvironmentsStatusData); + }); + }); + + it('should call renderEnvironments when the environments property is set', function() { + const spy = spyOn(this.class, 'renderEnvironments').and.stub(); + this.class.getCIEnvironmentsStatus(); + expect(spy).toHaveBeenCalledWith(this.ciEnvironmentsStatusData); + }); + + it('should not call renderEnvironments when the environments property is not set', function() { + const spy = spyOn(this.class, 'renderEnvironments').and.stub(); + this.class.getCIEnvironmentsStatus(); + expect(spy).not.toHaveBeenCalled(); + }); + }); + return describe('getCIStatus', function() { beforeEach(function() { + this.ciStatusData = { + "title": "Sample MR title", + "sha": "12a34bc5", + "status": "success", + "coverage": 98 + }; + spyOn(jQuery, 'getJSON').and.callFake((function(_this) { return function(req, cb) { return cb(_this.ciStatusData); @@ -68,23 +99,6 @@ this["class"].getCIStatus(true); return expect(spy).not.toHaveBeenCalled(); }); - it('should call renderEnvironments when the environments property is set', function() { - this.ciStatusData.environments = [{ - created_at: '2016-09-12T13:38:30.636Z', - environment_id: 1, - environment_name: 'env1', - external_url: 'https://test-url.com', - external_url_formatted: 'test-url.com' - }]; - var spy = spyOn(this['class'], 'renderEnvironments').and.stub(); - this['class'].getCIStatus(false); - expect(spy).toHaveBeenCalledWith(this.ciStatusData.environments); - }); - it('should not call renderEnvironments when the environments property is not set', function() { - var spy = spyOn(this['class'], 'renderEnvironments').and.stub(); - this['class'].getCIStatus(false); - expect(spy).not.toHaveBeenCalled(); - }); }); }); -- cgit v1.2.1 From 88b03bb542a8480d61c260a9dc3769ab791995e5 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Mon, 10 Oct 2016 20:38:48 +0200 Subject: Rename method in test --- .../projects/merge_requests_controller.rb | 41 ++++++++++++---------- config/routes.rb | 5 +++ spec/models/environment_spec.rb | 6 ++-- 3 files changed, 31 insertions(+), 21 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index e17d560138f..5f7f46cf566 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -404,29 +404,34 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def ci_environments_status - render json: @merge_request.environments.map do |environment| - next unless can?(current_user, :read_environment, environment) - - deployment = environment.first_deployment_for(@merge_request.diff_head_commit) + environments = + begin + @merge_request.environments.map do |environment| + next unless can?(current_user, :read_environment, environment) + + deployment = environment.first_deployment_for(@merge_request.diff_head_commit) + + environment_data = { + name: environment.name, + id: environment.id, + url: namespace_project_environment_path(@project.namespace, @project, environment), + external_url: environment.external_url, + deployed_at: deployment ? deployment.created_at : nil + } - environment_data = { - name: environment.name, - id: environment.id, - url: namespace_project_environment_path(@project.namespace, @project, environment), - external_url: environment.external_url, - deployed_at: deployment ? deployment.created_at : nil - } + if environment_data[:external_url] + environment_data[:external_url_formatted] = environment_data[:external_url].gsub(/\A.*?:\/\//, '') + end - if environment_data[:external_url] - environment_data[:external_url_formatted] = environment_data[:external_url].gsub(/\A.*?:\/\//, '') - end + if environment_data[:deployed_at] + environment_data[:deployed_at_formatted] = environment_data[:deployed_at].to_time.in_time_zone.to_s(:medium) + end - if environment_data[:deployed_at] - environment_data[:deployed_at_formatted] = environment_data[:deployed_at].to_time.in_time_zone.to_s(:medium) + environment_data + end.compact end - environment_data - end.compact + render json: environments end protected diff --git a/config/routes.rb b/config/routes.rb index 68dc84d9c9e..83c3a42c19f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -83,4 +83,9 @@ Rails.application.routes.draw do draw :group draw :user draw :project + + # Get all keys of user + get ':username.keys' => 'profiles/keys#get_keys', constraints: { username: /.*/ } + + root to: "root#index" end diff --git a/spec/models/environment_spec.rb b/spec/models/environment_spec.rb index fb9629ac47a..e172ee8e590 100644 --- a/spec/models/environment_spec.rb +++ b/spec/models/environment_spec.rb @@ -64,7 +64,7 @@ describe Environment, models: true do end end - describe '#deployment_id_for' do + describe '#first_deployment_for' do let(:project) { create(:project) } let!(:environment) { create(:environment, project: project) } let!(:deployment) { create(:deployment, environment: environment, ref: commit.parent.id) } @@ -73,11 +73,11 @@ describe Environment, models: true do let(:commit) { project.commit.parent } it 'returns deployment id for the environment' do - expect(environment.deployment_id_for(commit)).to eq deployment1.id + expect(environment.first_deployment_for(commit)).to eq deployment1 end it 'return nil when no deployment is found' do - expect(environment.deployment_id_for(head_commit)).to eq nil + expect(environment.first_deployment_for(head_commit)).to eq nil end end -- cgit v1.2.1 From 4b40027b50a2be4bad76b0a4a6a5e92c0de14255 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 11 Oct 2016 18:48:43 +0100 Subject: Fixed conflict and corrected teaspoon test --- spec/javascripts/merge_request_widget_spec.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/javascripts/merge_request_widget_spec.js b/spec/javascripts/merge_request_widget_spec.js index 7b20572742c..c9175e2b704 100644 --- a/spec/javascripts/merge_request_widget_spec.js +++ b/spec/javascripts/merge_request_widget_spec.js @@ -26,13 +26,13 @@ describe('getCIEnvironmentsStatus', function() { beforeEach(function() { - this.ciEnvironmentsStatusData = { + this.ciEnvironmentsStatusData = [{ created_at: '2016-09-12T13:38:30.636Z', environment_id: 1, environment_name: 'env1', external_url: 'https://test-url.com', external_url_formatted: 'test-url.com' - }; + }]; spyOn(jQuery, 'getJSON').and.callFake((req, cb) => { cb(this.ciEnvironmentsStatusData); @@ -46,6 +46,7 @@ }); it('should not call renderEnvironments when the environments property is not set', function() { + this.ciEnvironmentsStatusData = null; const spy = spyOn(this.class, 'renderEnvironments').and.stub(); this.class.getCIEnvironmentsStatus(); expect(spy).not.toHaveBeenCalled(); -- cgit v1.2.1 From 35e2315a66e34f290230720cfa74240fd5532970 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 12 Oct 2016 15:21:01 +0200 Subject: Minor style improvement --- .../projects/merge_requests_controller.rb | 20 ++++++-------------- app/models/deployment.rb | 4 ++++ app/models/environment.rb | 8 +++++++- db/schema.rb | 12 +++++++++++- 4 files changed, 28 insertions(+), 16 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 5f7f46cf566..5c802798028 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -404,30 +404,22 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def ci_environments_status - environments = + environments = begin @merge_request.environments.map do |environment| next unless can?(current_user, :read_environment, environment) deployment = environment.first_deployment_for(@merge_request.diff_head_commit) - environment_data = { - name: environment.name, + { id: environment.id, + name: environment.name, url: namespace_project_environment_path(@project.namespace, @project, environment), external_url: environment.external_url, - deployed_at: deployment ? deployment.created_at : nil + external_url_formatted: environment.formatted_external_url, + deployed_at: deployment.try(:created_at), + deployed_at_formatted: deployment.try(:formatted_deployment_time) } - - if environment_data[:external_url] - environment_data[:external_url_formatted] = environment_data[:external_url].gsub(/\A.*?:\/\//, '') - end - - if environment_data[:deployed_at] - environment_data[:deployed_at_formatted] = environment_data[:deployed_at].to_time.in_time_zone.to_s(:medium) - end - - environment_data end.compact end diff --git a/app/models/deployment.rb b/app/models/deployment.rb index f63cc179b9e..3d9902d496e 100644 --- a/app/models/deployment.rb +++ b/app/models/deployment.rb @@ -84,6 +84,10 @@ class Deployment < ActiveRecord::Base take end + def formatted_deployment_time + created_at.to_time.in_time_zone.to_s(:medium) + end + private def ref_path diff --git a/app/models/environment.rb b/app/models/environment.rb index c6cae81ce6a..d970bc0a005 100644 --- a/app/models/environment.rb +++ b/app/models/environment.rb @@ -53,11 +53,17 @@ class Environment < ActiveRecord::Base return nil unless ref - deployment_id = ref.split('/').last.to_i + deployment_id = ref.split('/').last deployments.find(deployment_id) end def ref_path "refs/environments/#{Shellwords.shellescape(name)}" end + + def formatted_external_url + return nil unless external_url + + external_url.gsub(/\A.*?:\/\//, '') + end end diff --git a/db/schema.rb b/db/schema.rb index a362fd8f228..0bb03dfe4bc 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -446,6 +446,17 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree + create_table "integrations", force: :cascade do |t| + t.integer "project_id" + t.string "name" + t.string "external_token" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "integrations", ["external_token"], name: "index_integrations_on_external_token", unique: true, using: :btree + add_index "integrations", ["project_id"], name: "index_integrations_on_project_id", using: :btree + create_table "issue_metrics", force: :cascade do |t| t.integer "issue_id", null: false t.datetime "first_mentioned_in_commit_at" @@ -613,7 +624,6 @@ ActiveRecord::Schema.define(version: 20161007133303) do t.datetime "updated_at", null: false end - add_index "merge_request_metrics", ["first_deployed_to_production_at"], name: "index_merge_request_metrics_on_first_deployed_to_production_at", using: :btree add_index "merge_request_metrics", ["merge_request_id"], name: "index_merge_request_metrics", using: :btree create_table "merge_requests", force: :cascade do |t| -- cgit v1.2.1 From 6e509ae3b60eb0fbe4bddb7dbb42beaf47817f3b Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 13 Oct 2016 13:00:06 +0100 Subject: Added safety check for formatted values --- app/assets/javascripts/merge_request_widget.js.es6 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/merge_request_widget.js.es6 b/app/assets/javascripts/merge_request_widget.js.es6 index 8354f7ab8a4..fcadc4bc515 100644 --- a/app/assets/javascripts/merge_request_widget.js.es6 +++ b/app/assets/javascripts/merge_request_widget.js.es6 @@ -204,8 +204,8 @@ const environment = environments[i]; if ($(`.mr-state-widget #${ environment.id }`).length) return; const $template = $(DEPLOYMENT_TEMPLATE); - if (!environment.external_url) $('.js-environment-link', $template).remove(); - if (environment.deployed_at) { + if (!environment.external_url || !environment.external_url_formatted) $('.js-environment-link', $template).remove(); + if (environment.deployed_at && environment.deployed_at_formatted) { environment.deployed_at = $.timeago(environment.deployed_at) + '.'; } else { $('.js-environment-timeago', $template).remove(); -- cgit v1.2.1 From d1a7b35b167b747a4794110dc1354200fd6dc592 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 14 Oct 2016 10:49:23 +0100 Subject: Updated issuable dropdown titles Closes #23337 --- app/views/shared/issuable/_form.html.haml | 6 +++--- app/views/shared/issuable/_label_dropdown.html.haml | 3 ++- app/views/shared/issuable/_milestone_dropdown.html.haml | 3 ++- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/views/shared/issuable/_form.html.haml b/app/views/shared/issuable/_form.html.haml index c3f4e10c954..a75653d842f 100644 --- a/app/views/shared/issuable/_form.html.haml +++ b/app/views/shared/issuable/_form.html.haml @@ -85,20 +85,20 @@ .issuable-form-select-holder - if issuable.assignee_id = f.hidden_field :assignee_id - = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Filter by assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", + = dropdown_tag(user_dropdown_label(issuable.assignee_id, "Assignee"), options: { toggle_class: "js-dropdown-keep-input js-user-search js-issuable-form-dropdown js-assignee-search", title: "Select assignee", filter: true, dropdown_class: "dropdown-menu-user dropdown-menu-selectable dropdown-menu-assignee js-filter-submit", placeholder: "Search assignee", data: { first_user: current_user.try(:username), null_user: true, current_user: true, project_id: project.try(:id), selected: issuable.assignee_id, field_name: "#{issuable.class.model_name.param_key}[assignee_id]", default_label: "Assignee", show_menu_above: true } }) .form-group.issue-milestone = f.label :milestone_id, "Milestone", class: "control-label #{"col-lg-4" if has_due_date}" .col-sm-10{ class: ("col-lg-8" if has_due_date) } .issuable-form-select-holder - = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input" + = render "shared/issuable/milestone_dropdown", selected: issuable.milestone, name: "#{issuable.class.model_name.param_key}[milestone_id]", show_any: false, show_menu_above: true, show_upcoming: false, extra_class: "js-issuable-form-dropdown js-dropdown-keep-input", dropdown_title: "Select milestone" .form-group - has_labels = issuable.project.labels.any? = f.label :label_ids, "Labels", class: "control-label #{"col-lg-4" if has_due_date}" = f.hidden_field :label_ids, multiple: true, value: '' .col-sm-10{ class: "#{"col-lg-8" if has_due_date} #{'issuable-form-padding-top' if !has_labels}" } .issuable-form-select-holder - = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' } + = render "shared/issuable/label_dropdown", classes: ["js-issuable-form-dropdown"], selected: issuable.labels, data_options: { field_name: "#{issuable.class.model_name.param_key}[label_ids][]", show_any: false, show_menu_above: 'true' }, dropdown_title: "Select label" - if has_due_date .col-lg-6 .form-group diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 6d307611640..22b5a6aa11b 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -8,6 +8,7 @@ - classes = local_assigns.fetch(:classes, []) - selected = local_assigns.fetch(:selected, nil) - selected_toggle = local_assigns.fetch(:selected_toggle, nil) +- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by label") - dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:path), project_path: @project.try(:path), labels: labels_filter_path, default_label: "Labels"} - dropdown_data.merge!(data_options) - classes << 'js-extra-options' if extra_options @@ -23,7 +24,7 @@ = multi_label_name(selected, "Labels") = icon('chevron-down') .dropdown-menu.dropdown-select.dropdown-menu-paging.dropdown-menu-labels.dropdown-menu-selectable - = render partial: "shared/issuable/label_page_default", locals: { title: "Filter by label", show_footer: show_footer, show_create: show_create } + = render partial: "shared/issuable/label_page_default", locals: { title: dropdown_title, show_footer: show_footer, show_create: show_create } - if show_create && project && can?(current_user, :admin_label, project) = render partial: "shared/issuable/label_page_create" = dropdown_loading diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index ab3cc33d18f..b85e2c11ff0 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -2,9 +2,10 @@ - extra_class = extra_class || '' - show_menu_above = show_menu_above || false - selected_text = selected.try(:title) +- dropdown_title = dropdown_title || "Filter by milestone" - if selected.present? = hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id) -= dropdown_tag(milestone_dropdown_label(selected_text), options: { title: "Filter by milestone", toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", += dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", placeholder: "Search milestones", footer_content: project.present?, data: { show_no: true, show_menu_above: show_menu_above, show_any: show_any, show_upcoming: show_upcoming, field_name: name, selected: selected.try(:title), project_id: project.try(:id), milestones: milestones_filter_dropdown_path, default_label: "Milestone" } }) do - if project %ul.dropdown-footer-list -- cgit v1.2.1 From 8710cdf57d4160685071a44dc225cb02865634e2 Mon Sep 17 00:00:00 2001 From: Phil Hughes Date: Fri, 14 Oct 2016 11:15:44 +0100 Subject: Use local assigns to get the dropdown title --- app/views/shared/issuable/_milestone_dropdown.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/issuable/_milestone_dropdown.html.haml b/app/views/shared/issuable/_milestone_dropdown.html.haml index b85e2c11ff0..f27a9002ec2 100644 --- a/app/views/shared/issuable/_milestone_dropdown.html.haml +++ b/app/views/shared/issuable/_milestone_dropdown.html.haml @@ -2,7 +2,7 @@ - extra_class = extra_class || '' - show_menu_above = show_menu_above || false - selected_text = selected.try(:title) -- dropdown_title = dropdown_title || "Filter by milestone" +- dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by milestone") - if selected.present? = hidden_field_tag(name, name == :milestone_title ? selected.title : selected.id) = dropdown_tag(milestone_dropdown_label(selected_text), options: { title: dropdown_title, toggle_class: "js-milestone-select js-filter-submit #{extra_class}", filter: true, dropdown_class: "dropdown-menu-selectable dropdown-menu-milestone", -- cgit v1.2.1 From cf15af31353b141028717456aa6967c9c11697af Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Thu, 13 Oct 2016 14:23:18 +0200 Subject: Add test, fix merge error --- .../projects/merge_requests_controller.rb | 3 ++- db/schema.rb | 12 +-------- .../projects/merge_requests_controller_spec.rb | 30 ++++++++++++++++++++++ 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 5c802798028..9207c954335 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -409,12 +409,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request.environments.map do |environment| next unless can?(current_user, :read_environment, environment) + project = environment.project deployment = environment.first_deployment_for(@merge_request.diff_head_commit) { id: environment.id, name: environment.name, - url: namespace_project_environment_path(@project.namespace, @project, environment), + url: namespace_project_environment_path(project.namespace, project, environment), external_url: environment.external_url, external_url_formatted: environment.formatted_external_url, deployed_at: deployment.try(:created_at), diff --git a/db/schema.rb b/db/schema.rb index 0bb03dfe4bc..a362fd8f228 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -446,17 +446,6 @@ ActiveRecord::Schema.define(version: 20161007133303) do add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree - create_table "integrations", force: :cascade do |t| - t.integer "project_id" - t.string "name" - t.string "external_token" - t.datetime "created_at", null: false - t.datetime "updated_at", null: false - end - - add_index "integrations", ["external_token"], name: "index_integrations_on_external_token", unique: true, using: :btree - add_index "integrations", ["project_id"], name: "index_integrations_on_project_id", using: :btree - create_table "issue_metrics", force: :cascade do |t| t.integer "issue_id", null: false t.datetime "first_mentioned_in_commit_at" @@ -624,6 +613,7 @@ ActiveRecord::Schema.define(version: 20161007133303) do t.datetime "updated_at", null: false end + add_index "merge_request_metrics", ["first_deployed_to_production_at"], name: "index_merge_request_metrics_on_first_deployed_to_production_at", using: :btree add_index "merge_request_metrics", ["merge_request_id"], name: "index_merge_request_metrics", using: :btree create_table "merge_requests", force: :cascade do |t| diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 84298f8bef4..d509f0f2b96 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -756,4 +756,34 @@ describe Projects::MergeRequestsController do post_assign_issues end end + + describe 'GET ci_environments_status' do + context 'when the environment is from a forked project' do + let!(:forked) { create(:project) } + let!(:environment) { create(:environment, project: forked) } + let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') } + let(:json_response) { JSON.parse(response.body) } + let(:admin) { create(:admin) } + + let(:merge_request) do + create(:forked_project_link, forked_to_project: forked, + forked_from_project: project) + + create(:merge_request, source_project: forked, target_project: project) + end + + before do + forked.team << [user, :master] + + get :ci_environments_status, + namespace_id: merge_request.project.namespace.to_param, + project_id: merge_request.project.to_param, + id: merge_request.iid, format: 'json' + end + + it 'links to the environment on that project' do + expect(json_response.first['url']).to match /#{forked.path_with_namespace}/ + end + end + end end -- cgit v1.2.1 From 4c46c9a9738cfa90dd450e70ccf85e470be1d789 Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Fri, 14 Oct 2016 09:38:20 +0200 Subject: Grapify boards API --- lib/api/boards.rb | 76 ++++++++++++++++++++++++++++++++----------------------- 1 file changed, 45 insertions(+), 31 deletions(-) diff --git a/lib/api/boards.rb b/lib/api/boards.rb index 9b71d335128..b14dd4f6e83 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -3,19 +3,28 @@ module API class Boards < Grape::API before { authenticate! } + params do + requires :id, type: String, desc: 'The ID of a project' + end resource :projects do - # Get the project board + desc 'Get all project boards' do + detail 'This feature was introduced in 8.13' + success Entities::Board + end get ':id/boards' do authorize!(:read_board, user_project) present user_project.boards, with: Entities::Board end + params do + requires :board_id, type: Integer, desc: 'The ID of a board' + end segment ':id/boards/:board_id' do helpers do def project_board board = user_project.boards.first - if params[:board_id].to_i == board.id + if params[:board_id] == board.id board else not_found!('Board') @@ -27,29 +36,35 @@ module API end end - # Get the lists of a project board - # Does not include `backlog` and `done` lists + desc 'Get the lists of a project board' do + detail 'Does not include `backlog` and `done` lists. This feature was introduced in 8.13' + success Entities::List + end get '/lists' do authorize!(:read_board, user_project) present board_lists, with: Entities::List end - # Get a list of a project board + desc 'Get a list of a project board' do + detail 'This feature was introduced in 8.13' + success Entities::List + end + params do + requires :list_id, type: Integer, desc: 'The ID of a list' + end get '/lists/:list_id' do authorize!(:read_board, user_project) present board_lists.find(params[:list_id]), with: Entities::List end - # Create a new board list - # - # Parameters: - # id (required) - The ID of a project - # label_id (required) - The ID of an existing label - # Example Request: - # POST /projects/:id/boards/:board_id/lists + desc 'Create a new board list' do + detail 'This feature was introduced in 8.13' + success Entities::List + end + params do + requires :label_id, type: Integer, desc: 'The ID of an existing label' + end post '/lists' do - required_attributes! [:label_id] - unless user_project.labels.exists?(params[:label_id]) render_api_error!({ error: "Label not found!" }, 400) end @@ -68,21 +83,21 @@ module API end end - # Moves a board list to a new position - # - # Parameters: - # id (required) - The ID of a project - # board_id (required) - The ID of a board - # position (required) - The position of the list - # Example Request: - # PUT /projects/:id/boards/:board_id/lists/:list_id + desc 'Moves a board list to a new position' do + detail 'This feature was introduced in 8.13' + success Entities::List + end + params do + requires :list_id, type: Integer, desc: 'The ID of a list' + requires :position, type: Integer, desc: 'The position of the list' + end put '/lists/:list_id' do list = project_board.lists.movable.find(params[:list_id]) authorize!(:admin_list, user_project) service = ::Boards::Lists::MoveService.new(user_project, current_user, - { position: params[:position].to_i }) + { position: params[:position] }) if service.execute(list) present list, with: Entities::List @@ -91,14 +106,13 @@ module API end end - # Delete a board list - # - # Parameters: - # id (required) - The ID of a project - # board_id (required) - The ID of a board - # list_id (required) - The ID of a board list - # Example Request: - # DELETE /projects/:id/boards/:board_id/lists/:list_id + desc 'Delete a board list' do + detail 'This feature was introduced in 8.13' + success Entities::List + end + params do + requires :list_id, type: Integer, desc: 'The ID of a board list' + end delete "/lists/:list_id" do authorize!(:admin_list, user_project) -- cgit v1.2.1