From 9b9be55908b5616a72b43706693429d109fec533 Mon Sep 17 00:00:00 2001 From: Holger Segnitz Date: Sun, 9 Nov 2014 17:01:06 +0100 Subject: [PATCH 001/255] ADD: Note on how to setup dns hostname resolution and why it is necessary to make the software run properly. --- doc/install/installation.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/install/installation.md b/doc/install/installation.md index 459a21ae82..feeda612a5 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -286,6 +286,8 @@ GitLab Shell is an SSH access and repository management software developed speci **Note:** If you want to use HTTPS, see [Using HTTPS](#using-https) for the additional steps. +**Note:** Make sure your hostname can be resolved on the machine itself by either a proper DNS record or an additional line in /etc/hosts ("127.0.0.1 hostname"). This might be necessary for example if you set up gitlab behind a reverse proxy. If the hostname cannot be resolved, the final installation check will fail with "Check GitLab API access: FAILED. code: 401" and pushing commits will be rejected with "[remote rejected] master -> master (hook declined)". + ### Initialize Database and Activate Advanced Features sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production From 4c5adb702caef0aebe0d10416521e24e68fa0801 Mon Sep 17 00:00:00 2001 From: Drunkard Zhang Date: Wed, 4 Feb 2015 09:36:51 +0800 Subject: [PATCH 002/255] Specify shell while run me as git user Some users disabled "git" user's shell after finished installation, this will lead to "This account is currently not available" and could not run /etc/init.d/gitlab, this dirty trick fix it. Signed-off-by: Drunkard Zhang --- lib/support/init.d/gitlab | 3 ++- lib/support/init.d/gitlab.default.example | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab index b066a1a693..946902e2f6 100755 --- a/lib/support/init.d/gitlab +++ b/lib/support/init.d/gitlab @@ -35,13 +35,14 @@ pid_path="$app_root/tmp/pids" socket_path="$app_root/tmp/sockets" web_server_pid_path="$pid_path/unicorn.pid" sidekiq_pid_path="$pid_path/sidekiq.pid" +shell_path="/bin/bash" # Read configuration variable file if it is present test -f /etc/default/gitlab && . /etc/default/gitlab # Switch to the app_user if it is not he/she who is running the script. if [ "$USER" != "$app_user" ]; then - eval su - "$app_user" -c $(echo \")$0 "$@"$(echo \"); exit; + eval su - "$app_user" -s $shell_path -c $(echo \")$0 "$@"$(echo \"); exit; fi # Switch to the gitlab path, exit on failure. diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example index 9951bacedf..4c5752766f 100755 --- a/lib/support/init.d/gitlab.default.example +++ b/lib/support/init.d/gitlab.default.example @@ -29,3 +29,9 @@ web_server_pid_path="$pid_path/unicorn.pid" # sidekiq_pid_path defines the path in which to create the pid file for sidekiq # The default is "$pid_path/sidekiq.pid" sidekiq_pid_path="$pid_path/sidekiq.pid" + +# shell_path defines the path of shell for "$app_user" in case you disabled +# shell of "$app_user" by commands like `usermod -s /sbin/nologin $app_user" +# for security decision. +# The default is "/bin/bash" +shell_path="/bin/bash" From ef351f4cf4c2648ba6e5a71dc6d5086b4e45358d Mon Sep 17 00:00:00 2001 From: Drunkard Zhang Date: Mon, 4 May 2015 16:46:16 +0800 Subject: [PATCH 003/255] Improve comments for shell_path --- lib/support/init.d/gitlab.default.example | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example index 4c5752766f..cf7f4198cb 100755 --- a/lib/support/init.d/gitlab.default.example +++ b/lib/support/init.d/gitlab.default.example @@ -30,8 +30,7 @@ web_server_pid_path="$pid_path/unicorn.pid" # The default is "$pid_path/sidekiq.pid" sidekiq_pid_path="$pid_path/sidekiq.pid" -# shell_path defines the path of shell for "$app_user" in case you disabled -# shell of "$app_user" by commands like `usermod -s /sbin/nologin $app_user" -# for security decision. +# shell_path defines the path of shell for "$app_user" in case you are using +# shell other than "bash" # The default is "/bin/bash" shell_path="/bin/bash" From 55f91f3d4348e1d7be0953d0ddf9984d65f18993 Mon Sep 17 00:00:00 2001 From: Martin Luder Date: Fri, 8 May 2015 14:34:10 +0200 Subject: [PATCH 004/255] Order commit comments in API chronologically When fetching commit comments via API, the comments were not ordered, but just returned in the order Postgresql finds them. Now the API always returns comments in chronological order. --- CHANGELOG | 1 + lib/api/commits.rb | 2 +- spec/requests/api/commits_spec.rb | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 84bdf78e98..ef0f164264 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -48,6 +48,7 @@ v 7.11.0 (unreleased) - Add footnotes support to Markdown (Guillaume Delbergue) - Add current_sign_in_at to UserFull REST api. - Make Sidekiq MemoryKiller shutdown signal configurable + - Order commit comments chronologically in API. v 7.10.2 - Fix CI links on MR page diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 23270b1c0f..f4efb651eb 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -62,7 +62,7 @@ module API sha = params[:sha] commit = user_project.commit(sha) not_found! 'Commit' unless commit - notes = Note.where(commit_id: commit.id) + notes = Note.where(commit_id: commit.id).order(:created_at) present paginate(notes), with: Entities::CommitNote end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 9ea60e1a4a..a1c248c636 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -9,6 +9,7 @@ describe API::API, api: true do let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') } + let!(:another_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'another comment on a commit') } before { project.team << [user, :reporter] } @@ -89,7 +90,7 @@ describe API::API, api: true do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) expect(response.status).to eq(200) expect(json_response).to be_an Array - expect(json_response.length).to eq(1) + expect(json_response.length).to eq(2) expect(json_response.first['note']).to eq('a comment on a commit') expect(json_response.first['author']['id']).to eq(user.id) end From a9103eae3a2e92517d01f82aeaa923983420916f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 13 May 2015 11:02:30 -0400 Subject: [PATCH 005/255] Make more migrations reversible [ci skip] --- .../20150406133311_add_invite_data_to_member.rb | 13 ++++++++++++- ...0150417122318_remove_import_data_from_project.rb | 6 +++++- ...dd_default_snippet_visibility_to_app_settings.rb | 6 +++++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/db/migrate/20150406133311_add_invite_data_to_member.rb b/db/migrate/20150406133311_add_invite_data_to_member.rb index 3452fd45c4..5d3e856ddc 100644 --- a/db/migrate/20150406133311_add_invite_data_to_member.rb +++ b/db/migrate/20150406133311_add_invite_data_to_member.rb @@ -1,5 +1,5 @@ class AddInviteDataToMember < ActiveRecord::Migration - def change + def up add_column :members, :created_by_id, :integer add_column :members, :invite_email, :string add_column :members, :invite_token, :string @@ -9,4 +9,15 @@ class AddInviteDataToMember < ActiveRecord::Migration add_index :members, :invite_token, unique: true end + + def down + remove_index :members, :invite_token + + change_column :members, :user_id, :integer, null: false + + remove_column :members, :invite_accepted_at + remove_column :members, :invite_token + remove_column :members, :invite_email + remove_column :members, :created_by_id + end end diff --git a/db/migrate/20150417122318_remove_import_data_from_project.rb b/db/migrate/20150417122318_remove_import_data_from_project.rb index c275b49d22..46cf63593c 100644 --- a/db/migrate/20150417122318_remove_import_data_from_project.rb +++ b/db/migrate/20150417122318_remove_import_data_from_project.rb @@ -1,5 +1,9 @@ class RemoveImportDataFromProject < ActiveRecord::Migration - def change + def up remove_column :projects, :import_data end + + def down + add_column :projects, :import_data, :text + end end diff --git a/db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb b/db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb index 51237354d9..8f1b0cc893 100644 --- a/db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb +++ b/db/migrate/20150425173433_add_default_snippet_visibility_to_app_settings.rb @@ -1,7 +1,11 @@ class AddDefaultSnippetVisibilityToAppSettings < ActiveRecord::Migration - def change + def up add_column :application_settings, :default_snippet_visibility, :integer visibility = Settings.gitlab.default_projects_features['visibility_level'] execute("update application_settings set default_snippet_visibility = #{visibility}") end + + def down + remove_column :application_settings, :default_snippet_visibility + end end From b43cec039c0f13da0ae3fe8b16418f65b01320a6 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 15 May 2015 04:44:49 +0000 Subject: [PATCH 006/255] Set gitlab.rb in Docker single image in order to make PostgreSQL start up properly See gitlab-org/omnibus-gitlab#552 --- docker/single/Dockerfile | 1 + docker/single/assets/gitlab.rb | 37 ++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 docker/single/assets/gitlab.rb diff --git a/docker/single/Dockerfile b/docker/single/Dockerfile index 8922457253..a6cbf13123 100644 --- a/docker/single/Dockerfile +++ b/docker/single/Dockerfile @@ -28,6 +28,7 @@ EXPOSE 80 22 # Copy assets COPY assets/wrapper /usr/local/bin/ +COPY assets/gitlab.rb /etc/gitlab/ # Wrapper to handle signal, trigger runit and reconfigure GitLab CMD ["/usr/local/bin/wrapper"] diff --git a/docker/single/assets/gitlab.rb b/docker/single/assets/gitlab.rb new file mode 100644 index 0000000000..ef84e7832d --- /dev/null +++ b/docker/single/assets/gitlab.rb @@ -0,0 +1,37 @@ +# External URL should be your Docker instance. +# By default, GitLab will use the Docker container hostname. +# Always use port 80 here to force the internal nginx to bind port 80, +# even if you intend to use another port in Docker. +# external_url "http://192.168.59.103/" + +# Prevent Postgres from trying to allocate 25% of total memory +postgresql['shared_buffers'] = '1MB' + +# Configure GitLab to redirect PostgreSQL logs to the data volume +postgresql['log_directory'] = '/var/log/gitlab/postgresql' + +# Some configuration of GitLab +# You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration +gitlab_rails['gitlab_email_from'] = 'gitlab@example.com' +gitlab_rails['gitlab_support_email'] = 'support@example.com' +gitlab_rails['time_zone'] = 'Europe/Paris' + +# SMTP settings +# You must use an external server, the Docker container does not install an SMTP server +gitlab_rails['smtp_enable'] = true +gitlab_rails['smtp_address'] = "smtp.example.com" +gitlab_rails['smtp_port'] = 587 +gitlab_rails['smtp_user_name'] = "user" +gitlab_rails['smtp_password'] = "password" +gitlab_rails['smtp_domain'] = "example.com" +gitlab_rails['smtp_authentication'] = "plain" +gitlab_rails['smtp_enable_starttls_auto'] = true + +# Enable LDAP authentication +# gitlab_rails['ldap_enabled'] = true +# gitlab_rails['ldap_host'] = 'ldap.example.com' +# gitlab_rails['ldap_port'] = 389 +# gitlab_rails['ldap_method'] = 'plain' # 'ssl' or 'plain' +# gitlab_rails['ldap_allow_username_or_email_login'] = false +# gitlab_rails['ldap_uid'] = 'uid' +# gitlab_rails['ldap_base'] = 'ou=users,dc=example,dc=com' From f710dead27fb1f7989fd0bdf40cfe2066ca4c6b1 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 15 May 2015 05:43:09 +0000 Subject: [PATCH 007/255] Update CHANGELOG with v7.10.4 changes --- CHANGELOG | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index e70f28f7fe..8cbdb3be61 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,8 +11,6 @@ v 7.11.0 (unreleased) - Don't allow a merge request to be merged when its title starts with "WIP". - Add a page title to every page. - Allow primary email to be set to an email that you've already added. - - Fix Error 500 when searching Wiki pages (Stan Hu) - - Get Gitorious importer to work again. - Fix clone URL field and X11 Primary selection (Dmitry Medvinsky) - Ignore invalid lines in .gitmodules - Fix "Cannot move project" error message from popping up after a successful transfer (Stan Hu) @@ -21,7 +19,6 @@ v 7.11.0 (unreleased) - Fix "Revspec not found" errors when viewing diffs in a forked project with submodules (Stan Hu) - Improve project page UI - Fix broken file browsing with relative submodule in personal projects (Stan Hu) - - Fix DB error when trying to tag a repository (Stan Hu) - Add "Reply quoting selected text" shortcut key (`r`) - Fix bug causing `@whatever` inside an issue's first code block to be picked up as a user mention. - Fix bug causing `@whatever` inside an inline code snippet (backtick-style) to be picked up as a user mention. @@ -39,7 +36,6 @@ v 7.11.0 (unreleased) - Add default project and snippet visibility settings to the admin web UI. - Show incompatible projects in Google Code import status (Stan Hu) - Fix bug where commit data would not appear in some subdirectories (Stan Hu) - - Unescape branch names in compare commit (Stan Hu) - Task lists are now usable in comments, and will show up in Markdown previews. - Fix bug where avatar filenames were not actually deleted from the database during removal (Stan Hu) - Fix bug where Slack service channel was not saved in admin template settings. (Stan Hu) @@ -65,6 +61,15 @@ v 7.11.0 (unreleased) - Fix mentioning of private groups. - Add style for element in markdown +v 7.10.4 + - Fix migrations broken in 7.10.2 + - Make tags for GitLab installations running on MySQL case sensitive + - Get Gitorious importer to work again. + - Fix adding new group members from admin area + - Fix DB error when trying to tag a repository (Stan Hu) + - Fix Error 500 when searching Wiki pages (Stan Hu) + - Unescape branch names in compare commit (Stan Hu) + v 7.10.2 - Fix CI links on MR page From fb86ec519c2a9928e207b2d4363cb4d7f1705cba Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 15 May 2015 12:45:18 +0200 Subject: [PATCH 008/255] Move stuff around a bit in NotifictionService. --- app/services/notification_service.rb | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 0d7ffbeebd..4af1ab8e4d 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -91,10 +91,14 @@ class NotificationService # * project team members with notification level higher then Participating # def merge_mr(merge_request, current_user) - recipients = reject_muted_users([merge_request.author, merge_request.assignee], merge_request.target_project) + recipients = [merge_request.author, merge_request.assignee] + + recipients = add_project_watchers(recipients, merge_request.target_project) + recipients = reject_muted_users(recipients, merge_request.target_project) + recipients = add_subscribed_users(recipients, merge_request) recipients = reject_unsubscribed_users(recipients, merge_request) - recipients = recipients.concat(project_watchers(merge_request.target_project)).uniq + recipients.delete(current_user) recipients.each do |recipient| @@ -137,20 +141,17 @@ class NotificationService recipients = recipients.concat(participants) # Merge project watchers - recipients = recipients.concat(project_watchers(note.project)).compact.uniq + recipients = add_project_watchers(recipients, note.project) # Reject users with Mention notification level, except those mentioned in _this_ note. recipients = reject_mention_users(recipients - note.mentioned_users, note.project) recipients = recipients + note.mentioned_users - # Reject mutes users recipients = reject_muted_users(recipients, note.project) recipients = add_subscribed_users(recipients, note.noteable) - recipients = reject_unsubscribed_users(recipients, note.noteable) - # Reject author recipients.delete(note.author) # build notify method like 'note_commit_email' @@ -287,6 +288,10 @@ class NotificationService users end + def add_project_watchers(recipients, project) + recipients.concat(project_watchers(project)).compact.uniq + end + # Remove users with disabled notifications from array # Also remove duplications and nil recipients def reject_muted_users(users, project = nil) @@ -403,11 +408,13 @@ class NotificationService [target.author, target.assignee] end - recipients = reject_muted_users(recipients, project) + recipients = add_project_watchers(recipients) recipients = reject_mention_users(recipients, project) + recipients = reject_muted_users(recipients, project) + recipients = add_subscribed_users(recipients, target) - recipients = recipients.concat(project_watchers(project)).uniq recipients = reject_unsubscribed_users(recipients, target) + recipients end From 0b7c4fe0482cbbc480ff363f2037d70fe52125ee Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 15 May 2015 12:45:45 +0200 Subject: [PATCH 009/255] Don't include users without project access in participants. --- CHANGELOG | 2 +- app/models/concerns/participable.rb | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index f92f486064..3dfa92f328 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -44,7 +44,7 @@ v 7.11.0 (unreleased) - Fix bug where avatar filenames were not actually deleted from the database during removal (Stan Hu) - Fix bug where Slack service channel was not saved in admin template settings. (Stan Hu) - Protect OmniAuth request phase against CSRF. - - + - Don't send notifications to mentioned users that don't have access to the project in question. - - Move snippets UI to fluid layout - Improve UI for sidebar. Increase separation between navigation and content diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb index a4832204f7..9f667f47e0 100644 --- a/app/models/concerns/participable.rb +++ b/app/models/concerns/participable.rb @@ -35,8 +35,8 @@ module Participable end end - def participants(current_user = self.author) - self.class.participant_attrs.flat_map do |attr| + def participants(current_user = self.author, project = self.project) + participants = self.class.participant_attrs.flat_map do |attr| meth = method(attr) value = @@ -46,20 +46,28 @@ module Participable meth.call end - participants_for(value, current_user) + participants_for(value, current_user, project) end.compact.uniq + + if project + participants.select! do |user| + user.can?(:read_project, project) + end + end + + participants end private - def participants_for(value, current_user = nil) + def participants_for(value, current_user = nil, project = nil) case value when User [value] when Enumerable, ActiveRecord::Relation - value.flat_map { |v| participants_for(v, current_user) } + value.flat_map { |v| participants_for(v, current_user, project) } when Participable - value.participants(current_user) + value.participants(current_user, project) end end end From d74673fd435e5238b5fb8d29735f31175c71e45f Mon Sep 17 00:00:00 2001 From: Fotis Gimian Date: Tue, 31 Mar 2015 20:25:50 +1100 Subject: [PATCH 010/255] Ensure that the first added admin performs repository imports --- CHANGELOG | 1 + lib/tasks/gitlab/import.rake | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index f92f486064..ade877feb9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -66,6 +66,7 @@ v 7.11.0 (unreleased) - Add style for element in markdown - Spin spinner icon next to "Checking for CI status..." on MR page. - Fix reference links in dashboard activity and ATOM feeds. + - Ensure that the first added admin performs repository imports v 7.10.2 - Fix CI links on MR page diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake index 20abb2fa50..7c98ad3144 100644 --- a/lib/tasks/gitlab/import.rake +++ b/lib/tasks/gitlab/import.rake @@ -35,7 +35,7 @@ namespace :gitlab do if project puts " * #{project.name} (#{repo_path}) exists" else - user = User.admins.first + user = User.admins.reorder("id").first project_params = { name: name, From fab9cbf98da4b23085ef5496d8e23aa9b0b5bd63 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 15 May 2015 13:12:54 +0200 Subject: [PATCH 011/255] Update installation/update guides for 7.11. --- doc/install/installation.md | 8 +- ...r-7.x-to-7.10.md => 6.x-or-7.x-to-7.11.md} | 25 +++-- doc/update/7.10-to-7.11.md | 103 ++++++++++++++++++ 3 files changed, 120 insertions(+), 16 deletions(-) rename doc/update/{6.x-or-7.x-to-7.10.md => 6.x-or-7.x-to-7.11.md} (93%) create mode 100644 doc/update/7.10-to-7.11.md diff --git a/doc/install/installation.md b/doc/install/installation.md index e777f6bbb4..d167d2889b 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -68,7 +68,7 @@ If you want to use Kerberos for user authentication, then install libkrb5-dev: sudo apt-get install libkrb5-dev -**Note:** If you don't know what Kerberos is, then you certainly don't need it. +**Note:** If you don't know what Kerberos is, you can assume you don't need it. Make sure you have the right version of Git installed @@ -195,9 +195,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-10-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-11-stable gitlab -**Note:** You can change `7-10-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! +**Note:** You can change `7-11-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! ### Configure It @@ -294,7 +294,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da GitLab Shell is an SSH access and repository management software developed specially for GitLab. # Run the installation task for gitlab-shell (replace `REDIS_URL` if needed): - sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.2] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production + sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.3] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production # By default, the gitlab-shell config is generated from your main GitLab config. # You can review (and modify) the gitlab-shell config as follows: diff --git a/doc/update/6.x-or-7.x-to-7.10.md b/doc/update/6.x-or-7.x-to-7.11.md similarity index 93% rename from doc/update/6.x-or-7.x-to-7.10.md rename to doc/update/6.x-or-7.x-to-7.11.md index 39e12f32d0..b1daa648f1 100644 --- a/doc/update/6.x-or-7.x-to-7.10.md +++ b/doc/update/6.x-or-7.x-to-7.11.md @@ -1,7 +1,7 @@ -# From 6.x or 7.x to 7.10 -*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.x-or-7.x-to-7.10.md) for the most up to date instructions.* +# From 6.x or 7.x to 7.11 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.x-or-7.x-to-7.11.md) for the most up to date instructions.* -This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.10. +This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.11. ## Global issue numbers @@ -71,7 +71,7 @@ sudo -u git -H git checkout -- db/schema.rb # local changes will be restored aut For GitLab Community Edition: ```bash -sudo -u git -H git checkout 7-10-stable +sudo -u git -H git checkout 7-11-stable ``` OR @@ -79,7 +79,7 @@ OR For GitLab Enterprise Edition: ```bash -sudo -u git -H git checkout 7-10-stable-ee +sudo -u git -H git checkout 7-11-stable-ee ``` ## 4. Install additional packages @@ -91,7 +91,8 @@ sudo apt-get install logrotate # Install pkg-config and cmake, which is needed for the latest versions of rugged sudo apt-get install pkg-config cmake -# Install Kerberos header files, which are needed for GitLab EE Kerberos support +# If you want to use Kerberos with GitLab EE for user authentication, install Kerberos header files +# If you don't know what Kerberos is, you can assume you don't need it. sudo apt-get install libkrb5-dev # Install nodejs, javascript runtime required for assets @@ -126,7 +127,7 @@ sudo apt-get install nodejs ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v2.6.2 +sudo -u git -H git checkout v2.6.3 ``` ## 7. Install libs, migrations, etc. @@ -161,11 +162,11 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab TIP: to see what changed in `gitlab.yml.example` in this release use next command: ``` -git diff 6-0-stable:config/gitlab.yml.example 7-10-stable:config/gitlab.yml.example +git diff 6-0-stable:config/gitlab.yml.example 7-11-stable:config/gitlab.yml.example ``` -* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/config/gitlab.yml.example but with your settings. -* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/config/unicorn.rb.example but with your settings. +* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-11-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-11-stable/config/unicorn.rb.example but with your settings. * Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.6.0/config.yml.example but with your settings. * Copy rack attack middleware config @@ -181,8 +182,8 @@ sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab ### Change Nginx settings -* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/lib/support/nginx/gitlab but with your settings. -* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/lib/support/nginx/gitlab-ssl but with your settings. +* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-11-stable/lib/support/nginx/gitlab but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-11-stable/lib/support/nginx/gitlab-ssl but with your settings. * A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section. ## 9. Start application diff --git a/doc/update/7.10-to-7.11.md b/doc/update/7.10-to-7.11.md new file mode 100644 index 0000000000..79bc6de1e4 --- /dev/null +++ b/doc/update/7.10-to-7.11.md @@ -0,0 +1,103 @@ +# From 7.10 to 7.11 + +### 0. Stop server + + sudo service gitlab stop + +### 1. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 2. Get latest code + +```bash +sudo -u git -H git fetch --all +sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically +``` + +For GitLab Community Edition: + +```bash +sudo -u git -H git checkout 7-11-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 7-11-stable-ee +``` + +### 3. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v2.6.3 +``` + +### 4. Install libs, migrations, etc. + +```bash +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without ... postgres') +sudo -u git -H bundle install --without development test postgres --deployment + +# PostgreSQL installations (note: the line below states '--without ... mysql') +sudo -u git -H bundle install --without development test mysql --deployment + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +### 5. Update config files + +#### New configuration options for `gitlab.yml` + +There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`. + +``` +git diff origin/7-10-stable:config/gitlab.yml.example origin/7-11-stable:config/gitlab.yml.example +`````` + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. Check application status + +Check if GitLab and its environment are configured correctly: + + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations, the upgrade is complete! + +## Things went south? Revert to previous version (7.10) + +### 1. Revert the code to the previous version +Follow the [upgrade guide from 7.9 to 7.10](7.9-to-7.10.md), except for the database migration +(The backup is already migrated to the previous version) + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` +If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above. From c74375ae60970d812f034c34037d27ac47517c1d Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 15 May 2015 13:50:04 +0200 Subject: [PATCH 012/255] Add explanation about WIP status to MR form. --- app/assets/stylesheets/generic/forms.scss | 1 - app/views/projects/_issuable_form.html.haml | 9 +++++++++ .../projects/merge_requests/show/_mr_accept.html.haml | 8 ++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/generic/forms.scss index 266041403e..7e070b4f38 100644 --- a/app/assets/stylesheets/generic/forms.scss +++ b/app/assets/stylesheets/generic/forms.scss @@ -89,7 +89,6 @@ label { @include box-shadow(none); } -.issuable-description, .wiki-content { margin-top: 35px; } diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml index e321a84974..3141acf000 100644 --- a/app/views/projects/_issuable_form.html.haml +++ b/app/views/projects/_issuable_form.html.haml @@ -11,6 +11,15 @@ .col-sm-10 = f.text_field :title, maxlength: 255, autofocus: true, class: 'form-control pad js-gfm-input', required: true + + - if issuable.is_a?(MergeRequest) + %p.help-block.hint.col-sm-12 + - if issuable.work_in_progress? + This merge request is marked a Work In Progress. + When it's ready, remove the WIP prefix from the title to allow it to be accepted. + - else + To prevent this merge request from being accepted until it's ready, + mark it a Work In Progress by starting the title with [WIP] or WIP:. .form-group.issuable-description = f.label :description, 'Description', class: 'control-label' .col-sm-10 diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml index cb536214c6..882b219f6e 100644 --- a/app/views/projects/merge_requests/show/_mr_accept.html.haml +++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml @@ -6,7 +6,7 @@ .automerge_widget.cannot_be_merged.hide %strong This request can't be merged automatically. Even if it could be merged, you don't have permission to do so. .automerge_widget.work_in_progress.hide - %strong This request can't be merged automatically because it is marked a Work In Progress. Even if it could be merged, you don't have permission to do so. + %strong This request can't be accepted because it is marked a Work In Progress. Even if it could be accepted, you don't have permission to do so. .automerge_widget.can_be_merged.hide %strong This request can be merged automatically, but you don't have permission to do so. @@ -57,11 +57,11 @@ %i.fa.fa-warning Accept Merge Request   - This usually happens when git can not resolve conflicts between branches automatically. + This usually happens when Git can not resolve conflicts between branches automatically. .automerge_widget.work_in_progress.hide %h4 - This request can't be merged because it is marked a Work In Progress. + This request can't be accepted because it is marked a Work In Progress. %p %button.btn.disabled{:type => 'button'} @@ -69,7 +69,7 @@ Accept Merge Request   - When the merge request is ready, remove the "WIP" prefix from the title to allow it to be merged. + When the merge request is ready, remove the "WIP" prefix from the title to allow it to be accepted. .automerge_widget.unchecked %p From 6960e83b1e9b4aa7dfb0d5fa2d5d5e70a81bb680 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 15 May 2015 13:54:41 +0200 Subject: [PATCH 013/255] Fix. --- app/services/notification_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 4af1ab8e4d..312b56eb87 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -408,7 +408,7 @@ class NotificationService [target.author, target.assignee] end - recipients = add_project_watchers(recipients) + recipients = add_project_watchers(recipients, project) recipients = reject_mention_users(recipients, project) recipients = reject_muted_users(recipients, project) From b77e1ae6f78461a1721150538158480d99bd899b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 15 May 2015 14:34:22 +0200 Subject: [PATCH 014/255] Don't require DB conncetion in AttrEncrypted. --- .../attr_encrypted_no_db_connection.rb | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 config/initializers/attr_encrypted_no_db_connection.rb diff --git a/config/initializers/attr_encrypted_no_db_connection.rb b/config/initializers/attr_encrypted_no_db_connection.rb new file mode 100644 index 0000000000..72e257a013 --- /dev/null +++ b/config/initializers/attr_encrypted_no_db_connection.rb @@ -0,0 +1,29 @@ +module AttrEncrypted + module Adapters + module ActiveRecord + protected + + def attribute_instance_methods_as_symbols + # We add accessor methods of the db columns to the list of instance + # methods returned to let ActiveRecord define the accessor methods + # for the db columns + if connection_established? && table_exists? + columns_hash.keys.inject(super) {|instance_methods, column_name| instance_methods.concat [column_name.to_sym, :"#{column_name}="]} + else + super + end + end + + def connection_established? + begin + # use with_connection so the connection doesn't stay pinned to the thread. + ActiveRecord::Base.connection_pool.with_connection { + ActiveRecord::Base.connection.active? + } + rescue Exception + false + end + end + end + end +end From ba07c9f7f599cecac2c0840484f8bfc62d9e716b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 15 May 2015 14:56:04 +0200 Subject: [PATCH 015/255] Improve fix. --- .../attr_encrypted_no_db_connection.rb | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/config/initializers/attr_encrypted_no_db_connection.rb b/config/initializers/attr_encrypted_no_db_connection.rb index 72e257a013..e270028f45 100644 --- a/config/initializers/attr_encrypted_no_db_connection.rb +++ b/config/initializers/attr_encrypted_no_db_connection.rb @@ -1,25 +1,24 @@ module AttrEncrypted module Adapters module ActiveRecord - protected - - def attribute_instance_methods_as_symbols - # We add accessor methods of the db columns to the list of instance - # methods returned to let ActiveRecord define the accessor methods - # for the db columns - if connection_established? && table_exists? - columns_hash.keys.inject(super) {|instance_methods, column_name| instance_methods.concat [column_name.to_sym, :"#{column_name}="]} + def attribute_instance_methods_as_symbols_with_no_db_connection + if connection_established? + # Call version from AttrEncrypted::Adapters::ActiveRecord + attribute_instance_methods_as_symbols_without_no_db_connection else - super + # Call version from AttrEncrypted (`super` with regards to AttrEncrypted::Adapters::ActiveRecord) + AttrEncrypted.instance_method(:attribute_instance_methods_as_symbols).bind(self).call end end + alias_method_chain :attribute_instance_methods_as_symbols, :no_db_connection + + private + def connection_established? begin - # use with_connection so the connection doesn't stay pinned to the thread. - ActiveRecord::Base.connection_pool.with_connection { - ActiveRecord::Base.connection.active? - } + # Use with_connection so the connection doesn't stay pinned to the thread. + ActiveRecord::Base.connection_pool.with_connection { |con| con.active? } rescue Exception false end From 61ceb45088d9bfb04890866718f87686bfb5f3c1 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 15 May 2015 15:32:49 +0200 Subject: [PATCH 016/255] Fix. --- .../attr_encrypted_no_db_connection.rb | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/config/initializers/attr_encrypted_no_db_connection.rb b/config/initializers/attr_encrypted_no_db_connection.rb index e270028f45..c668864089 100644 --- a/config/initializers/attr_encrypted_no_db_connection.rb +++ b/config/initializers/attr_encrypted_no_db_connection.rb @@ -2,27 +2,19 @@ module AttrEncrypted module Adapters module ActiveRecord def attribute_instance_methods_as_symbols_with_no_db_connection - if connection_established? + # Use with_connection so the connection doesn't stay pinned to the thread. + connected = ::ActiveRecord::Base.connection_pool.with_connection(&:active?) rescue false + + if connected # Call version from AttrEncrypted::Adapters::ActiveRecord attribute_instance_methods_as_symbols_without_no_db_connection else - # Call version from AttrEncrypted (`super` with regards to AttrEncrypted::Adapters::ActiveRecord) + # Call version from AttrEncrypted, i.e., `super` with regards to AttrEncrypted::Adapters::ActiveRecord AttrEncrypted.instance_method(:attribute_instance_methods_as_symbols).bind(self).call end end alias_method_chain :attribute_instance_methods_as_symbols, :no_db_connection - - private - - def connection_established? - begin - # Use with_connection so the connection doesn't stay pinned to the thread. - ActiveRecord::Base.connection_pool.with_connection { |con| con.active? } - rescue Exception - false - end - end end end end From 5210778d6ef61d006a67cec8a785da6c112fc76c Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 15 May 2015 15:38:05 +0200 Subject: [PATCH 017/255] Fix specs. --- spec/services/issues/close_service_spec.rb | 2 +- spec/services/issues/update_service_spec.rb | 2 +- spec/services/notification_service_spec.rb | 15 ++++++++++----- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index d15dff1b52..0e5ae724bf 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -1,10 +1,10 @@ require 'spec_helper' describe Issues::CloseService do - let(:project) { create(:empty_project) } let(:user) { create(:user) } let(:user2) { create(:user) } let(:issue) { create(:issue, assignee: user2) } + let(:project) { issue.project } before do project.team << [user, :master] diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 22b89bec96..6fc69e9362 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -1,11 +1,11 @@ require 'spec_helper' describe Issues::UpdateService do - let(:project) { create(:empty_project) } let(:user) { create(:user) } let(:user2) { create(:user) } let(:issue) { create(:issue) } let(:label) { create(:label) } + let(:project) { issue.project } before do project.team << [user, :master] diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index 2a54b2e920..62a99d1595 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -31,7 +31,8 @@ describe NotificationService do describe 'Notes' do context 'issue note' do - let(:issue) { create(:issue, assignee: create(:user)) } + let(:project) { create(:empty_project, :public) } + let(:issue) { create(:issue, project: project, assignee: create(:user)) } let(:mentioned_issue) { create(:issue, assignee: issue.assignee) } let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@mention referenced') } @@ -101,7 +102,8 @@ describe NotificationService do end context 'issue note mention' do - let(:issue) { create(:issue, assignee: create(:user)) } + let(:project) { create(:empty_project, :public) } + let(:issue) { create(:issue, project: project, assignee: create(:user)) } let(:mentioned_issue) { create(:issue, assignee: issue.assignee) } let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id, note: '@all mentioned') } @@ -145,7 +147,8 @@ describe NotificationService do end context 'commit note' do - let(:note) { create(:note_on_commit) } + let(:project) { create(:project, :public) } + let(:note) { create(:note_on_commit, project: project) } before do build_team(note.project) @@ -192,7 +195,8 @@ describe NotificationService do end describe 'Issues' do - let(:issue) { create :issue, assignee: create(:user), description: 'cc @participant' } + let(:project) { create(:empty_project, :public) } + let(:issue) { create :issue, project: project, assignee: create(:user), description: 'cc @participant' } before do build_team(issue.project) @@ -295,7 +299,8 @@ describe NotificationService do end describe 'Merge Requests' do - let(:merge_request) { create :merge_request, assignee: create(:user) } + let(:project) { create(:project, :public) } + let(:merge_request) { create :merge_request, source_project: project, assignee: create(:user) } before do build_team(merge_request.target_project) From ed3298fc019d224b9048901972ac03e5272a3b25 Mon Sep 17 00:00:00 2001 From: Jakub Jirutka Date: Mon, 16 Feb 2015 13:16:26 +0100 Subject: [PATCH 018/255] Allow to configure gitlab_shell_secret location --- CHANGELOG | 1 + config/gitlab.yml.example | 4 ++++ config/initializers/1_settings.rb | 1 + config/initializers/gitlab_shell_secret_token.rb | 8 ++++---- lib/api/helpers.rb | 2 +- spec/requests/api/internal_spec.rb | 2 +- 6 files changed, 12 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ade877feb9..5afd70a2f4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -67,6 +67,7 @@ v 7.11.0 (unreleased) - Spin spinner icon next to "Checking for CI status..." on MR page. - Fix reference links in dashboard activity and ATOM feeds. - Ensure that the first added admin performs repository imports + - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) v 7.10.2 - Fix CI links on MR page diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index bd2081688d..fbc7f515f3 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -245,6 +245,10 @@ production: &base repos_path: /home/git/repositories/ hooks_path: /home/git/gitlab-shell/hooks/ + # File that contains the secret key for verifying access for gitlab-shell. + # Default is '.gitlab_shell_secret' relative to Rails.root (i.e. root of the GitLab app). + # secret_file: /home/git/gitlab/.gitlab_shell_secret + # Git over HTTP upload_pack: true receive_pack: true diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index e5ac66a232..2351ef7b0c 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -148,6 +148,7 @@ Settings.gravatar['ssl_url'] ||= 'https://secure.gravatar.com/avatar/%{hash}? Settings['gitlab_shell'] ||= Settingslogic.new({}) Settings.gitlab_shell['path'] ||= Settings.gitlab['user_home'] + '/gitlab-shell/' Settings.gitlab_shell['hooks_path'] ||= Settings.gitlab['user_home'] + '/gitlab-shell/hooks/' +Settings.gitlab_shell['secret_file'] ||= Rails.root.join('.gitlab_shell_secret') Settings.gitlab_shell['receive_pack'] = true if Settings.gitlab_shell['receive_pack'].nil? Settings.gitlab_shell['upload_pack'] = true if Settings.gitlab_shell['upload_pack'].nil? Settings.gitlab_shell['repos_path'] ||= Settings.gitlab['user_home'] + '/repositories/' diff --git a/config/initializers/gitlab_shell_secret_token.rb b/config/initializers/gitlab_shell_secret_token.rb index e7c9f0ba7c..751fccead0 100644 --- a/config/initializers/gitlab_shell_secret_token.rb +++ b/config/initializers/gitlab_shell_secret_token.rb @@ -5,8 +5,7 @@ require 'securerandom' # Your secret key for verifying the gitlab_shell. -secret_file = Rails.root.join('.gitlab_shell_secret') -gitlab_shell_symlink = File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret') +secret_file = Gitlab.config.gitlab_shell.secret_file unless File.exist? secret_file # Generate a new token of 16 random hexadecimal characters and store it in secret_file. @@ -14,6 +13,7 @@ unless File.exist? secret_file File.write(secret_file, token) end -if File.exist?(Gitlab.config.gitlab_shell.path) && !File.exist?(gitlab_shell_symlink) - FileUtils.symlink(secret_file, gitlab_shell_symlink) +link_path = File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret') +if File.exist?(Gitlab.config.gitlab_shell.path) && !File.exist?(link_path) + FileUtils.symlink(secret_file, link_path) end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 85e9081680..1ebf9a1f02 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -243,7 +243,7 @@ module API end def secret_token - File.read(Rails.root.join('.gitlab_shell_secret')).chomp + File.read(Gitlab.config.gitlab_shell.secret_file).chomp end def handle_member_errors(errors) diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index 4c7d15d659..8d0ae1475c 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -5,7 +5,7 @@ describe API::API, api: true do let(:user) { create(:user) } let(:key) { create(:key, user: user) } let(:project) { create(:project) } - let(:secret_token) { File.read Rails.root.join('.gitlab_shell_secret') } + let(:secret_token) { File.read Gitlab.config.gitlab_shell.secret_file } describe "GET /internal/check", no_db: true do it do From 0bfab084a811d7dad1f1929ee7b5c2bc59015173 Mon Sep 17 00:00:00 2001 From: Vinnie Okada Date: Sun, 26 Apr 2015 22:04:33 -0600 Subject: [PATCH 019/255] Explain reset token expiration in emails Tell new users when their password reset token expires and provide a link to get a new one. --- CHANGELOG | 1 + app/helpers/emails_helper.rb | 19 ++++++++++ app/views/notify/new_user_email.html.haml | 2 + app/views/notify/new_user_email.text.erb | 2 + spec/helpers/emails_helper_spec.rb | 46 +++++++++++++++++++++++ spec/mailers/notify_spec.rb | 5 +++ 6 files changed, 75 insertions(+) create mode 100644 spec/helpers/emails_helper_spec.rb diff --git a/CHANGELOG b/CHANGELOG index ade877feb9..15bfe570f1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -32,6 +32,7 @@ v 7.11.0 (unreleased) - Show Atom feed buttons everywhere where applicable. - Add project activity atom feed. - Don't crash when an MR from a fork has a cross-reference comment from the target project on one of its commits. + - Explain how to get a new password reset token in welcome emails - Include commit comments in MR from a forked project. - Fix adding new group members from admin area - Group milestones by title in the dashboard and all other issue views. diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index 0df3ecc90b..12aa561a14 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -35,4 +35,23 @@ module EmailsHelper lexer = Rugments::Lexers::Diff.new raw formatter.format(lexer.lex(diffcontent)) end + + def password_reset_token_valid_time + valid_hours = Devise.reset_password_within / 60 / 60 + if valid_hours >= 24 + unit = 'day' + valid_length = (valid_hours / 24).floor + else + unit = 'hour' + valid_length = valid_hours.floor + end + + pluralize(valid_length, unit) + end + + def reset_token_expire_message + link_tag = link_to('request a new one', new_user_password_url) + msg = "This link is valid for #{password_reset_token_valid_time}. " + msg << "After it expires, you can #{link_tag}." + end end diff --git a/app/views/notify/new_user_email.html.haml b/app/views/notify/new_user_email.html.haml index ebbe98dd47..39cb01d4d2 100644 --- a/app/views/notify/new_user_email.html.haml +++ b/app/views/notify/new_user_email.html.haml @@ -12,3 +12,5 @@ - if @user.created_by_id %p = link_to "Click here to set your password", edit_password_url(@user, :reset_password_token => @token) + %p + = reset_token_expire_message diff --git a/app/views/notify/new_user_email.text.erb b/app/views/notify/new_user_email.text.erb index 96b26879a7..dd9b71e3b8 100644 --- a/app/views/notify/new_user_email.text.erb +++ b/app/views/notify/new_user_email.text.erb @@ -5,4 +5,6 @@ The Administrator created an account for you. Now you are a member of the compan login.................. <%= @user.email %> <% if @user.created_by_id %> <%= link_to "Click here to set your password", edit_password_url(@user, :reset_password_token => @token) %> + + <%= reset_token_expire_message %> <% end %> diff --git a/spec/helpers/emails_helper_spec.rb b/spec/helpers/emails_helper_spec.rb new file mode 100644 index 0000000000..7a3e38d7e6 --- /dev/null +++ b/spec/helpers/emails_helper_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe EmailsHelper do + describe 'password_reset_token_valid_time' do + def validate_time_string(time_limit, expected_string) + Devise.reset_password_within = time_limit + expect(password_reset_token_valid_time).to eq(expected_string) + end + + context 'when time limit is less than 2 hours' do + it 'should display the time in hours using a singular unit' do + validate_time_string(1.hour, '1 hour') + end + end + + context 'when time limit is 2 or more hours' do + it 'should display the time in hours using a plural unit' do + validate_time_string(2.hours, '2 hours') + end + end + + context 'when time limit contains fractions of an hour' do + it 'should round down to the nearest hour' do + validate_time_string(96.minutes, '1 hour') + end + end + + context 'when time limit is 24 or more hours' do + it 'should display the time in days using a singular unit' do + validate_time_string(24.hours, '1 day') + end + end + + context 'when time limit is 2 or more days' do + it 'should display the time in days using a plural unit' do + validate_time_string(2.days, '2 days') + end + end + + context 'when time limit contains fractions of a day' do + it 'should round down to the nearest day' do + validate_time_string(57.hours, '2 days') + end + end + end +end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index dbcf7286e4..4da91eea98 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -91,6 +91,11 @@ describe Notify do it 'includes a link to the site' do is_expected.to have_body_text /#{example_site_path}/ end + + it 'explains the reset link expiration' do + is_expected.to have_body_text(/This link is valid for \d+ (hours?|days?)/) + is_expected.to have_body_text(new_user_password_url) + end end From c68c23210bdf9f0d7212fa55e7bef71ac0f87bcf Mon Sep 17 00:00:00 2001 From: Vinnie Okada Date: Wed, 13 May 2015 20:29:15 -0600 Subject: [PATCH 020/255] Redirect if password reset token is expired Don't display the password editing form if the user's token is expired; redirect to the form that allows users to request a new password reset token. --- app/controllers/passwords_controller.rb | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb index 88459d4080..fbb9d371a7 100644 --- a/app/controllers/passwords_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -36,4 +36,24 @@ class PasswordsController < Devise::PasswordsController end end end + + def edit + super + reset_password_token = Devise.token_generator.digest( + User, + :reset_password_token, + resource.reset_password_token + ) + + unless reset_password_token.nil? + user = User.where( + reset_password_token: reset_password_token + ).first_or_initialize + + unless user.reset_password_period_valid? + flash[:alert] = 'Your password reset token has expired.' + redirect_to(new_user_password_url) + end + end + end end From af428b12598f06073327bc63d75d9c358c95067a Mon Sep 17 00:00:00 2001 From: Vinnie Okada Date: Wed, 13 May 2015 21:57:16 -0600 Subject: [PATCH 021/255] Fill in email on the new password form --- app/controllers/passwords_controller.rb | 2 +- app/helpers/emails_helper.rb | 2 +- app/views/devise/passwords/new.html.haml | 2 +- app/views/notify/new_user_email.html.haml | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb index fbb9d371a7..145f27b67d 100644 --- a/app/controllers/passwords_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -52,7 +52,7 @@ class PasswordsController < Devise::PasswordsController unless user.reset_password_period_valid? flash[:alert] = 'Your password reset token has expired.' - redirect_to(new_user_password_url) + redirect_to(new_user_password_url(user_email: user['email'])) end end end diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb index 12aa561a14..128de18bc4 100644 --- a/app/helpers/emails_helper.rb +++ b/app/helpers/emails_helper.rb @@ -50,7 +50,7 @@ module EmailsHelper end def reset_token_expire_message - link_tag = link_to('request a new one', new_user_password_url) + link_tag = link_to('request a new one', new_user_password_url(user_email: @user.email)) msg = "This link is valid for #{password_reset_token_valid_time}. " msg << "After it expires, you can #{link_tag}." end diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml index e8820daf58..29ffe8a8be 100644 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -6,7 +6,7 @@ .devise-errors = devise_error_messages! .clearfix.append-bottom-20 - = f.email_field :email, placeholder: "Email", class: "form-control", required: true + = f.email_field :email, placeholder: "Email", class: "form-control", required: true, value: params[:user_email] .clearfix = f.submit "Reset password", class: "btn-primary btn" diff --git a/app/views/notify/new_user_email.html.haml b/app/views/notify/new_user_email.html.haml index 39cb01d4d2..4feacdaacf 100644 --- a/app/views/notify/new_user_email.html.haml +++ b/app/views/notify/new_user_email.html.haml @@ -11,6 +11,6 @@ - if @user.created_by_id %p - = link_to "Click here to set your password", edit_password_url(@user, :reset_password_token => @token) + = link_to "Click here to set your password", edit_password_url(@user, reset_password_token: @token) %p = reset_token_expire_message From c7ae428b4cf36203a32d98df5542761e7e370200 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 16 May 2015 16:09:04 -0400 Subject: [PATCH 022/255] Fix labels for project/snippet visibility selection --- app/views/shared/_visibility_radios.html.haml | 2 +- app/views/shared/snippets/_form.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml index b07c4d20f1..02416125a7 100644 --- a/app/views/shared/_visibility_radios.html.haml +++ b/app/views/shared/_visibility_radios.html.haml @@ -1,7 +1,7 @@ - Gitlab::VisibilityLevel.values.each do |level| .radio - restricted = restricted_visibility_levels.include?(level) - = label model_method, level do + = form.label "#{model_method}_#{level}" do = form.radio_button model_method, level, checked: (selected_level == level), disabled: restricted = visibility_level_icon(level) .option-title diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index 6783587bda..9610f9ce41 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -11,7 +11,7 @@ .col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true = render 'shared/visibility_level', f: f, visibility_level: visibility_level, can_change_visibility_level: true, form_model: @snippet - + .form-group .file-editor = f.label :file_name, "File", class: 'control-label' From 2be995d71ca111b06b070bb0b724d76ea752ecb0 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 16 May 2015 16:09:32 -0400 Subject: [PATCH 023/255] Increase left margin for visibility descriptions Now they line up with their title. --- app/assets/stylesheets/pages/projects.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 49e5aad1f6..16b9814a0f 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -129,7 +129,7 @@ } .option-descr { - margin-left: 24px; + margin-left: 36px; color: $gray; } } From 56e06b03bfb76a6fea263940cd4512276c3225c8 Mon Sep 17 00:00:00 2001 From: Vinnie Okada Date: Wed, 13 May 2015 22:21:34 -0600 Subject: [PATCH 024/255] Add tests for password reset token changes --- spec/mailers/notify_spec.rb | 57 ++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 4da91eea98..37607b55eb 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -5,6 +5,8 @@ describe Notify do include EmailSpec::Matchers include RepoHelpers + new_user_address = 'newguy@example.com' + let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name } let(:gitlab_sender) { Gitlab.config.gitlab.email_from } let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to } @@ -55,18 +57,9 @@ describe Notify do end end - describe 'for new users, the email' do - let(:example_site_path) { root_path } - let(:new_user) { create(:user, email: 'newguy@example.com', created_by_id: 1) } - - token = 'kETLwRaayvigPq_x3SNM' - - subject { Notify.new_user_email(new_user.id, token) } - - it_behaves_like 'an email sent from GitLab' - + shared_examples 'a new user email' do |user_email, site_path| it 'is sent to the new user' do - is_expected.to deliver_to new_user.email + is_expected.to deliver_to user_email end it 'has the correct subject' do @@ -74,9 +67,25 @@ describe Notify do end it 'contains the new user\'s login name' do - is_expected.to have_body_text /#{new_user.email}/ + is_expected.to have_body_text /#{user_email}/ end + it 'includes a link to the site' do + is_expected.to have_body_text /#{site_path}/ + end + end + + describe 'for new users, the email' do + let(:example_site_path) { root_path } + let(:new_user) { create(:user, email: new_user_address, created_by_id: 1) } + + token = 'kETLwRaayvigPq_x3SNM' + + subject { Notify.new_user_email(new_user.id, token) } + + it_behaves_like 'an email sent from GitLab' + it_behaves_like 'a new user email', new_user_address + it 'contains the password text' do is_expected.to have_body_text /Click here to set your password/ end @@ -88,44 +97,26 @@ describe Notify do ) end - it 'includes a link to the site' do - is_expected.to have_body_text /#{example_site_path}/ - end - it 'explains the reset link expiration' do is_expected.to have_body_text(/This link is valid for \d+ (hours?|days?)/) is_expected.to have_body_text(new_user_password_url) + is_expected.to have_body_text(/\?user_email=.*%40.*/) end end describe 'for users that signed up, the email' do let(:example_site_path) { root_path } - let(:new_user) { create(:user, email: 'newguy@example.com', password: "securePassword") } + let(:new_user) { create(:user, email: new_user_address, password: "securePassword") } subject { Notify.new_user_email(new_user.id) } it_behaves_like 'an email sent from GitLab' - - it 'is sent to the new user' do - is_expected.to deliver_to new_user.email - end - - it 'has the correct subject' do - is_expected.to have_subject /^Account was created for you$/i - end - - it 'contains the new user\'s login name' do - is_expected.to have_body_text /#{new_user.email}/ - end + it_behaves_like 'a new user email', new_user_address it 'should not contain the new user\'s password' do is_expected.not_to have_body_text /password/ end - - it 'includes a link to the site' do - is_expected.to have_body_text /#{example_site_path}/ - end end describe 'user added ssh key' do From b606bfdcafea4322fd08700be42b6611a0274c45 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Sun, 17 May 2015 18:11:41 +0200 Subject: [PATCH 025/255] Fix copy. --- app/views/projects/_issuable_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml index 3141acf000..f3464e5eb1 100644 --- a/app/views/projects/_issuable_form.html.haml +++ b/app/views/projects/_issuable_form.html.haml @@ -18,7 +18,7 @@ This merge request is marked a Work In Progress. When it's ready, remove the WIP prefix from the title to allow it to be accepted. - else - To prevent this merge request from being accepted until it's ready, + To prevent this merge request from being accepted before it's ready, mark it a Work In Progress by starting the title with [WIP] or WIP:. .form-group.issuable-description = f.label :description, 'Description', class: 'control-label' From bd85e8ea5a54757e4a6f6b6ed6687b1e21e411e9 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sun, 17 May 2015 18:08:37 -0400 Subject: [PATCH 026/255] Fix default tooltip placement --- app/assets/javascripts/application.js.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index bb9da14701..caf18c0d86 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -140,8 +140,8 @@ $ -> # Place the logo tooltip on the right when collapsed, bottom when expanded $el.parents('header').hasClass('header-collapsed') and 'right' or 'bottom' else - # Otherwise use the data-placement attribute like normal - $el.data('placement') + # Otherwise use the data-placement attribute, or 'bottom' if undefined + $el.data('placement') or 'bottom' }) # Form submitter From 241f5971ba657960a316fd3e43c7db5a6de41969 Mon Sep 17 00:00:00 2001 From: Nikita Verkhovin Date: Sun, 17 May 2015 23:32:58 +0600 Subject: [PATCH 027/255] Add search issues/MR by number --- CHANGELOG | 2 +- app/controllers/projects/issues_controller.rb | 10 +++++++++- .../projects/merge_requests_controller.rb | 10 +++++++++- lib/gitlab/search_results.rb | 16 ++++++++++++++-- 4 files changed, 33 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 11eeac0108..b0c2bb74e4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -45,7 +45,7 @@ v 7.11.0 (unreleased) - Fix bug where Slack service channel was not saved in admin template settings. (Stan Hu) - Protect OmniAuth request phase against CSRF. - Don't send notifications to mentioned users that don't have access to the project in question. - - + - Add search issues/MR by number - Move snippets UI to fluid layout - Improve UI for sidebar. Increase separation between navigation and content - Improve new project command options (Ben Bodenmiller) diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index c524e1a0ea..7d168aa827 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -19,7 +19,15 @@ class Projects::IssuesController < Projects::ApplicationController def index terms = params['issue_search'] @issues = get_issues_collection - @issues = @issues.full_search(terms) if terms.present? + + if terms.present? + if terms =~ /\A#(\d+)\z/ + @issues = @issues.where(iid: $1) + else + @issues = @issues.full_search(terms) + end + end + @issues = @issues.page(params[:page]).per(PER_PAGE) respond_to do |format| diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 5b93e95866..c7467e9b2f 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -19,7 +19,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController def index terms = params['issue_search'] @merge_requests = get_merge_requests_collection - @merge_requests = @merge_requests.full_search(terms) if terms.present? + + if terms.present? + if terms =~ /\A[#!](\d+)\z/ + @merge_requests = @merge_requests.where(iid: $1) + else + @merge_requests = @merge_requests.full_search(terms) + end + end + @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE) respond_to do |format| diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb index 75a3dfe37c..06245374bc 100644 --- a/lib/gitlab/search_results.rb +++ b/lib/gitlab/search_results.rb @@ -51,11 +51,23 @@ module Gitlab end def issues - Issue.where(project_id: limit_project_ids).full_search(query).order('updated_at DESC') + issues = Issue.where(project_id: limit_project_ids) + if query =~ /#(\d+)\z/ + issues = issues.where(iid: $1) + else + issues = issues.full_search(query) + end + issues.order('updated_at DESC') end def merge_requests - MergeRequest.in_projects(limit_project_ids).full_search(query).order('updated_at DESC') + merge_requests = MergeRequest.in_projects(limit_project_ids) + if query =~ /[#!](\d+)\z/ + merge_requests = merge_requests.where(iid: $1) + else + merge_requests = merge_requests.full_search(query) + end + merge_requests.order('updated_at DESC') end def default_scope From 5483c2b0a93c7313c7a7b42a402090f3f235519b Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 18 May 2015 13:36:41 +0200 Subject: [PATCH 028/255] How to dump a production DB to staging --- doc/development/README.md | 1 + doc/development/db_dump.md | 45 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 doc/development/db_dump.md diff --git a/doc/development/README.md b/doc/development/README.md index 16df0b40c4..6bc8e1888d 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -7,3 +7,4 @@ - [Sidekiq debugging](sidekiq_debugging.md) - [UI guide](ui_guide.md) for building GitLab with existing css styles and elements - [Migration Style Guide](migration_style_guide.md) for creating safe migrations +- [How to dump production data to staging](dump_db.md) diff --git a/doc/development/db_dump.md b/doc/development/db_dump.md new file mode 100644 index 0000000000..4ad3bd534e --- /dev/null +++ b/doc/development/db_dump.md @@ -0,0 +1,45 @@ +# Importing a database dump into a staging enviroment + +Sometimes it is useful to import the database from a production environment +into a staging environment for testing. The procedure below assumes you have +SSH+sudo access to both the production environment and the staging VM. + +On the staging VM, add the following line to `/etc/gitlab/gitlab.rb` to speed up +large database imports. + +``` +# On STAGING +echo "postgresql['checkpoint_segments'] = 64" | sudo tee -a /etc/gitlab/gitlab.rb +sudo touch /etc/gitlab/skip-auto-migrations +sudo gitlab-ctl reconfigure +``` + +Next, we let the production environment stream a compressed SQL dump to our +local machine via SSH, and redirect this stream to a psql client on the staging +VM. + +``` +# On LOCAL MACHINE +ssh -C gitlab.example.com sudo -u gitlab-psql /opt/gitlab/embedded/bin/pg_dump -Cc gitlabhq_production |\ + ssh -C staging-vm sudo -u gitlab-psql /opt/gitlab/embedded/bin/psql -d template1 +``` + +## Recreating directory structure + +If you need to re-create some directory structure on the staging server you can +use this procedure. + +First, on the production server, create a list of directories you want to +re-create. + +``` +# On PRODUCTION +(umask 077; sudo find /var/opt/gitlab/git-data/repositories -maxdepth 1 -type d -print0 > directories.txt) +``` + +Copy `directories.txt` to the staging server and create the directories there. + +``` +# On STAGING +sudo -u git xargs -0 mkdir -p < directories.txt +``` From b8571f141ec8e746d5e74c1a66434b4166db3c64 Mon Sep 17 00:00:00 2001 From: Ben Boeckel Date: Fri, 1 May 2015 11:43:25 -0400 Subject: [PATCH 029/255] email: fix typo --- app/views/notify/new_issue_email.text.erb | 4 ++-- app/views/notify/new_merge_request_email.text.erb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb index 0cc6293549..fc64c98038 100644 --- a/app/views/notify/new_issue_email.text.erb +++ b/app/views/notify/new_issue_email.text.erb @@ -1,5 +1,5 @@ New Issue was created. Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %> -Author: <%= @issue.author_name %> -Asignee: <%= @issue.assignee_name %> +Author: <%= @issue.author_name %> +Assignee: <%= @issue.assignee_name %> diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb index f08039ad04..bdcca6e4ab 100644 --- a/app/views/notify/new_merge_request_email.text.erb +++ b/app/views/notify/new_merge_request_email.text.erb @@ -3,6 +3,6 @@ New Merge Request #<%= @merge_request.iid %> <%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %> <%= merge_path_description(@merge_request, 'to') %> -Author: <%= @merge_request.author_name %> -Asignee: <%= @merge_request.assignee_name %> +Author: <%= @merge_request.author_name %> +Assignee: <%= @merge_request.assignee_name %> From dc348baf18240dc05e209ec781daada2cbcfe16f Mon Sep 17 00:00:00 2001 From: Jakub Jirutka Date: Wed, 13 May 2015 00:54:13 +0200 Subject: [PATCH 030/255] Update asciidoctor gem to the latest version --- Gemfile | 2 +- Gemfile.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index 84007d4a77..f7da36be94 100644 --- a/Gemfile +++ b/Gemfile @@ -102,7 +102,7 @@ gem 'rdoc', '~>3.6' gem 'org-ruby', '= 0.9.12' gem 'creole', '~>0.3.6' gem 'wikicloth', '=0.8.1' -gem 'asciidoctor', '= 0.1.4' +gem 'asciidoctor', '~> 1.5.2' # Diffs gem 'diffy', '~> 3.0.3' diff --git a/Gemfile.lock b/Gemfile.lock index 571ab27ea7..b6cf03b0fd 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -42,7 +42,7 @@ GEM arel (5.0.1.20140414130214) asana (0.0.6) activeresource (>= 3.2.3) - asciidoctor (0.1.4) + asciidoctor (1.5.2) ast (2.0.0) astrolabe (1.3.0) parser (>= 2.2.0.pre.3, < 3.0) @@ -683,7 +683,7 @@ DEPENDENCIES addressable annotate (~> 2.6.0.beta2) asana (~> 0.0.6) - asciidoctor (= 0.1.4) + asciidoctor (~> 1.5.2) attr_encrypted (= 1.3.4) awesome_print better_errors From 8dbc4746fe7c723b67f3c90cbf40fd7bf6c29cb7 Mon Sep 17 00:00:00 2001 From: Jakub Jirutka Date: Wed, 13 May 2015 01:07:48 +0200 Subject: [PATCH 031/255] Handle AsciiDoc better, reuse HTML pipeline filters (fixes #9263) --- app/helpers/application_helper.rb | 12 +++- app/helpers/gitlab_markdown_helper.rb | 15 ++++- app/helpers/tree_helper.rb | 2 + lib/gitlab/asciidoc.rb | 60 +++++++++++++++++++ lib/gitlab/markdown_helper.rb | 11 +++- spec/helpers/application_helper_spec.rb | 9 ++- spec/helpers/gitlab_markdown_helper_spec.rb | 8 +++ spec/lib/gitlab/asciidoc_spec.rb | 59 ++++++++++++++++++ .../lib/gitlab/gitlab_markdown_helper_spec.rb | 14 ++++- 9 files changed, 184 insertions(+), 6 deletions(-) create mode 100644 lib/gitlab/asciidoc.rb create mode 100644 spec/lib/gitlab/asciidoc_spec.rb diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index ea9722b9be..bc07c09cd4 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -222,8 +222,12 @@ module ApplicationHelper end def render_markup(file_name, file_content) - GitHub::Markup.render(file_name, file_content). - force_encoding(file_content.encoding).html_safe + if asciidoc?(file_name) + asciidoc(file_content) + else + GitHub::Markup.render(file_name, file_content). + force_encoding(file_content.encoding).html_safe + end rescue RuntimeError simple_format(file_content) end @@ -236,6 +240,10 @@ module ApplicationHelper Gitlab::MarkdownHelper.gitlab_markdown?(filename) end + def asciidoc?(filename) + Gitlab::MarkdownHelper.asciidoc?(filename) + end + # Overrides ActionView::Helpers::UrlHelper#link_to to add `rel="nofollow"` to # external links def link_to(name = nil, options = nil, html_options = {}) diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 846aded4bd..7bcc011fd5 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -56,6 +56,16 @@ module GitlabMarkdownHelper @markdown.render(text).html_safe end + def asciidoc(text) + Gitlab::Asciidoc.render(text, { + commit: @commit, + project: @project, + project_wiki: @project_wiki, + requested_path: @path, + ref: @ref + }) + end + # Return the first line of +text+, up to +max_chars+, after parsing the line # as Markdown. HTML tags in the parsed output are not counted toward the # +max_chars+ limit. If the length limit falls within a tag's contents, then @@ -67,8 +77,11 @@ module GitlabMarkdownHelper end def render_wiki_content(wiki_page) - if wiki_page.format == :markdown + case wiki_page.format + when :markdown markdown(wiki_page.content) + when :asciidoc + asciidoc(wiki_page.content) else wiki_page.formatted_content.html_safe end diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index 6dd9b6f017..c03564a71a 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -27,6 +27,8 @@ module TreeHelper def render_readme(readme) if gitlab_markdown?(readme.name) preserve(markdown(readme.data)) + elsif asciidoc?(readme.name) + asciidoc(readme.data) elsif markup?(readme.name) render_markup(readme.name, readme.data) else diff --git a/lib/gitlab/asciidoc.rb b/lib/gitlab/asciidoc.rb new file mode 100644 index 0000000000..bf33e5b1b1 --- /dev/null +++ b/lib/gitlab/asciidoc.rb @@ -0,0 +1,60 @@ +require 'asciidoctor' +require 'html/pipeline' + +module Gitlab + # Parser/renderer for the AsciiDoc format that uses Asciidoctor and filters + # the resulting HTML through HTML pipeline filters. + module Asciidoc + + # Provide autoload paths for filters to prevent a circular dependency error + autoload :RelativeLinkFilter, 'gitlab/markdown/relative_link_filter' + + DEFAULT_ADOC_ATTRS = [ + 'showtitle', 'idprefix=user-content-', 'idseparator=-', 'env=gitlab', + 'env-gitlab', 'source-highlighter=html-pipeline' + ].freeze + + # Public: Converts the provided Asciidoc markup into HTML. + # + # input - the source text in Asciidoc format + # context - a Hash with the template context: + # :commit + # :project + # :project_wiki + # :requested_path + # :ref + # asciidoc_opts - a Hash of options to pass to the Asciidoctor converter + # html_opts - a Hash of options for HTML output: + # :xhtml - output XHTML instead of HTML + # + def self.render(input, context, asciidoc_opts = {}, html_opts = {}) + asciidoc_opts = asciidoc_opts.reverse_merge( + safe: :secure, + backend: html_opts[:xhtml] ? :xhtml5 : :html5, + attributes: [] + ) + asciidoc_opts[:attributes].unshift(*DEFAULT_ADOC_ATTRS) + + html = ::Asciidoctor.convert(input, asciidoc_opts) + + if context[:project] + result = HTML::Pipeline.new(filters).call(html, context) + + save_opts = html_opts[:xhtml] ? + Nokogiri::XML::Node::SaveOptions::AS_XHTML : 0 + + html = result[:output].to_html(save_with: save_opts) + end + + html.html_safe + end + + private + + def self.filters + [ + Gitlab::Markdown::RelativeLinkFilter + ] + end + end +end diff --git a/lib/gitlab/markdown_helper.rb b/lib/gitlab/markdown_helper.rb index 5e3cfc0585..70384b1db2 100644 --- a/lib/gitlab/markdown_helper.rb +++ b/lib/gitlab/markdown_helper.rb @@ -9,7 +9,7 @@ module Gitlab # Returns boolean def markup?(filename) filename.downcase.end_with?(*%w(.textile .rdoc .org .creole .wiki - .mediawiki .rst .adoc .asciidoc .asc)) + .mediawiki .rst .adoc .ad .asciidoc)) end # Public: Determines if a given filename is compatible with @@ -22,6 +22,15 @@ module Gitlab filename.downcase.end_with?(*%w(.mdown .md .markdown)) end + # Public: Determines if the given filename has AsciiDoc extension. + # + # filename - Filename string to check + # + # Returns boolean + def asciidoc?(filename) + filename.downcase.end_with?(*%w(.adoc .ad .asciidoc)) + end + def previewable?(filename) gitlab_markdown?(filename) || markup?(filename) end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index d4cf654008..59870dfb19 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -261,12 +261,19 @@ describe ApplicationHelper do end end - describe 'markup_render' do + describe 'render_markup' do let(:content) { 'Noël' } it 'should preserve encoding' do expect(content.encoding.name).to eq('UTF-8') expect(render_markup('foo.rst', content).encoding.name).to eq('UTF-8') end + + it "should delegate to #asciidoc when file name corresponds to AsciiDoc" do + expect(self).to receive(:asciidoc?).with('foo.adoc').and_return(true) + expect(self).to receive(:asciidoc).and_return('NOEL') + + expect(render_markup('foo.adoc', content)).to eq('NOEL') + end end end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index 9f3e8cf585..0d0418f84a 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -110,6 +110,14 @@ describe GitlabMarkdownHelper do helper.render_wiki_content(@wiki) end + it "should use Asciidoctor for asciidoc files" do + allow(@wiki).to receive(:format).and_return(:asciidoc) + + expect(helper).to receive(:asciidoc).with('wiki content') + + helper.render_wiki_content(@wiki) + end + it "should use the Gollum renderer for all other file types" do allow(@wiki).to receive(:format).and_return(:rdoc) formatted_content_stub = double('formatted_content') diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb new file mode 100644 index 0000000000..23f83339ec --- /dev/null +++ b/spec/lib/gitlab/asciidoc_spec.rb @@ -0,0 +1,59 @@ +require 'spec_helper' +require 'nokogiri' + +module Gitlab + describe Asciidoc do + + let(:input) { 'ascii' } + let(:context) { {} } + let(:html) { 'H2O' } + + context "without project" do + + it "should convert the input using Asciidoctor and default options" do + expected_asciidoc_opts = { safe: :secure, backend: :html5, + attributes: described_class::DEFAULT_ADOC_ATTRS } + + expect(Asciidoctor).to receive(:convert) + .with(input, expected_asciidoc_opts).and_return(html) + + expect( render(input, context) ).to eql html + end + + context "with asciidoc_opts" do + + let(:asciidoc_opts) { {safe: :safe, attributes: ['foo']} } + + it "should merge the options with default ones" do + expected_asciidoc_opts = { safe: :safe, backend: :html5, + attributes: described_class::DEFAULT_ADOC_ATTRS + ['foo'] } + + expect(Asciidoctor).to receive(:convert) + .with(input, expected_asciidoc_opts).and_return(html) + + render(input, context, asciidoc_opts) + end + end + end + + context "with project in context" do + + let(:context) { {project: create(:project)} } + + it "should filter converted input via HTML pipeline and return result" do + filtered_html = 'ASCII' + + allow(Asciidoctor).to receive(:convert).and_return(html) + expect_any_instance_of(HTML::Pipeline).to receive(:call) + .with(html, context) + .and_return(output: Nokogiri::HTML.fragment(filtered_html)) + + expect( render('foo', context) ).to eql filtered_html + end + end + + def render(*args) + described_class.render(*args) + end + end +end diff --git a/spec/lib/gitlab/gitlab_markdown_helper_spec.rb b/spec/lib/gitlab/gitlab_markdown_helper_spec.rb index ab613193f4..beaafd5635 100644 --- a/spec/lib/gitlab/gitlab_markdown_helper_spec.rb +++ b/spec/lib/gitlab/gitlab_markdown_helper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::MarkdownHelper do describe '#markup?' do %w(textile rdoc org creole wiki - mediawiki rst adoc asciidoc asc).each do |type| + mediawiki rst adoc ad asciidoc).each do |type| it "returns true for #{type} files" do expect(Gitlab::MarkdownHelper.markup?("README.#{type}")).to be_truthy end @@ -25,4 +25,16 @@ describe Gitlab::MarkdownHelper do expect(Gitlab::MarkdownHelper.gitlab_markdown?('README.rb')).not_to be_truthy end end + + describe '#asciidoc?' do + %w(adoc ad asciidoc ADOC).each do |type| + it "returns true for #{type} files" do + expect(Gitlab::MarkdownHelper.asciidoc?("README.#{type}")).to be_truthy + end + end + + it 'returns false when given a non-asciidoc filename' do + expect(Gitlab::MarkdownHelper.asciidoc?('README.rb')).not_to be_truthy + end + end end From 92133a89a42c219478b0cb8496d0efa377bd60b3 Mon Sep 17 00:00:00 2001 From: Jakub Jirutka Date: Thu, 14 May 2015 20:00:16 +0200 Subject: [PATCH 032/255] Add changelog items for #9288 --- CHANGELOG | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 2c36875137..d170e6d27c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -68,6 +68,8 @@ v 7.11.0 (unreleased) - Spin spinner icon next to "Checking for CI status..." on MR page. - Fix reference links in dashboard activity and ATOM feeds. - Ensure that the first added admin performs repository imports + - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) + - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) v 7.10.2 - Fix CI links on MR page From daa0925016a63dcde448643cbf1310aca359cf37 Mon Sep 17 00:00:00 2001 From: Jakub Jirutka Date: Wed, 13 May 2015 01:40:11 +0200 Subject: [PATCH 033/255] Rename MarkdownHelper to MarkupHelper --- app/helpers/application_helper.rb | 6 +++--- app/helpers/blob_helper.rb | 2 +- app/models/tree.rb | 6 +++--- .../{markdown_helper.rb => markup_helper.rb} | 2 +- ...rkdown_helper_spec.rb => markup_helper_spec.rb} | 14 +++++++------- 5 files changed, 15 insertions(+), 15 deletions(-) rename lib/gitlab/{markdown_helper.rb => markup_helper.rb} (97%) rename spec/lib/gitlab/{gitlab_markdown_helper_spec.rb => markup_helper_spec.rb} (59%) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index bc07c09cd4..5bcc002601 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -233,15 +233,15 @@ module ApplicationHelper end def markup?(filename) - Gitlab::MarkdownHelper.markup?(filename) + Gitlab::MarkupHelper.markup?(filename) end def gitlab_markdown?(filename) - Gitlab::MarkdownHelper.gitlab_markdown?(filename) + Gitlab::MarkupHelper.gitlab_markdown?(filename) end def asciidoc?(filename) - Gitlab::MarkdownHelper.asciidoc?(filename) + Gitlab::MarkupHelper.asciidoc?(filename) end # Overrides ActionView::Helpers::UrlHelper#link_to to add `rel="nofollow"` to diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 4ea838ca44..885ac5f85b 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -55,7 +55,7 @@ module BlobHelper end def editing_preview_title(filename) - if Gitlab::MarkdownHelper.previewable?(filename) + if Gitlab::MarkupHelper.previewable?(filename) 'Preview' else 'Preview changes' diff --git a/app/models/tree.rb b/app/models/tree.rb index f279e896cd..93b3246a66 100644 --- a/app/models/tree.rb +++ b/app/models/tree.rb @@ -1,11 +1,11 @@ class Tree - include Gitlab::MarkdownHelper + include Gitlab::MarkupHelper attr_accessor :repository, :sha, :path, :entries def initialize(repository, sha, path = '/') path = '/' if path.blank? - + @repository = repository @sha = sha @path = path @@ -20,7 +20,7 @@ class Tree available_readmes = blobs.select(&:readme?) if available_readmes.count == 0 - return @readme = nil + return @readme = nil end # Take the first previewable readme, or the first available readme, if we diff --git a/lib/gitlab/markdown_helper.rb b/lib/gitlab/markup_helper.rb similarity index 97% rename from lib/gitlab/markdown_helper.rb rename to lib/gitlab/markup_helper.rb index 70384b1db2..fb037266d2 100644 --- a/lib/gitlab/markdown_helper.rb +++ b/lib/gitlab/markup_helper.rb @@ -1,5 +1,5 @@ module Gitlab - module MarkdownHelper + module MarkupHelper module_function # Public: Determines if a given filename is compatible with GitHub::Markup. diff --git a/spec/lib/gitlab/gitlab_markdown_helper_spec.rb b/spec/lib/gitlab/markup_helper_spec.rb similarity index 59% rename from spec/lib/gitlab/gitlab_markdown_helper_spec.rb rename to spec/lib/gitlab/markup_helper_spec.rb index beaafd5635..448beecf01 100644 --- a/spec/lib/gitlab/gitlab_markdown_helper_spec.rb +++ b/spec/lib/gitlab/markup_helper_spec.rb @@ -1,40 +1,40 @@ require 'spec_helper' -describe Gitlab::MarkdownHelper do +describe Gitlab::MarkupHelper do describe '#markup?' do %w(textile rdoc org creole wiki mediawiki rst adoc ad asciidoc).each do |type| it "returns true for #{type} files" do - expect(Gitlab::MarkdownHelper.markup?("README.#{type}")).to be_truthy + expect(Gitlab::MarkupHelper.markup?("README.#{type}")).to be_truthy end end it 'returns false when given a non-markup filename' do - expect(Gitlab::MarkdownHelper.markup?('README.rb')).not_to be_truthy + expect(Gitlab::MarkupHelper.markup?('README.rb')).not_to be_truthy end end describe '#gitlab_markdown?' do %w(mdown md markdown).each do |type| it "returns true for #{type} files" do - expect(Gitlab::MarkdownHelper.gitlab_markdown?("README.#{type}")).to be_truthy + expect(Gitlab::MarkupHelper.gitlab_markdown?("README.#{type}")).to be_truthy end end it 'returns false when given a non-markdown filename' do - expect(Gitlab::MarkdownHelper.gitlab_markdown?('README.rb')).not_to be_truthy + expect(Gitlab::MarkupHelper.gitlab_markdown?('README.rb')).not_to be_truthy end end describe '#asciidoc?' do %w(adoc ad asciidoc ADOC).each do |type| it "returns true for #{type} files" do - expect(Gitlab::MarkdownHelper.asciidoc?("README.#{type}")).to be_truthy + expect(Gitlab::MarkupHelper.asciidoc?("README.#{type}")).to be_truthy end end it 'returns false when given a non-asciidoc filename' do - expect(Gitlab::MarkdownHelper.asciidoc?('README.rb')).not_to be_truthy + expect(Gitlab::MarkupHelper.asciidoc?('README.rb')).not_to be_truthy end end end From b0659c1b072267e0e1fa3066ca1a8cc17bc8f6c0 Mon Sep 17 00:00:00 2001 From: Jakub Jirutka Date: Wed, 13 May 2015 01:54:13 +0200 Subject: [PATCH 034/255] Simplify and unify helpers for rendering markup --- app/helpers/application_helper.rb | 4 +++- app/helpers/tree_helper.rb | 10 +--------- app/views/projects/blob/_text.html.haml | 6 +----- app/views/search/results/_snippet_blob.html.haml | 11 +---------- app/views/shared/snippets/_blob.html.haml | 6 +----- lib/gitlab/markup_helper.rb | 8 +++++--- spec/helpers/application_helper_spec.rb | 7 +++++++ spec/lib/gitlab/markup_helper_spec.rb | 2 +- 8 files changed, 20 insertions(+), 34 deletions(-) diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 5bcc002601..bcd400b7e7 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -222,7 +222,9 @@ module ApplicationHelper end def render_markup(file_name, file_content) - if asciidoc?(file_name) + if gitlab_markdown?(file_name) + Haml::Helpers.preserve(markdown(file_content)) + elsif asciidoc?(file_name) asciidoc(file_content) else GitHub::Markup.render(file_name, file_content). diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index c03564a71a..03a49e119b 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -25,15 +25,7 @@ module TreeHelper end def render_readme(readme) - if gitlab_markdown?(readme.name) - preserve(markdown(readme.data)) - elsif asciidoc?(readme.name) - asciidoc(readme.data) - elsif markup?(readme.name) - render_markup(readme.name, readme.data) - else - simple_format(readme.data) - end + render_markup(readme.name, readme.data) end # Return an image icon depending on the file type and mode diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml index f6bd62f239..4429c395ae 100644 --- a/app/views/projects/blob/_text.html.haml +++ b/app/views/projects/blob/_text.html.haml @@ -1,8 +1,4 @@ -- if gitlab_markdown?(blob.name) - .file-content.wiki - = preserve do - = markdown(blob.data) -- elsif markup?(blob.name) +- if markup?(blob.name) .file-content.wiki = render_markup(blob.name, blob.data) - else diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml index 8af393777f..9509985391 100644 --- a/app/views/search/results/_snippet_blob.html.haml +++ b/app/views/search/results/_snippet_blob.html.haml @@ -13,16 +13,7 @@ .file-title %i.fa.fa-file %strong= snippet_blob[:snippet_object].file_name - - if gitlab_markdown?(snippet_blob[:snippet_object].file_name) - .file-content.wiki - - snippet_blob[:snippet_chunks].each do |snippet| - - unless snippet[:data].empty? - = preserve do - = markdown(snippet[:data]) - - else - .file-content.code - .nothing-here-block Empty file - - elsif markup?(snippet_blob[:snippet_object].file_name) + - if markup?(snippet_blob[:snippet_object].file_name) .file-content.wiki - snippet_blob[:snippet_chunks].each do |snippet| - unless snippet[:data].empty? diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml index 30458793fd..d26a99bb14 100644 --- a/app/views/shared/snippets/_blob.html.haml +++ b/app/views/shared/snippets/_blob.html.haml @@ -1,9 +1,5 @@ - unless @snippet.content.empty? - - if gitlab_markdown?(@snippet.file_name) - .file-content.wiki - = preserve do - = markdown(@snippet.data) - - elsif markup?(@snippet.file_name) + - if markup?(@snippet.file_name) .file-content.wiki = render_markup(@snippet.file_name, @snippet.data) - else diff --git a/lib/gitlab/markup_helper.rb b/lib/gitlab/markup_helper.rb index fb037266d2..f99be969d3 100644 --- a/lib/gitlab/markup_helper.rb +++ b/lib/gitlab/markup_helper.rb @@ -8,8 +8,10 @@ module Gitlab # # Returns boolean def markup?(filename) - filename.downcase.end_with?(*%w(.textile .rdoc .org .creole .wiki - .mediawiki .rst .adoc .ad .asciidoc)) + gitlab_markdown?(filename) || + asciidoc?(filename) || + filename.downcase.end_with?(*%w(.textile .rdoc .org .creole .wiki + .mediawiki .rst)) end # Public: Determines if a given filename is compatible with @@ -32,7 +34,7 @@ module Gitlab end def previewable?(filename) - gitlab_markdown?(filename) || markup?(filename) + markup?(filename) end end end diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 59870dfb19..3307ac776f 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -269,6 +269,13 @@ describe ApplicationHelper do expect(render_markup('foo.rst', content).encoding.name).to eq('UTF-8') end + it "should delegate to #markdown when file name corresponds to Markdown" do + expect(self).to receive(:gitlab_markdown?).with('foo.md').and_return(true) + expect(self).to receive(:markdown).and_return('NOEL') + + expect(render_markup('foo.md', content)).to eq('NOEL') + end + it "should delegate to #asciidoc when file name corresponds to AsciiDoc" do expect(self).to receive(:asciidoc?).with('foo.adoc').and_return(true) expect(self).to receive(:asciidoc).and_return('NOEL') diff --git a/spec/lib/gitlab/markup_helper_spec.rb b/spec/lib/gitlab/markup_helper_spec.rb index 448beecf01..7e716e866b 100644 --- a/spec/lib/gitlab/markup_helper_spec.rb +++ b/spec/lib/gitlab/markup_helper_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::MarkupHelper do describe '#markup?' do %w(textile rdoc org creole wiki - mediawiki rst adoc ad asciidoc).each do |type| + mediawiki rst adoc ad asciidoc mdown md markdown).each do |type| it "returns true for #{type} files" do expect(Gitlab::MarkupHelper.markup?("README.#{type}")).to be_truthy end From 37e94e3c5bfde0dc6d74decdd52db55e8a0c905e Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Tue, 19 May 2015 14:38:45 +0200 Subject: [PATCH 035/255] Move the changelog item to 7.12 --- CHANGELOG | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 85562497b3..fdb37bcc3a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,8 @@ Please view this file on the master branch, on stable branches it's out of date. +v 7.12.0 (unreleased) +- Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) + v 7.11.0 (unreleased) - Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu) - Don't show duplicate deploy keys @@ -70,7 +73,6 @@ v 7.11.0 (unreleased) - Ensure that the first added admin performs repository imports - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) - - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) v 7.10.2 - Fix CI links on MR page From 15a6211e81a3ece82a93ba387298ac1813615eb9 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 19 May 2015 21:15:41 +0300 Subject: [PATCH 036/255] Small css improvements --- app/assets/stylesheets/generic/header.scss | 3 ++- app/assets/stylesheets/pages/projects.scss | 4 ++++ app/views/projects/_aside.html.haml | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss index fcd62373bf..362b217a44 100644 --- a/app/assets/stylesheets/generic/header.scss +++ b/app/assets/stylesheets/generic/header.scss @@ -188,7 +188,7 @@ header { border: 1px solid #DDD; box-shadow: none; @include transition(all 0.15s ease-in 0s); - background-color: #f5f5f5; + background-color: #f9f9f9; } } } @@ -197,6 +197,7 @@ header { width: 300px; &:focus { width: 330px; + background-color: #FFF; } } diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 16b9814a0f..83771480cb 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -230,6 +230,10 @@ ul.nav.nav-projects-tabs { margin: 10px 0; } } + + .ci-status-image { + max-height: 22px; + } } .transfer-project .select2-container { diff --git a/app/views/projects/_aside.html.haml b/app/views/projects/_aside.html.haml index 333a1e6156..e90c7b26dd 100644 --- a/app/views/projects/_aside.html.haml +++ b/app/views/projects/_aside.html.haml @@ -57,7 +57,7 @@ .pull-right - if ci_service.respond_to?(:status_img_path) = link_to ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' do - = image_tag ci_service.status_img_path, alt: "build status" + = image_tag ci_service.status_img_path, alt: "build status", class: 'ci-status-image' - else = link_to 'view builds', ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' From a393d6b163da8ccd7e88f68074292809f94a02d3 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Wed, 20 May 2015 14:00:45 +0200 Subject: [PATCH 037/255] Remove italics and margin from merge request form help. --- app/views/projects/_issuable_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml index f3464e5eb1..5a19e980b6 100644 --- a/app/views/projects/_issuable_form.html.haml +++ b/app/views/projects/_issuable_form.html.haml @@ -13,7 +13,7 @@ class: 'form-control pad js-gfm-input', required: true - if issuable.is_a?(MergeRequest) - %p.help-block.hint.col-sm-12 + %p.help-block - if issuable.work_in_progress? This merge request is marked a Work In Progress. When it's ready, remove the WIP prefix from the title to allow it to be accepted. From dfce7b62f7e126a0891f81abc0bda20cfb7a58b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hannes=20Rosen=C3=B6gger?= <123haynes@gmail.com> Date: Wed, 20 May 2015 15:46:40 +0200 Subject: [PATCH 038/255] workaround for buggy lexers if something goes wrong, fall back to the plaintext lexer --- CHANGELOG | 1 + app/helpers/blob_helper.rb | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index fdb37bcc3a..3c8b839c21 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 7.12.0 (unreleased) - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) v 7.11.0 (unreleased) + - Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger) - Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu) - Don't show duplicate deploy keys - Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger) diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 885ac5f85b..9fe5f82f02 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -9,11 +9,13 @@ module BlobHelper begin lexer = Rugments::Lexer.guess(filename: blob_name, source: blob_content) - rescue Rugments::Lexer::AmbiguousGuess + result = formatter.format(lexer.lex(blob_content)).html_safe + rescue lexer = Rugments::Lexers::PlainText + result = formatter.format(lexer.lex(blob_content)).html_safe end - formatter.format(lexer.lex(blob_content)).html_safe + result end def no_highlight_files From 8ee382087d06c50d2f8c7f60ce79294af0b89201 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 18 May 2015 15:44:45 -0400 Subject: [PATCH 039/255] Subclass TaskList::Filter to fix a bug Instead of using a fork, we subclass the filter and only apply the `task-list` class to list items that actually are task lists. Closes #1645 See https://github.com/github/task_list/pull/60 --- Gemfile | 2 +- Gemfile.lock | 6 ++--- app/assets/stylesheets/pages/notes.scss | 4 ++-- lib/gitlab/markdown.rb | 4 ++-- lib/gitlab/markdown/task_list_filter.rb | 23 +++++++++++++++++++ .../gitlab/markdown/task_list_filter_spec.rb | 14 +++++++++++ 6 files changed, 45 insertions(+), 8 deletions(-) create mode 100644 lib/gitlab/markdown/task_list_filter.rb create mode 100644 spec/lib/gitlab/markdown/task_list_filter_spec.rb diff --git a/Gemfile b/Gemfile index f7da36be94..59adf6d2d6 100644 --- a/Gemfile +++ b/Gemfile @@ -94,7 +94,7 @@ gem "seed-fu" # Markdown and HTML processing gem 'html-pipeline', '~> 1.11.0' -gem 'task_list', '~> 1.0.0', require: 'task_list/railtie' +gem 'task_list', '1.0.2', require: 'task_list/railtie' gem 'github-markup' gem 'redcarpet', '~> 3.2.3' gem 'RedCloth' diff --git a/Gemfile.lock b/Gemfile.lock index b6cf03b0fd..9ea77151c7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -338,7 +338,7 @@ GEM method_source (0.8.2) mime-types (1.25.1) mimemagic (0.3.0) - mini_portile (0.6.1) + mini_portile (0.6.2) minitest (5.3.5) mousetrap-rails (1.4.6) multi_json (1.10.1) @@ -350,7 +350,7 @@ GEM net-ssh (>= 2.6.5) net-ssh (2.8.0) newrelic_rpm (3.9.4.245) - nokogiri (1.6.5) + nokogiri (1.6.6.2) mini_portile (~> 0.6.0) nprogress-rails (0.1.2.3) oauth (0.4.7) @@ -802,7 +802,7 @@ DEPENDENCIES spring-commands-spinach (= 1.0.0) stamp state_machine - task_list (~> 1.0.0) + task_list (= 1.0.2) test_after_commit thin tinder (~> 1.9.2) diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index e943be67db..42b8ecabb3 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -79,11 +79,11 @@ ul.notes { word-wrap: break-word; @include md-typography; - // Reduce left padding of first ul element + // Reduce left padding of first task list ul element ul.task-list:first-child { padding-left: 10px; - // sub-lists should be padded normally + // sub-tasks should be padded normally ul { padding-left: 20px; } diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 133010adca..c0fb22e7f3 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -1,5 +1,4 @@ require 'html/pipeline' -require 'task_list/filter' module Gitlab # Custom parser for GitLab-flavored Markdown @@ -19,6 +18,7 @@ module Gitlab autoload :SanitizationFilter, 'gitlab/markdown/sanitization_filter' autoload :SnippetReferenceFilter, 'gitlab/markdown/snippet_reference_filter' autoload :TableOfContentsFilter, 'gitlab/markdown/table_of_contents_filter' + autoload :TaskListFilter, 'gitlab/markdown/task_list_filter' autoload :UserReferenceFilter, 'gitlab/markdown/user_reference_filter' # Public: Parse the provided text with GitLab-Flavored Markdown @@ -113,7 +113,7 @@ module Gitlab Gitlab::Markdown::CommitReferenceFilter, Gitlab::Markdown::LabelReferenceFilter, - TaskList::Filter + Gitlab::Markdown::TaskListFilter ] end end diff --git a/lib/gitlab/markdown/task_list_filter.rb b/lib/gitlab/markdown/task_list_filter.rb new file mode 100644 index 0000000000..c6eb2e2bf6 --- /dev/null +++ b/lib/gitlab/markdown/task_list_filter.rb @@ -0,0 +1,23 @@ +require 'task_list/filter' + +module Gitlab + module Markdown + # Work around a bug in the default TaskList::Filter that adds a `task-list` + # class to every list element, regardless of whether or not it contains a + # task list. + # + # This is a (hopefully) temporary fix, pending a new release of the + # task_list gem. + # + # See https://github.com/github/task_list/pull/60 + class TaskListFilter < TaskList::Filter + def add_css_class(node, *new_class_names) + if new_class_names.include?('task-list') + super if node.children.any? { |c| c['class'] == 'task-list-item' } + else + super + end + end + end + end +end diff --git a/spec/lib/gitlab/markdown/task_list_filter_spec.rb b/spec/lib/gitlab/markdown/task_list_filter_spec.rb new file mode 100644 index 0000000000..2a1e1cc512 --- /dev/null +++ b/spec/lib/gitlab/markdown/task_list_filter_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +module Gitlab::Markdown + describe TaskListFilter do + def filter(html, options = {}) + described_class.call(html, options) + end + + it 'does not apply `task-list` class to non-task lists' do + exp = act = %(
  • Item
) + expect(filter(act).to_html).to eq exp + end + end +end From 8e40d594a2e53585c2ed06a2c2616fcd24c0eae8 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 20 May 2015 12:53:35 -0400 Subject: [PATCH 040/255] Work around a Chrome 43 bug preventing note editing --- app/assets/javascripts/notes.js.coffee | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index c25b1ddb06..b87659d622 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -312,6 +312,13 @@ class @Notes form.show() textarea = form.find("textarea") textarea.focus() + + # HACK (rspeicher): Work around a Chrome 43 bug(?). + # The textarea has the correct value, Chrome just won't show it unless we + # modify it, so let's add a newline! + textarea.val (_, value) -> + "#{value}\n" + disableButtonIfEmptyField textarea, form.find(".js-comment-button") ### From 3601ebda79b84c3ea4b21f0e2b91003f206c342a Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 20 May 2015 18:39:33 -0400 Subject: [PATCH 041/255] Update spring, re-run binstubs --- Gemfile.lock | 2 +- bin/rails | 5 ++--- bin/rake | 5 ++--- bin/spring | 11 ++++------- 4 files changed, 9 insertions(+), 14 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 9ea77151c7..340b9f6e3c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -589,7 +589,7 @@ GEM capybara (>= 2.0.0) railties (>= 3) spinach (>= 0.4) - spring (1.3.3) + spring (1.3.6) spring-commands-rspec (1.0.4) spring (>= 0.9.1) spring-commands-spinach (1.0.0) diff --git a/bin/rails b/bin/rails index 7feb6a30e6..215a2ea01f 100755 --- a/bin/rails +++ b/bin/rails @@ -3,6 +3,5 @@ begin load File.expand_path("../spring", __FILE__) rescue LoadError end -APP_PATH = File.expand_path('../../config/application', __FILE__) -require_relative '../config/boot' -require 'rails/commands' +require 'bundler/setup' +load Gem.bin_path('rails', 'rails') diff --git a/bin/rake b/bin/rake index 8017a0271d..0fb4e07e13 100755 --- a/bin/rake +++ b/bin/rake @@ -3,6 +3,5 @@ begin load File.expand_path("../spring", __FILE__) rescue LoadError end -require_relative '../config/boot' -require 'rake' -Rake.application.run +require 'bundler/setup' +load Gem.bin_path('rake', 'rake') diff --git a/bin/spring b/bin/spring index 253ec37c34..7b45d374fc 100755 --- a/bin/spring +++ b/bin/spring @@ -1,17 +1,14 @@ #!/usr/bin/env ruby -# This file loads spring without using Bundler, in order to be fast -# It gets overwritten when you run the `spring binstub` command +# This file loads spring without using Bundler, in order to be fast. +# It gets overwritten when you run the `spring binstub` command. unless defined?(Spring) require "rubygems" require "bundler" - if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ spring \((.*?)\)$.*?^$/m) - ENV["GEM_PATH"] = ([Bundler.bundle_path.to_s] + Gem.path).join(File::PATH_SEPARATOR) - ENV["GEM_HOME"] = "" - Gem.paths = ENV - + if match = Bundler.default_lockfile.read.match(/^GEM$.*?^ (?: )*spring \((.*?)\)$.*?^$/m) + Gem.paths = { "GEM_PATH" => [Bundler.bundle_path.to_s, *Gem.path].uniq } gem "spring", match[1] require "spring/binstub" end From 9789b56a319a885e1086f3475814477946ccb548 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 20 May 2015 19:22:00 -0400 Subject: [PATCH 042/255] Update ffaker gem Adds a version requirement which it didn't have before, at 2.0.0. This version has breaking API changes in that the namespace is now `FFaker` instead of `Faker`. --- Gemfile | 2 +- Gemfile.lock | 10 +++++----- db/fixtures/development/04_project.rb | 4 ++-- db/fixtures/development/05_users.rb | 6 +++--- db/fixtures/development/07_milestones.rb | 2 +- db/fixtures/development/09_issues.rb | 4 ++-- db/fixtures/development/10_merge_requests.rb | 4 ++-- db/fixtures/development/12_snippets.rb | 4 ++-- db/fixtures/development/13_comments.rb | 4 ++-- features/steps/project/hooks.rb | 2 +- spec/factories.rb | 16 ++++++++-------- spec/features/admin/admin_hooks_spec.rb | 2 +- spec/mailers/notify_spec.rb | 4 ++-- spec/requests/api/projects_spec.rb | 4 ++-- 14 files changed, 34 insertions(+), 34 deletions(-) diff --git a/Gemfile b/Gemfile index 59adf6d2d6..276efd0db0 100644 --- a/Gemfile +++ b/Gemfile @@ -239,7 +239,7 @@ group :development, :test do gem 'minitest', '~> 5.3.0' # Generate Fake data - gem "ffaker" + gem 'ffaker', '~> 2.0.0' # Guard gem 'guard-rspec' diff --git a/Gemfile.lock b/Gemfile.lock index 340b9f6e3c..a384ccb1d6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -176,7 +176,7 @@ GEM faraday_middleware (0.9.0) faraday (>= 0.7.4, < 0.9) fastercsv (1.5.5) - ffaker (1.22.1) + ffaker (2.0.0) ffi (1.9.8) fog (1.21.0) fog-brightbox @@ -547,9 +547,9 @@ GEM sdoc (0.3.20) json (>= 1.1.3) rdoc (~> 3.10) - seed-fu (2.3.1) - activerecord (>= 3.1, < 4.2) - activesupport (>= 3.1, < 4.2) + seed-fu (2.3.5) + activerecord (>= 3.1, < 4.3) + activesupport (>= 3.1, < 4.3) select2-rails (3.5.2) thor (~> 0.14) settingslogic (2.0.9) @@ -713,7 +713,7 @@ DEPENDENCIES email_spec enumerize factory_girl_rails - ffaker + ffaker (~> 2.0.0) fog (~> 1.14) font-awesome-rails (~> 4.2) foreman diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb index ae4c0550a4..8783977092 100644 --- a/db/fixtures/development/04_project.rb +++ b/db/fixtures/development/04_project.rb @@ -23,7 +23,7 @@ Sidekiq::Testing.inline! do name: group_path.titleize, path: group_path ) - group.description = Faker::Lorem.sentence + group.description = FFaker::Lorem.sentence group.save group.add_owner(User.first) @@ -35,7 +35,7 @@ Sidekiq::Testing.inline! do import_url: url, namespace_id: group.id, name: project_path.titleize, - description: Faker::Lorem.sentence, + description: FFaker::Lorem.sentence, visibility_level: Gitlab::VisibilityLevel.values.sample } diff --git a/db/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb index 24952a1f66..378354efd5 100644 --- a/db/fixtures/development/05_users.rb +++ b/db/fixtures/development/05_users.rb @@ -2,9 +2,9 @@ Gitlab::Seeder.quiet do (2..20).each do |i| begin User.create!( - username: Faker::Internet.user_name, - name: Faker::Name.name, - email: Faker::Internet.email, + username: FFaker::Internet.user_name, + name: FFaker::Name.name, + email: FFaker::Internet.email, confirmed_at: DateTime.now, password: '12345678' ) diff --git a/db/fixtures/development/07_milestones.rb b/db/fixtures/development/07_milestones.rb index 2296821e52..a43116829d 100644 --- a/db/fixtures/development/07_milestones.rb +++ b/db/fixtures/development/07_milestones.rb @@ -3,7 +3,7 @@ Gitlab::Seeder.quiet do (1..5).each do |i| milestone_params = { title: "v#{i}.0", - description: Faker::Lorem.sentence, + description: FFaker::Lorem.sentence, state: ['opened', 'closed'].sample, } diff --git a/db/fixtures/development/09_issues.rb b/db/fixtures/development/09_issues.rb index e8b01b46d2..c636e96381 100644 --- a/db/fixtures/development/09_issues.rb +++ b/db/fixtures/development/09_issues.rb @@ -2,8 +2,8 @@ Gitlab::Seeder.quiet do Project.all.each do |project| (1..10).each do |i| issue_params = { - title: Faker::Lorem.sentence(6), - description: Faker::Lorem.sentence, + title: FFaker::Lorem.sentence(6), + description: FFaker::Lorem.sentence, state: ['opened', 'closed'].sample, milestone: project.milestones.sample, assignee: project.team.users.sample diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb index f9b2fd8b05..0825776ffa 100644 --- a/db/fixtures/development/10_merge_requests.rb +++ b/db/fixtures/development/10_merge_requests.rb @@ -10,8 +10,8 @@ Gitlab::Seeder.quiet do params = { source_branch: source_branch, target_branch: target_branch, - title: Faker::Lorem.sentence(6), - description: Faker::Lorem.sentences(3).join(" "), + title: FFaker::Lorem.sentence(6), + description: FFaker::Lorem.sentences(3).join(" "), milestone: project.milestones.sample, assignee: project.team.users.sample } diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb index b3a6f39c7d..3bd4b442ad 100644 --- a/db/fixtures/development/12_snippets.rb +++ b/db/fixtures/development/12_snippets.rb @@ -28,8 +28,8 @@ eos PersonalSnippet.seed(:id, [{ id: i, author_id: user.id, - title: Faker::Lorem.sentence(3), - file_name: Faker::Internet.domain_word + '.rb', + title: FFaker::Lorem.sentence(3), + file_name: FFaker::Internet.domain_word + '.rb', visibility_level: Gitlab::VisibilityLevel.values.sample, content: content, }]) diff --git a/db/fixtures/development/13_comments.rb b/db/fixtures/development/13_comments.rb index d37be53c7b..566c070563 100644 --- a/db/fixtures/development/13_comments.rb +++ b/db/fixtures/development/13_comments.rb @@ -6,7 +6,7 @@ Gitlab::Seeder.quiet do note_params = { noteable_type: 'Issue', noteable_id: issue.id, - note: Faker::Lorem.sentence, + note: FFaker::Lorem.sentence, } Notes::CreateService.new(project, user, note_params).execute @@ -21,7 +21,7 @@ Gitlab::Seeder.quiet do note_params = { noteable_type: 'MergeRequest', noteable_id: mr.id, - note: Faker::Lorem.sentence, + note: FFaker::Lorem.sentence, } Notes::CreateService.new(project, user, note_params).execute diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb index 4b13520259..d06905285f 100644 --- a/features/steps/project/hooks.rb +++ b/features/steps/project/hooks.rb @@ -23,7 +23,7 @@ class Spinach::Features::ProjectHooks < Spinach::FeatureSteps end step 'I submit new hook' do - @url = Faker::Internet.uri("http") + @url = FFaker::Internet.uri("http") fill_in "hook_url", with: @url expect { click_button "Add Web Hook" }.to change(ProjectHook, :count).by(1) end diff --git a/spec/factories.rb b/spec/factories.rb index 26e8a795fa..b7b2a1dac8 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -2,23 +2,23 @@ include ActionDispatch::TestProcess FactoryGirl.define do sequence :sentence, aliases: [:title, :content] do - Faker::Lorem.sentence + FFaker::Lorem.sentence end sequence :name do - Faker::Name.name + FFaker::Name.name end sequence :file_name do - Faker::Internet.user_name + FFaker::Internet.user_name end - sequence(:url) { Faker::Internet.uri('http') } + sequence(:url) { FFaker::Internet.uri('http') } factory :user, aliases: [:author, :assignee, :owner, :creator] do - email { Faker::Internet.email } + email { FFaker::Internet.email } name - sequence(:username) { |n| "#{Faker::Internet.user_name}#{n}" } + sequence(:username) { |n| "#{FFaker::Internet.user_name}#{n}" } password "12345678" confirmed_at { Time.now } confirmation_token { nil } @@ -122,12 +122,12 @@ FactoryGirl.define do factory :email do user email do - Faker::Internet.email('alias') + FFaker::Internet.email('alias') end factory :another_email do email do - Faker::Internet.email('another.alias') + FFaker::Internet.email('another.alias') end end end diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index 25862614d2..00906e8087 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -26,7 +26,7 @@ describe "Admin::Hooks", feature: true do describe "New Hook" do before do - @url = Faker::Internet.uri("http") + @url = FFaker::Internet.uri("http") visit admin_hooks_path fill_in "hook_url", with: @url expect { click_button "Add System Hook" }.to change(SystemHook, :count).by(1) diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 37607b55eb..c40ae7b570 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -185,7 +185,7 @@ describe Notify do context 'for issues' do let(:issue) { create(:issue, author: current_user, assignee: assignee, project: project) } - let(:issue_with_description) { create(:issue, author: current_user, assignee: assignee, project: project, description: Faker::Lorem.sentence) } + let(:issue_with_description) { create(:issue, author: current_user, assignee: assignee, project: project, description: FFaker::Lorem.sentence) } describe 'that are new' do subject { Notify.new_issue_email(issue.assignee_id, issue.id) } @@ -273,7 +273,7 @@ describe Notify do context 'for merge requests' do let(:merge_author) { create(:user) } let(:merge_request) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project) } - let(:merge_request_with_description) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project, description: Faker::Lorem.sentence) } + let(:merge_request_with_description) { create(:merge_request, author: current_user, assignee: assignee, source_project: project, target_project: project, description: FFaker::Lorem.sentence) } describe 'that are new' do subject { Notify.new_merge_request_email(merge_request.assignee_id, merge_request.id) } diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index aada7febf6..46cd26eb92 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -165,7 +165,7 @@ describe API::API, api: true do it "should assign attributes to project" do project = attributes_for(:project, { path: 'camelCasePath', - description: Faker::Lorem.sentence, + description: FFaker::Lorem.sentence, issues_enabled: false, merge_requests_enabled: false, wiki_enabled: false @@ -274,7 +274,7 @@ describe API::API, api: true do it 'should assign attributes to project' do project = attributes_for(:project, { - description: Faker::Lorem.sentence, + description: FFaker::Lorem.sentence, issues_enabled: false, merge_requests_enabled: false, wiki_enabled: false From 11af51613d219aecd2a8cdce879d983f82434d47 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 20 May 2015 19:50:35 -0400 Subject: [PATCH 043/255] Fix bin/rails binstub --- bin/rails | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bin/rails b/bin/rails index 215a2ea01f..7feb6a30e6 100755 --- a/bin/rails +++ b/bin/rails @@ -3,5 +3,6 @@ begin load File.expand_path("../spring", __FILE__) rescue LoadError end -require 'bundler/setup' -load Gem.bin_path('rails', 'rails') +APP_PATH = File.expand_path('../../config/application', __FILE__) +require_relative '../config/boot' +require 'rails/commands' From 212fe14c65b523ba71e3a199028d577b21216c60 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 20 May 2015 20:55:11 -0400 Subject: [PATCH 044/255] Customize the sanitization whitelist only once Fixes #1651 --- lib/gitlab/markdown/sanitization_filter.rb | 35 ++++++++++++++-------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/lib/gitlab/markdown/sanitization_filter.rb b/lib/gitlab/markdown/sanitization_filter.rb index 6f33155bad..88781fea0c 100644 --- a/lib/gitlab/markdown/sanitization_filter.rb +++ b/lib/gitlab/markdown/sanitization_filter.rb @@ -8,28 +8,33 @@ module Gitlab # Extends HTML::Pipeline::SanitizationFilter with a custom whitelist. class SanitizationFilter < HTML::Pipeline::SanitizationFilter def whitelist - whitelist = HTML::Pipeline::SanitizationFilter::WHITELIST + whitelist = super - # Allow code highlighting - whitelist[:attributes]['pre'] = %w(class) - whitelist[:attributes]['span'] = %w(class) + # Only push these customizations once + unless customized?(whitelist[:transformers]) + # Allow code highlighting + whitelist[:attributes]['pre'] = %w(class) + whitelist[:attributes]['span'] = %w(class) - # Allow table alignment - whitelist[:attributes]['th'] = %w(style) - whitelist[:attributes]['td'] = %w(style) + # Allow table alignment + whitelist[:attributes]['th'] = %w(style) + whitelist[:attributes]['td'] = %w(style) - # Allow span elements - whitelist[:elements].push('span') + # Allow span elements + whitelist[:elements].push('span') - # Remove `rel` attribute from `a` elements - whitelist[:transformers].push(remove_rel) + # Remove `rel` attribute from `a` elements + whitelist[:transformers].push(remove_rel) - # Remove `class` attribute from non-highlight spans - whitelist[:transformers].push(clean_spans) + # Remove `class` attribute from non-highlight spans + whitelist[:transformers].push(clean_spans) + end whitelist end + private + def remove_rel lambda do |env| if env[:node_name] == 'a' @@ -48,6 +53,10 @@ module Gitlab end end end + + def customized?(transformers) + transformers.last.source_location[0] == __FILE__ + end end end end From 3c892f3554dd0a7d03c1961a7e4504f03a17c3b5 Mon Sep 17 00:00:00 2001 From: Alex Connor Date: Sun, 8 Feb 2015 17:12:44 -0600 Subject: [PATCH 045/255] Disabled expansion of top/bottom blobs for new file diffs --- CHANGELOG | 1 + app/helpers/diff_helper.rb | 4 ++++ app/views/projects/blob/diff.html.haml | 4 ++-- app/views/projects/diffs/_match_line.html.haml | 8 ++++---- app/views/projects/diffs/_text_file.html.haml | 4 ++-- spec/helpers/diff_helper_spec.rb | 10 ++++++++++ 6 files changed, 23 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index fdb37bcc3a..6d9fb13a81 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 7.12.0 (unreleased) - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) + - Disabled expansion of top/bottom blobs for new file diffs v 7.11.0 (unreleased) - Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu) diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index 1b10795bb7..1bd3ec5e0e 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -101,6 +101,10 @@ module DiffHelper (bottom) ? 'js-unfold-bottom' : '' end + def unfold_class(unfold) + (unfold) ? 'unfold js-unfold' : '' + end + def diff_line_content(line) if line.blank? "  " diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index 5c79d0ef11..8474260898 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -2,7 +2,7 @@ - if @form.unfold? && @form.since != 1 && !@form.bottom? %tr.line_holder{ id: @form.since } = render "projects/diffs/match_line", {line: @match_line, - line_old: @form.since, line_new: @form.since, bottom: false} + line_old: @form.since, line_new: @form.since, bottom: false, new_file: false} - @lines.each_with_index do |line, index| - line_new = index + @form.since @@ -16,4 +16,4 @@ - if @form.unfold? && @form.bottom? && @form.to < @blob.loc %tr.line_holder{ id: @form.to } = render "projects/diffs/match_line", {line: @match_line, - line_old: @form.to, line_new: @form.to, bottom: true} + line_old: @form.to, line_new: @form.to, bottom: true, new_file: false} diff --git a/app/views/projects/diffs/_match_line.html.haml b/app/views/projects/diffs/_match_line.html.haml index 4ebe337973..d1f897b99f 100644 --- a/app/views/projects/diffs/_match_line.html.haml +++ b/app/views/projects/diffs/_match_line.html.haml @@ -1,7 +1,7 @@ -%td.old_line.diff-line-num.unfold.js-unfold{data: {linenumber: line_old}, - class: unfold_bottom_class(bottom)} +%td.old_line.diff-line-num{data: {linenumber: line_old}, + class: [unfold_bottom_class(bottom), unfold_class(!new_file)]} \... -%td.new_line.diff-line-num.unfold.js-unfold{data: {linenumber: line_new}, - class: unfold_bottom_class(bottom)} +%td.new_line.diff-line-num{data: {linenumber: line_new}, + class: [unfold_bottom_class(bottom), unfold_class(!new_file)]} \... %td.line_content.matched= line diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml index e6dfbfd651..a6373181b4 100644 --- a/app/views/projects/diffs/_text_file.html.haml +++ b/app/views/projects/diffs/_text_file.html.haml @@ -12,7 +12,7 @@ %tr.line_holder{ id: line_code, class: "#{type}" } - if type == "match" = render "projects/diffs/match_line", {line: line.text, - line_old: line_old, line_new: line.new_pos, bottom: false} + line_old: line_old, line_new: line.new_pos, bottom: false, new_file: diff_file.new_file} - else %td.old_line = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code @@ -29,7 +29,7 @@ - if last_line > 0 = render "projects/diffs/match_line", {line: "", - line_old: last_line, line_new: last_line, bottom: true} + line_old: last_line, line_new: last_line, bottom: true, new_file: diff_file.new_file} - if diff_file.diff.blank? && diff_file.mode_changed? .file-mode-changed diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb index dd4c1d645e..e0be2df0e5 100644 --- a/spec/helpers/diff_helper_spec.rb +++ b/spec/helpers/diff_helper_spec.rb @@ -106,6 +106,16 @@ describe DiffHelper do end end + describe 'unfold_class' do + it 'returns empty on false' do + expect(unfold_class(false)).to eq('') + end + + it 'returns a class on true' do + expect(unfold_class(true)).to eq('unfold js-unfold') + end + end + describe 'diff_line_content' do it 'should return non breaking space when line is empty' do From 5b44a54239284f9f0129ca229f4528a1c71f4193 Mon Sep 17 00:00:00 2001 From: Jeroen van Baarsen Date: Thu, 21 May 2015 09:27:57 +0200 Subject: [PATCH 046/255] Updated the gitlab_git gem **What does this do?** It updated the version of gitlab_git to the latest version **Why is this needed?* There was a bug in rugged (dependency of gitlab_git) that causes a segfault error when seeding the database. That error has been fixed and the fix is in the latest version of gitlab_git Signed-off-by: Jeroen van Baarsen --- Gemfile | 2 +- Gemfile.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Gemfile b/Gemfile index f7da36be94..6de516a11f 100644 --- a/Gemfile +++ b/Gemfile @@ -44,7 +44,7 @@ gem "browser" # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '~> 7.1.12' +gem "gitlab_git", '~> 7.1.13' # Ruby/Rack Git Smart-HTTP Server Handler gem 'gitlab-grack', '~> 2.0.2', require: 'grack' diff --git a/Gemfile.lock b/Gemfile.lock index b6cf03b0fd..586036923f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -225,11 +225,11 @@ GEM mime-types (~> 1.19) gitlab_emoji (0.1.0) gemojione (~> 2.0) - gitlab_git (7.1.12) + gitlab_git (7.1.13) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-linguist (~> 3.0) - rugged (~> 0.21.2) + rugged (~> 0.22.2) gitlab_meta (7.0) gitlab_omniauth-ldap (1.2.1) net-ldap (~> 0.9) @@ -530,7 +530,7 @@ GEM sexp_processor (~> 4.1) rubyntlm (0.5.0) rubypants (0.2.0) - rugged (0.21.4) + rugged (0.22.2) rugments (1.0.0.beta6) safe_yaml (0.9.7) sanitize (2.1.0) @@ -723,7 +723,7 @@ DEPENDENCIES gitlab-grack (~> 2.0.2) gitlab-linguist (~> 3.0.1) gitlab_emoji (~> 0.1) - gitlab_git (~> 7.1.12) + gitlab_git (~> 7.1.13) gitlab_meta (= 7.0) gitlab_omniauth-ldap (= 1.2.1) gollum-lib (~> 4.0.2) From 7d658f2dbde614090941983b9f8bc3ae091235ea Mon Sep 17 00:00:00 2001 From: Jeroen van Baarsen Date: Thu, 21 May 2015 09:46:42 +0200 Subject: [PATCH 047/255] Moved changelog stuff to the correct release **Why was this needed?** The changes are pulled in 7.12.0 but where mistakenly put under 7.11.0 [skip-ci] Signed-off-by: Jeroen van Baarsen --- CHANGELOG | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6d9fb13a81..aea66986e3 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,8 +1,10 @@ Please view this file on the master branch, on stable branches it's out of date. v 7.12.0 (unreleased) -- Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) + - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) - Disabled expansion of top/bottom blobs for new file diffs + - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) + - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) v 7.11.0 (unreleased) - Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu) @@ -72,8 +74,6 @@ v 7.11.0 (unreleased) - Spin spinner icon next to "Checking for CI status..." on MR page. - Fix reference links in dashboard activity and ATOM feeds. - Ensure that the first added admin performs repository imports - - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) - - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) v 7.10.2 - Fix CI links on MR page From cbcc5f854400f8e22c2164d6265fb863279c9622 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 21 May 2015 11:22:21 +0200 Subject: [PATCH 048/255] Workaround that doesn't add unwanted newline. --- CHANGELOG | 1 + app/assets/javascripts/notes.js.coffee | 9 +++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index fdb37bcc3a..36fd26baa5 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,6 +4,7 @@ v 7.12.0 (unreleased) - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) v 7.11.0 (unreleased) + - Get editing comments to work in Chrome 43 again. - Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu) - Don't show duplicate deploy keys - Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger) diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index b87659d622..f186fec2a0 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -313,11 +313,12 @@ class @Notes textarea = form.find("textarea") textarea.focus() - # HACK (rspeicher): Work around a Chrome 43 bug(?). + # HACK (rspeicher/DouweM): Work around a Chrome 43 bug(?). # The textarea has the correct value, Chrome just won't show it unless we - # modify it, so let's add a newline! - textarea.val (_, value) -> - "#{value}\n" + # modify it, so let's clear it and re-set it! + value = textarea.val() + textarea.val "" + textarea.val value disableButtonIfEmptyField textarea, form.find(".js-comment-button") From 9a60441ce42f6f8cef2186fd53adfe4c694f2402 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Thu, 21 May 2015 11:39:33 +0200 Subject: [PATCH 049/255] Fix Atom feeds. --- app/views/dashboard/issues.atom.builder | 2 +- app/views/dashboard/show.atom.builder | 2 +- app/views/events/_event_push.atom.haml | 2 +- app/views/groups/show.atom.builder | 2 +- app/views/projects/commits/show.atom.builder | 2 +- app/views/projects/issues/index.atom.builder | 2 +- app/views/projects/show.atom.builder | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder index 6e88fc9be4..07bda1c77f 100644 --- a/app/views/dashboard/issues.atom.builder +++ b/app/views/dashboard/issues.atom.builder @@ -1,7 +1,7 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "#{current_user.name} issues" - xml.link href: issues_dashboard_url(format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" + xml.link href: issues_dashboard_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html" xml.id issues_dashboard_url xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/show.atom.builder index 71edb73cd8..e9a612231d 100644 --- a/app/views/dashboard/show.atom.builder +++ b/app/views/dashboard/show.atom.builder @@ -1,7 +1,7 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "Activity" - xml.link href: dashboard_url(format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" + xml.link href: dashboard_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: dashboard_url, rel: "alternate", type: "text/html" xml.id dashboard_url xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml index 42762e04b5..3625cb49d8 100644 --- a/app/views/events/_event_push.atom.haml +++ b/app/views/events/_event_push.atom.haml @@ -6,7 +6,7 @@ %i at = commit[:timestamp].to_time.to_s(:short) - %blockquote= markdown(escape_once(commit[:message]), xhtml: true, reference_only_path: false, project: note.project) + %blockquote= markdown(escape_once(commit[:message]), xhtml: true, reference_only_path: false, project: event.project) - if event.commits_count > 15 %p %i diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder index b52e78faaa..a91d1a6e94 100644 --- a/app/views/groups/show.atom.builder +++ b/app/views/groups/show.atom.builder @@ -1,7 +1,7 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "#{@group.name} activity" - xml.link href: group_url(@group, format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" + xml.link href: group_url(@group, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: group_url(@group), rel: "alternate", type: "text/html" xml.id group_url(@group) xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder index 01edd9447c..3854ad5d61 100644 --- a/app/views/projects/commits/show.atom.builder +++ b/app/views/projects/commits/show.atom.builder @@ -1,7 +1,7 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "#{@project.name}:#{@ref} commits" - xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" + xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref), rel: "alternate", type: "text/html" xml.id namespace_project_commits_url(@project.namespace, @project, @ref) xml.updated @commits.first.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") if @commits.any? diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder index 5fa8fbdf89..dc8e477185 100644 --- a/app/views/projects/issues/index.atom.builder +++ b/app/views/projects/issues/index.atom.builder @@ -1,7 +1,7 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "#{@project.name} issues" - xml.link href: namespace_project_issues_url(@project.namespace, @project, format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" + xml.link href: namespace_project_issues_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: namespace_project_issues_url(@project.namespace, @project), rel: "alternate", type: "text/html" xml.id namespace_project_issues_url(@project.namespace, @project) xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? diff --git a/app/views/projects/show.atom.builder b/app/views/projects/show.atom.builder index bb713dcafa..242684e5c7 100644 --- a/app/views/projects/show.atom.builder +++ b/app/views/projects/show.atom.builder @@ -1,7 +1,7 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "#{@project.name} activity" - xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" + xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html" xml.id namespace_project_url(@project.namespace, @project) xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? From 871c90c3d1bd02e0d4163afebe422f307e90c7c0 Mon Sep 17 00:00:00 2001 From: Karen Carias Date: Thu, 21 May 2015 20:03:25 +0000 Subject: [PATCH 050/255] Added instructions to change time zone in GitLab CE --- doc/workflow/timezone.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 doc/workflow/timezone.md diff --git a/doc/workflow/timezone.md b/doc/workflow/timezone.md new file mode 100644 index 0000000000..b513088dbc --- /dev/null +++ b/doc/workflow/timezone.md @@ -0,0 +1,18 @@ +# Changing your time zone + +GitLab defaults its time zone to UTC. It has a global timezone configuration parameter in config/application.rb. + +To update, add the time zone that best applies to your location. Here are two examples: +``` +gitlab_rails['time_zone'] = 'America/New_York' +``` +or +``` +gitlab_rails['time_zone'] = 'Europe/Brussels' +``` + +After you added this field, reconfigure and restart: +``` +gitlab-ctl reconfigure +gitlab-ctl restart +``` \ No newline at end of file From 1132b3e8dc2c17a1999ec7f8bc06a0d3c810c9e1 Mon Sep 17 00:00:00 2001 From: Karen Carias Date: Thu, 21 May 2015 20:05:39 +0000 Subject: [PATCH 051/255] Link to change your time zone in Gitlab CE --- doc/workflow/README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 7e996dc47d..b90a6a50af 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -12,4 +12,5 @@ - [Project importing from GitHub to GitLab](import_projects_from_github.md) - [Project importing from GitLab.com to your private GitLab instance](import_projects_from_gitlab_com.md) - [Protected branches](protected_branches.md) -- [Web Editor](web_editor.md) +- [Change your time zone](timezone.md) +- [Web Editor](web_editor.md) \ No newline at end of file From 9622348bed60f630ec4c760695b7012a8c9f5c74 Mon Sep 17 00:00:00 2001 From: Karen Carias Date: Thu, 21 May 2015 21:39:00 +0000 Subject: [PATCH 052/255] Added note about backups for gitlab.com --- doc/raketasks/backup_restore.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index bca4fcfb40..2c858ed780 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -299,3 +299,6 @@ Example: LVM snapshots + rsync If you are running GitLab on a virtualized server you can possibly also create VM snapshots of the entire GitLab server. It is not uncommon however for a VM snapshot to require you to power down the server, so this approach is probably of limited practical use. + +### Note +This documentation is for GitLab CE. Users can't create backups for gitlab.com. \ No newline at end of file From 9bdd7f34a52da2faa3677e26b57a7b270d629d97 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 21 May 2015 23:40:25 -0400 Subject: [PATCH 053/255] Add link_to_label helper --- app/helpers/labels_helper.rb | 38 +++++++++++ .../projects/issues/_discussion.html.haml | 3 +- app/views/projects/issues/_issue.html.haml | 3 +- app/views/projects/labels/_label.html.haml | 2 +- .../merge_requests/_discussion.html.haml | 3 +- .../merge_requests/_merge_request.html.haml | 3 +- spec/helpers/labels_helper_spec.rb | 68 ++++++++++++++++++- 7 files changed, 109 insertions(+), 11 deletions(-) diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 8272c177d5..8036303851 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -1,6 +1,44 @@ module LabelsHelper include ActionView::Helpers::TagHelper + # Link to a Label + # + # label - Label object to link to + # project - Project object which will be used as the context for the label's + # link. If omitted, defaults to `@project`, or the label's own + # project. + # block - An optional block that will be passed to `link_to`, forming the + # body of the link element. If omitted, defaults to + # `render_colored_label`. + # + # Examples: + # + # # Allow the generated link to use the label's own project + # link_to_label(label) + # + # # Force the generated link to use @project + # @project = Project.first + # link_to_label(label) + # + # # Force the generated link to use a provided project + # link_to_label(label, project: Project.last) + # + # # Customize link body with a block + # link_to_label(label) { "My Custom Label Text" } + # + # Returns a String + def link_to_label(label, project: nil, &block) + project ||= @project || label.project + link = namespace_project_issues_path(project.namespace, project, + label_name: label.name) + + if block_given? + link_to link, &block + else + link_to render_colored_label(label), link + end + end + def project_label_names @project.labels.pluck(:title) end diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml index 2016f5c709..48858fa32d 100644 --- a/app/views/projects/issues/_discussion.html.haml +++ b/app/views/projects/issues/_discussion.html.haml @@ -30,5 +30,4 @@ %label Labels .issue-show-labels - @issue.labels.each do |label| - = link_to namespace_project_issues_path(@project.namespace, @project, label_name: label.name) do - = render_colored_label(label) + = link_to_label(label) diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index ef36d1f954..a4e25e5ce8 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -8,8 +8,7 @@ = link_to_gfm issue.title, issue_path(issue), class: "row_title" .issue-labels - issue.labels.each do |label| - = link_to namespace_project_issues_path(issue.project.namespace, issue.project, label_name: label.name) do - = render_colored_label(label) + = link_to_label(label, project: issue.project) .pull-right.light - if issue.closed? %span diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index 8282945286..c9ac0dbe0c 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -2,7 +2,7 @@ = render_colored_label(label) .pull-right %strong.append-right-20 - = link_to namespace_project_issues_path(@project.namespace, @project, label_name: label.name) do + = link_to_label(label) do = pluralize label.open_issues_count, 'open issue' - if can? current_user, :admin_label, @project diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml index 9a2aa9c3de..eb3dba6858 100644 --- a/app/views/projects/merge_requests/_discussion.html.haml +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -27,5 +27,4 @@ %label Labels .merge-request-show-labels - @merge_request.labels.each do |label| - = link_to namespace_project_merge_requests_path(@project.namespace, @project, label_name: label.name) do - = render_colored_label(label) + = link_to_label(label) diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 5d5a23b540..073476b0d2 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -4,8 +4,7 @@ = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title" .merge-request-labels - merge_request.labels.each do |label| - = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, label_name: label.name) do - = render_colored_label(label) + = link_to_label(label, project: merge_request.project) .pull-right.light - if merge_request.merged? %span diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb index 0b7e3b1d11..0c8d06b705 100644 --- a/spec/helpers/labels_helper_spec.rb +++ b/spec/helpers/labels_helper_spec.rb @@ -1,6 +1,70 @@ require 'spec_helper' describe LabelsHelper do - it { expect(text_color_for_bg('#EEEEEE')).to eq('#333333') } - it { expect(text_color_for_bg('#222E2E')).to eq('#FFFFFF') } + describe 'link_to_label' do + let(:project) { create(:empty_project) } + let(:label) { create(:label, project: project) } + + context 'with @project set' do + before do + @project = project + end + + it 'uses the instance variable' do + expect(label).not_to receive(:project) + link_to_label(label) + end + end + + context 'without @project set' do + it "uses the label's project" do + expect(label).to receive(:project).and_return(project) + link_to_label(label) + end + end + + context 'with a named project argument' do + it 'uses the provided project' do + arg = double('project') + expect(arg).to receive(:namespace).and_return('foo') + expect(arg).to receive(:to_param).and_return('foo') + + link_to_label(label, project: arg) + end + + it 'takes precedence over other types' do + @project = project + expect(@project).not_to receive(:namespace) + expect(label).not_to receive(:project) + + arg = double('project', namespace: 'foo', to_param: 'foo') + link_to_label(label, project: arg) + end + end + + context 'with block' do + it 'passes the block to link_to' do + link = link_to_label(label) { 'Foo' } + expect(link).to match('Foo') + end + end + + context 'without block' do + it 'uses render_colored_label as the link content' do + expect(self).to receive(:render_colored_label). + with(label).and_return('Foo') + expect(link_to_label(label)).to match('Foo') + end + end + end + + describe 'text_color_for_bg' do + it 'uses light text on dark backgrounds' do + expect(text_color_for_bg('#222E2E')).to eq('#FFFFFF') + end + + it 'uses dark text on light backgrounds' do + expect(text_color_for_bg('#EEEEEE')).to eq('#333333') + end + end end From 4e2ee018c372b2973001e58e57e2a9747763f284 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 21 May 2015 23:40:46 -0400 Subject: [PATCH 054/255] Make the actual labels on Labels#index links Now the user can click the labels themselves or the "X open issues" text. --- app/views/projects/labels/_label.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index c9ac0dbe0c..7fa1ee53f7 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -1,5 +1,5 @@ %li{id: dom_id(label)} - = render_colored_label(label) + = link_to_label(label) .pull-right %strong.append-right-20 = link_to_label(label) do From deeff56967516764b287e15b2063899b13395b41 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 15 May 2015 23:33:31 -0700 Subject: [PATCH 055/255] Add support for Webhook note events Closes https://github.com/gitlabhq/gitlabhq/issues/6745 --- CHANGELOG | 1 + app/controllers/projects/hooks_controller.rb | 2 +- app/models/hooks/project_hook.rb | 2 + app/models/hooks/service_hook.rb | 1 + app/models/hooks/system_hook.rb | 1 + app/models/hooks/web_hook.rb | 2 + app/services/notes/create_service.rb | 2 +- app/views/projects/hooks/index.html.haml | 9 +- ...0516060434_add_note_events_to_web_hooks.rb | 9 + db/schema.rb | 3 +- doc/web_hooks/web_hooks.md | 279 ++++++++++++++++++ lib/api/project_hooks.rb | 6 +- spec/models/hooks/project_hook_spec.rb | 1 + spec/models/hooks/service_hook_spec.rb | 1 + spec/models/hooks/system_hook_spec.rb | 1 + spec/models/hooks/web_hook_spec.rb | 1 + spec/services/notes/create_service_spec.rb | 2 + 17 files changed, 317 insertions(+), 6 deletions(-) create mode 100644 db/migrate/20150516060434_add_note_events_to_web_hooks.rb diff --git a/CHANGELOG b/CHANGELOG index d847496817..a1e972db8c 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 7.12.0 (unreleased) + - Add web hook support for note events (Stan Hu) - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) - Disabled expansion of top/bottom blobs for new file diffs - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index 57fc48ac7d..76062446c9 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -53,6 +53,6 @@ class Projects::HooksController < Projects::ApplicationController end def hook_params - params.require(:hook).permit(:url, :push_events, :issues_events, :merge_requests_events, :tag_push_events) + params.require(:hook).permit(:url, :push_events, :issues_events, :merge_requests_events, :tag_push_events, :note_events) end end diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb index 21867a9316..ca7066b959 100644 --- a/app/models/hooks/project_hook.rb +++ b/app/models/hooks/project_hook.rb @@ -13,6 +13,7 @@ # issues_events :boolean default(FALSE), not null # merge_requests_events :boolean default(FALSE), not null # tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null # class ProjectHook < WebHook @@ -21,5 +22,6 @@ class ProjectHook < WebHook scope :push_hooks, -> { where(push_events: true) } scope :tag_push_hooks, -> { where(tag_push_events: true) } scope :issue_hooks, -> { where(issues_events: true) } + scope :note_hooks, -> { where(note_events: true) } scope :merge_request_hooks, -> { where(merge_requests_events: true) } end diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb index 5b38ade2e6..b55e217975 100644 --- a/app/models/hooks/service_hook.rb +++ b/app/models/hooks/service_hook.rb @@ -13,6 +13,7 @@ # issues_events :boolean default(FALSE), not null # merge_requests_events :boolean default(FALSE), not null # tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null # class ServiceHook < WebHook diff --git a/app/models/hooks/system_hook.rb b/app/models/hooks/system_hook.rb index ee32b49bc6..6fb2d42102 100644 --- a/app/models/hooks/system_hook.rb +++ b/app/models/hooks/system_hook.rb @@ -13,6 +13,7 @@ # issues_events :boolean default(FALSE), not null # merge_requests_events :boolean default(FALSE), not null # tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null # class SystemHook < WebHook diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index e9fd441352..46fb85336e 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -13,6 +13,7 @@ # issues_events :boolean default(FALSE), not null # merge_requests_events :boolean default(FALSE), not null # tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null # class WebHook < ActiveRecord::Base @@ -21,6 +22,7 @@ class WebHook < ActiveRecord::Base default_value_for :push_events, true default_value_for :issues_events, false + default_value_for :note_events, false default_value_for :merge_requests_events, false default_value_for :tag_push_events, false diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index d19a6c2eca..0ff37c4174 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -31,7 +31,7 @@ module Notes def execute_hooks(note) note_data = hook_data(note) - # TODO: Support Webhooks + note.project.execute_hooks(note_data, :note_hooks) note.project.execute_services(note_data, :note_hooks) end end diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index 808c03148f..eadbf61fdd 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -34,6 +34,13 @@ %strong Tag push events %p.light This url will be triggered when a new tag is pushed to the repository + %div + = f.check_box :note_events, class: 'pull-left' + .prepend-left-20 + = f.label :note_events, class: 'list-label' do + %strong Comments + %p.light + This url will be triggered when someone adds a comment %div = f.check_box :issues_events, class: 'pull-left' .prepend-left-20 @@ -64,6 +71,6 @@ .clearfix %span.monospace= hook.url %p - - %w(push_events tag_push_events issues_events merge_requests_events).each do |trigger| + - %w(push_events tag_push_events issues_events note_events merge_requests_events).each do |trigger| - if hook.send(trigger) %span.label.label-gray= trigger.titleize diff --git a/db/migrate/20150516060434_add_note_events_to_web_hooks.rb b/db/migrate/20150516060434_add_note_events_to_web_hooks.rb new file mode 100644 index 0000000000..0097587b4f --- /dev/null +++ b/db/migrate/20150516060434_add_note_events_to_web_hooks.rb @@ -0,0 +1,9 @@ +class AddNoteEventsToWebHooks < ActiveRecord::Migration + def up + add_column :web_hooks, :note_events, :boolean, default: false, null: false + end + + def down + remove_column :web_hooks, :note_events, :boolean + end +end diff --git a/db/schema.rb b/db/schema.rb index f7581eaf7f..1ab9125640 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: 20150509180749) do +ActiveRecord::Schema.define(version: 20150516060434) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -533,6 +533,7 @@ ActiveRecord::Schema.define(version: 20150509180749) do 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 end add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index d140f3a457..73717ffc7d 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -140,6 +140,285 @@ X-Gitlab-Event: Issue Hook } } ``` +## Comment events + +Triggered when a new comment is made on commits, merge requests, issues, and code snippets. +The note data will be stored in `object_attributes` (e.g. `note`, `noteable_type`). The +payload will also include information about the target of the comment. For example, +a comment on a issue will include the specific issue information under the `issue` key. +Valid target types: + +1. `commit` +2. `merge_request` +3. `issue` +4. `snippet` + +### Comment on commit + +**Request header**: + +``` +X-Gitlab-Event: Note Hook +``` + +**Request body:** + +```json +{ + "object_kind": "note", + "user": { + "name": "Adminstrator", + "username": "root", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" + }, + "project_id": 5, + "repository": { + "name": "Gitlab Test", + "url": "http://localhost/gitlab-org/gitlab-test.git", + "description": "Aut reprehenderit ut est.", + "homepage": "http://example.com/gitlab-org/gitlab-test" + }, + "object_attributes": { + "id": 1243, + "note": "This is a commit comment. How does this work?", + "noteable_type": "Commit", + "author_id": 1, + "created_at": "2015-05-17 18:08:09 UTC", + "updated_at": "2015-05-17 18:08:09 UTC", + "project_id": 5, + "attachment":null, + "line_code": "bec9703f7a456cd2b4ab5fb3220ae016e3e394e3_0_1", + "commit_id": "cfe32cf61b73a0d5e9f13e774abde7ff789b1660", + "noteable_id": null, + "system": false, + "st_diff": { + "diff": "--- /dev/null\n+++ b/six\n@@ -0,0 +1 @@\n+Subproject commit 409f37c4f05865e4fb208c771485f211a22c4c2d\n", + "new_path": "six", + "old_path": "six", + "a_mode": "0", + "b_mode": "160000", + "new_file": true, + "renamed_file": false, + "deleted_file": false + }, + "url": "http://example.com/gitlab-org/gitlab-test/commit/cfe32cf61b73a0d5e9f13e774abde7ff789b1660#note_1243" + }, + "commit": { + "id": "cfe32cf61b73a0d5e9f13e774abde7ff789b1660", + "message": "Add submodule\n\nSigned-off-by: Dmitriy Zaporozhets \u003cdmitriy.zaporozhets@gmail.com\u003e\n", + "timestamp": "2014-02-27T10:06:20+02:00", + "url": "http://example.com/gitlab-org/gitlab-test/commit/cfe32cf61b73a0d5e9f13e774abde7ff789b1660", + "author": { + "name": "Dmitriy Zaporozhets", + "email": "dmitriy.zaporozhets@gmail.com" + } + } +} +``` + +### Comment on merge request + +**Request header**: + +``` +X-Gitlab-Event: Note Hook +``` + +**Request body:** + +```json +{ + "object_kind": "note", + "user": { + "name": "Administrator", + "username": "root", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" + }, + "project_id": 5, + "repository": { + "name": "Gitlab Test", + "url": "http://example.com/gitlab-org/gitlab-test.git", + "description": "Aut reprehenderit ut est.", + "homepage": "http://example.com/gitlab-org/gitlab-test" + }, + "object_attributes": { + "id": 1244, + "note": "This MR needs work.", + "noteable_type": "MergeRequest", + "author_id": 1, + "created_at": "2015-05-17 18:21:36 UTC", + "updated_at": "2015-05-17 18:21:36 UTC", + "project_id": 5, + "attachment": null, + "line_code": null, + "commit_id": "", + "noteable_id": 7, + "system": false, + "st_diff": null, + "url": "http://example.com/gitlab-org/gitlab-test/merge_requests/1#note_1244" + }, + "merge_request": { + "id": 7, + "target_branch": "markdown", + "source_branch": "master", + "source_project_id": 5, + "author_id": 8, + "assignee_id": 28, + "title": "Tempora et eos debitis quae laborum et.", + "created_at": "2015-03-01 20:12:53 UTC", + "updated_at": "2015-03-21 18:27:27 UTC", + "milestone_id": 11, + "state": "opened", + "merge_status": "cannot_be_merged", + "target_project_id": 5, + "iid": 1, + "description": "Et voluptas corrupti assumenda temporibus. Architecto cum animi eveniet amet asperiores. Vitae numquam voluptate est natus sit et ad id.", + "position": 0, + "locked_at": null, + "source": { + "name": "Gitlab Test", + "ssh_url": "git@example.com:gitlab-org/gitlab-test.git", + "http_url": "http://example.com/gitlab-org/gitlab-test.git", + "namespace": "Gitlab Org", + "visibility_level": 10 + }, + "target": { + "name": "Gitlab Test", + "ssh_url": "git@example.com:gitlab-org/gitlab-test.git", + "http_url": "http://example.com/gitlab-org/gitlab-test.git", + "namespace": "Gitlab Org", + "visibility_level": 10 + }, + "last_commit": { + "id": "562e173be03b8ff2efb05345d12df18815438a4b", + "message": "Merge branch 'another-branch' into 'master'\n\nCheck in this test\n", + "timestamp": "2015-04-08T21: 00:25-07:00", + "url": "http://example.com/gitlab-org/gitlab-test/commit/562e173be03b8ff2efb05345d12df18815438a4b", + "author": { + "name": "John Smith", + "email": "john@example.com" + } + } + } +} +``` + +### Comment on issue + +**Request header**: + +``` +X-Gitlab-Event: Note Hook +``` + +**Request body:** + +```json +{ + "object_kind": "note", + "user": { + "name": "Adminstrator", + "username": "root", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" + }, + "project_id": 5, + "repository": { + "name": "Gitlab Test", + "url": "http://example.com/gitlab-org/gitlab-test.git", + "description": "Aut reprehenderit ut est.", + "homepage": "http://example.com/gitlab-org/gitlab-test" + }, + "object_attributes": { + "id": 1241, + "note": "Hello world", + "noteable_type": "Issue", + "author_id": 1, + "created_at": "2015-05-17 17:06:40 UTC", + "updated_at": "2015-05-17 17:06:40 UTC", + "project_id": 5, + "attachment": null, + "line_code": null, + "commit_id": "", + "noteable_id": 92, + "system": false, + "st_diff": null, + "url": "http://example.com/gitlab-org/gitlab-test/issues/17#note_1241" + }, + "issue": { + "id": 92, + "title": "test", + "assignee_id": null, + "author_id": 1, + "project_id": 5, + "created_at": "2015-04-12 14:53:17 UTC", + "updated_at": "2015-04-26 08:28:42 UTC", + "position": 0, + "branch_name": null, + "description": "test", + "milestone_id": null, + "state": "closed", + "iid": 17 + } +} +``` + +### Comment on code snippet + + +**Request header**: + +``` +X-Gitlab-Event: Note Hook +``` + +**Request body:** + +``` +{ + "object_kind": "note", + "user": { + "name": "Administrator", + "username": "root", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" + }, + "project_id": 5, + "repository": { + "name": "Gitlab Test", + "url": "http://example.com/gitlab-org/gitlab-test.git", + "description": "Aut reprehenderit ut est.", + "homepage": "http://example.com/gitlab-org/gitlab-test" + }, + "object_attributes": { + "id": 1245, + "note": "Is this snippet doing what it's supposed to be doing?", + "noteable_type": "Snippet", + "author_id": 1, + "created_at": "2015-05-17 18:35:50 UTC", + "updated_at": "2015-05-17 18:35:50 UTC", + "project_id": 5, + "attachment": null, + "line_code": null, + "commit_id": "", + "noteable_id": 53, + "system": false, + "st_diff": null, + "url": "http://example.com/gitlab-org/gitlab-test/snippets/53#note_1245" + }, + "snippet": { + "id": 53, + "title": "test", + "content": "puts 'Hello world'", + "author_id": 1, + "project_id": 5, + "created_at": "2015-04-09 02:40:38 UTC", + "updated_at": "2015-04-09 02:40:38 UTC", + "file_name": "test.rb", + "expires_at": null, + "type": "ProjectSnippet", + "visibility_level": 0 + } +} +``` ## Merge request events diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index be9850367b..ad4d2e65df 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -43,7 +43,8 @@ module API :push_events, :issues_events, :merge_requests_events, - :tag_push_events + :tag_push_events, + :note_events ] @hook = user_project.hooks.new(attrs) @@ -73,7 +74,8 @@ module API :push_events, :issues_events, :merge_requests_events, - :tag_push_events + :tag_push_events, + :note_events ] if @hook.update_attributes attrs diff --git a/spec/models/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb index 4e0d50d7f3..dae7e399cf 100644 --- a/spec/models/hooks/project_hook_spec.rb +++ b/spec/models/hooks/project_hook_spec.rb @@ -13,6 +13,7 @@ # issues_events :boolean default(FALSE), not null # merge_requests_events :boolean default(FALSE), not null # tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null # require 'spec_helper' diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb index d9714596f5..fb5111dd9f 100644 --- a/spec/models/hooks/service_hook_spec.rb +++ b/spec/models/hooks/service_hook_spec.rb @@ -13,6 +13,7 @@ # issues_events :boolean default(FALSE), not null # merge_requests_events :boolean default(FALSE), not null # tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null # require "spec_helper" diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb index e4b6b88656..edb21fc2e4 100644 --- a/spec/models/hooks/system_hook_spec.rb +++ b/spec/models/hooks/system_hook_spec.rb @@ -13,6 +13,7 @@ # issues_events :boolean default(FALSE), not null # merge_requests_events :boolean default(FALSE), not null # tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null # require "spec_helper" diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb index 9f5ef3eff7..4c3f0cbcbb 100644 --- a/spec/models/hooks/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -13,6 +13,7 @@ # issues_events :boolean default(FALSE), not null # merge_requests_events :boolean default(FALSE), not null # tag_push_events :boolean default(FALSE) +# note_events :boolean default(FALSE), not null # require 'spec_helper' diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index 1a02299bf1..0dc3b41278 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -15,6 +15,8 @@ describe Notes::CreateService do noteable_id: issue.id } + expect(project).to receive(:execute_hooks) + expect(project).to receive(:execute_services) @note = Notes::CreateService.new(project, user, opts).execute end From 33dbb30fb46f7b5714ccf8d096999e8c42ae5476 Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Fri, 22 May 2015 15:19:06 +0200 Subject: [PATCH 056/255] notify the core team and devs --- doc/release/monthly.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 5dd495718b..eb97f3cd7f 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -9,7 +9,8 @@ The new release manager should create overall issue to track the progress. ## Release Manager -A release manager is selected that coordinates all releases the coming month, including the patch releases for previous releases. +A release manager is selected that coordinates all releases the coming month, +including the patch releases for previous releases. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated. @@ -93,6 +94,8 @@ There are three changelogs that need to be updated: CE, EE and CI. Once the stable branches have been created, update the CHANGELOG in `master` with the upcoming version, usually X.X.X.pre. +On creating the stable branches, notify the core team and developers. + ## QA Create issue on dev.gitlab.org `gitlab` repository, named "GitLab X.X QA" in order to keep track of the progress. From 7a7ce701692d4048a7f5ab2767aa30a8f5eb05c8 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 19 May 2015 20:46:48 -0700 Subject: [PATCH 057/255] Use the user list from the target project in a merge request Closes #1535 --- CHANGELOG | 1 + app/helpers/selects_helper.rb | 5 +++-- app/views/projects/_issuable_form.html.haml | 4 ++-- .../merge_requests/show/_context.html.haml | 2 +- features/project/forked_merge_requests.feature | 12 ++++++++++++ features/steps/project/forked_merge_requests.rb | 15 +++++++++++++++ 6 files changed, 34 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d847496817..24f4215fee 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -5,6 +5,7 @@ v 7.12.0 (unreleased) - Disabled expansion of top/bottom blobs for new file diffs - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) + - Use the user list from the target project in a merge request (Stan Hu) v 7.11.0 (unreleased) - Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger) diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb index bec8f2f1aa..2b99a39804 100644 --- a/app/helpers/selects_helper.rb +++ b/app/helpers/selects_helper.rb @@ -10,6 +10,7 @@ module SelectsHelper any_user = opts[:any_user] || false email_user = opts[:email_user] || false first_user = opts[:first_user] && current_user ? current_user.username : false + project = opts[:project] || @project html = { class: css_class, @@ -21,8 +22,8 @@ module SelectsHelper } unless opts[:scope] == :all - if @project - html['data-project-id'] = @project.id + if project + html['data-project-id'] = project.id elsif @group html['data-group-id'] = @group.id end diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml index 5a19e980b6..2292aaaa21 100644 --- a/app/views/projects/_issuable_form.html.haml +++ b/app/views/projects/_issuable_form.html.haml @@ -18,7 +18,7 @@ This merge request is marked a Work In Progress. When it's ready, remove the WIP prefix from the title to allow it to be accepted. - else - To prevent this merge request from being accepted before it's ready, + To prevent this merge request from being accepted before it's ready, mark it a Work In Progress by starting the title with [WIP] or WIP:. .form-group.issuable-description = f.label :description, 'Description', class: 'control-label' @@ -46,7 +46,7 @@ .col-sm-10 = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", placeholder: 'Select a user', class: 'custom-form-control', null_user: true, - selected: issuable.assignee_id) + selected: issuable.assignee_id, project: @target_project || @project)   = link_to 'Assign to me', '#', class: 'btn assign-to-me-link' .form-group diff --git a/app/views/projects/merge_requests/show/_context.html.haml b/app/views/projects/merge_requests/show/_context.html.haml index a5a821c184..1d0e2e350b 100644 --- a/app/views/projects/merge_requests/show/_context.html.haml +++ b/app/views/projects/merge_requests/show/_context.html.haml @@ -9,7 +9,7 @@ none .issuable-context-selectbox - if can?(current_user, :modify_merge_request, @merge_request) - = users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @merge_request.assignee_id, null_user: true) + = users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @merge_request.assignee_id, project: @target_project, null_user: true) %div.prepend-top-20.clearfix .issuable-context-title diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature index d9fbb875c2..ad1160e334 100644 --- a/features/project/forked_merge_requests.feature +++ b/features/project/forked_merge_requests.feature @@ -38,3 +38,15 @@ Feature: Project Forked Merge Requests Given I visit project "Forked Shop" merge requests page And I click link "New Merge Request" Then the target repository should be the original repository + + @javascript + Scenario: I see the users in the target project for a new merge request + Given I logout + And I sign in as an admin + And I have a project forked off of "Shop" called "Forked Shop" + Then I visit project "Forked Shop" merge requests page + And I click link "New Merge Request" + And I fill out a "Merge Request On Forked Project" merge request + When I click "Assign to" dropdown" + Then I should see the target project ID in the input selector + And I should see the users from the target project ID diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb index 94d21d28a0..ebfa102cee 100644 --- a/features/steps/project/forked_merge_requests.rb +++ b/features/steps/project/forked_merge_requests.rb @@ -128,6 +128,21 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps page.should have_select("merge_request_target_project_id", selected: @project.path_with_namespace) end + step 'I click "Assign to" dropdown"' do + first('.ajax-users-select').click + end + + step 'I should see the target project ID in the input selector' do + expect(page).to have_selector("input[data-project-id=\"#{@project.id}\"]") + end + + step 'I should see the users from the target project ID' do + expect(page).to have_selector('.user-result', visible: true, count: 2) + users = page.all('.user-name') + users[0].text.should == 'Unassigned' + users[1].text.should == @project.users.first.name + end + # Verify a link is generated against the correct project def verify_commit_link(container_div, container_project) # This should force a wait for the javascript to execute From ddc66af804f47bb0a6685bb0c761fd8b8290c8c9 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 22 May 2015 10:34:40 -0400 Subject: [PATCH 058/255] This entry was already present in v7.10.4 --- CHANGELOG | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index d847496817..0af682a9e1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -39,7 +39,6 @@ v 7.11.0 (unreleased) - Don't crash when an MR from a fork has a cross-reference comment from the target project on one of its commits. - Explain how to get a new password reset token in welcome emails - Include commit comments in MR from a forked project. - - Fix adding new group members from admin area - Group milestones by title in the dashboard and all other issue views. - Query issues, merge requests and milestones with their IID through API (Julien Bianchi) - Add default project and snippet visibility settings to the admin web UI. From c24906016fded766b713dfff504d7866dcecf379 Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Fri, 22 May 2015 14:43:37 +0000 Subject: [PATCH 059/255] update CHANGELOG with release 7.11.1 --- CHANGELOG | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d847496817..edb7970c23 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,7 +6,10 @@ v 7.12.0 (unreleased) - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) -v 7.11.0 (unreleased) +v 7.11.1 + - no changes + +v 7.11.0 - Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger) - Get editing comments to work in Chrome 43 again. - Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu) @@ -1437,4 +1440,4 @@ v 0.8.0 - stability - security fixes - increased test coverage - - email notification + - email notification \ No newline at end of file From b1e860a63adc6eb6cd0d9a50e46401b8caa0f24d Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Fri, 22 May 2015 19:03:23 +0000 Subject: [PATCH 060/255] EE can be accessed without a subscription --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1ceac4621d..85ea5c876a 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ There are two editions of GitLab. *GitLab [Community Edition](https://about.gitlab.com/features/) (CE)* is available without any costs under an MIT license. *GitLab Enterprise Edition (EE)* includes [extra features](https://about.gitlab.com/features/#compare) that are most useful for organizations with more than 100 users. -To get access to the EE and support please [become a subscriber](https://about.gitlab.com/pricing/). +To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/). ## Code status From 3b22cfe6001db636a1750a475821af5f9fa7cf1b Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 22 May 2015 16:25:03 -0400 Subject: [PATCH 061/255] Remove Rack Attack monkey patches and bump to version 4.3.0 --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 4 +-- lib/gitlab/backend/grack_auth.rb | 1 - lib/gitlab/backend/rack_attack_helpers.rb | 31 ---------------- spec/lib/gitlab/backend/grack_auth_spec.rb | 2 +- .../backend/rack_attack_helpers_spec.rb | 35 ------------------- 7 files changed, 5 insertions(+), 71 deletions(-) delete mode 100644 lib/gitlab/backend/rack_attack_helpers.rb delete mode 100644 spec/lib/gitlab/backend/rack_attack_helpers_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 6bcb531fc0..9e79b56777 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 7.12.0 (unreleased) + - Remove Rack Attack monkey patches and bump to version 4.3.0 (Stan Hu) - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) - Disabled expansion of top/bottom blobs for new file diffs - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) diff --git a/Gemfile b/Gemfile index c47a947cab..5bf71b871e 100644 --- a/Gemfile +++ b/Gemfile @@ -172,7 +172,7 @@ gem "underscore-rails", "~> 1.4.4" gem "sanitize", '~> 2.0' # Protect against bruteforcing -gem "rack-attack" +gem "rack-attack", '~> 4.3.0' # Ace editor gem 'ace-rails-ap' diff --git a/Gemfile.lock b/Gemfile.lock index 529131f09b..4aa56cc7a9 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -421,7 +421,7 @@ GEM rack (1.5.2) rack-accept (0.4.5) rack (>= 0.4) - rack-attack (4.2.0) + rack-attack (4.3.0) rack rack-cors (0.2.9) rack-mini-profiler (0.9.0) @@ -764,7 +764,7 @@ DEPENDENCIES poltergeist (~> 1.5.1) pry-rails quiet_assets (~> 1.0.1) - rack-attack + rack-attack (~> 4.3.0) rack-cors rack-mini-profiler rack-oauth2 (~> 1.0.5) diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index 050b5ba29d..03cef30c97 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -1,4 +1,3 @@ -require_relative 'rack_attack_helpers' require_relative 'shell_env' module Grack diff --git a/lib/gitlab/backend/rack_attack_helpers.rb b/lib/gitlab/backend/rack_attack_helpers.rb deleted file mode 100644 index 8538f3f6ec..0000000000 --- a/lib/gitlab/backend/rack_attack_helpers.rb +++ /dev/null @@ -1,31 +0,0 @@ -# rack-attack v4.2.0 doesn't yet support clearing of keys. -# Taken from https://github.com/kickstarter/rack-attack/issues/113 -class Rack::Attack::Allow2Ban - def self.reset(discriminator, options) - findtime = options[:findtime] or raise ArgumentError, "Must pass findtime option" - - cache.reset_count("#{key_prefix}:count:#{discriminator}", findtime) - cache.delete("#{key_prefix}:ban:#{discriminator}") - end -end - -class Rack::Attack::Cache - def reset_count(unprefixed_key, period) - epoch_time = Time.now.to_i - # Add 1 to expires_in to avoid timing error: http://git.io/i1PHXA - expires_in = period - (epoch_time % period) + 1 - key = "#{(epoch_time / period).to_i}:#{unprefixed_key}" - delete(key) - end - - def delete(unprefixed_key) - store.delete("#{prefix}:#{unprefixed_key}") - end -end - -class Rack::Attack::StoreProxy::RedisStoreProxy - def delete(key, options={}) - self.del(key) - rescue Redis::BaseError - end -end diff --git a/spec/lib/gitlab/backend/grack_auth_spec.rb b/spec/lib/gitlab/backend/grack_auth_spec.rb index d0aad54f67..42c9946d2a 100644 --- a/spec/lib/gitlab/backend/grack_auth_spec.rb +++ b/spec/lib/gitlab/backend/grack_auth_spec.rb @@ -156,7 +156,7 @@ describe Grack::Auth do end expect(attempt_login(true)).to eq(200) - expect(Rack::Attack::Allow2Ban.send(:banned?, ip)).to eq(nil) + expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey for n in 0..maxretry do expect(attempt_login(false)).to eq(401) diff --git a/spec/lib/gitlab/backend/rack_attack_helpers_spec.rb b/spec/lib/gitlab/backend/rack_attack_helpers_spec.rb deleted file mode 100644 index 2ac496fd66..0000000000 --- a/spec/lib/gitlab/backend/rack_attack_helpers_spec.rb +++ /dev/null @@ -1,35 +0,0 @@ -require "spec_helper" - -describe 'RackAttackHelpers' do - describe 'reset' do - let(:discriminator) { 'test-key'} - let(:maxretry) { 5 } - let(:period) { 1.minute } - let(:options) { { findtime: period, bantime: 60, maxretry: maxretry } } - - def do_filter - for i in 1..maxretry - 1 do - status = Rack::Attack::Allow2Ban.filter(discriminator, options) { true } - expect(status).to eq(false) - end - end - - def do_reset - Rack::Attack::Allow2Ban.reset(discriminator, options) - end - - before do - do_reset - end - - after do - do_reset - end - - it 'user is not banned after n - 1 retries' do - do_filter - do_reset - do_filter - end - end -end From fdc9ae7ca2966ff3e2bc5942c89d1b38172bad94 Mon Sep 17 00:00:00 2001 From: karen Carias Date: Fri, 22 May 2015 15:57:29 -0700 Subject: [PATCH 062/255] picture for shortcuts --- doc/workflow/shortcuts.png | Bin 0 -> 78736 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 doc/workflow/shortcuts.png diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png new file mode 100644 index 0000000000000000000000000000000000000000..68756ed1f98dd474ada0b8da8d98840a25c3a123 GIT binary patch literal 78736 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 Dc9TY$ literal 0 HcmV?d00001 From c17a15db080563a8cf183776c724c55f5a74e213 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Fri, 22 May 2015 18:03:31 -0500 Subject: [PATCH 063/255] Added 7.11.2 to CHANGELOG --- CHANGELOG | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index edb7970c23..ddc8548f57 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,9 @@ v 7.12.0 (unreleased) - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) +v 7.11.2 + - no changes + v 7.11.1 - no changes From 920b547d63657bfd40b12cdff89de4c61848be7d Mon Sep 17 00:00:00 2001 From: Karen Carias Date: Fri, 22 May 2015 23:08:12 +0000 Subject: [PATCH 064/255] keyboard shortcuts in GitLab --- doc/workflow/shortcuts.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 doc/workflow/shortcuts.md diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md new file mode 100644 index 0000000000..226e86af4e --- /dev/null +++ b/doc/workflow/shortcuts.md @@ -0,0 +1,5 @@ +# GitLab keyboard shortcuts + +You can see GitLab's keyboard shortcuts by using 'shift + ?' + +!(shortcuts.png) \ No newline at end of file From dffc313bb5c990ff41d83571ba39a3d7d3dda5d6 Mon Sep 17 00:00:00 2001 From: Karen Carias Date: Fri, 22 May 2015 23:08:50 +0000 Subject: [PATCH 065/255] test1 image --- doc/workflow/shortcuts.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md index 226e86af4e..ffcb832cdd 100644 --- a/doc/workflow/shortcuts.md +++ b/doc/workflow/shortcuts.md @@ -2,4 +2,4 @@ You can see GitLab's keyboard shortcuts by using 'shift + ?' -!(shortcuts.png) \ No newline at end of file +![Shortcuts](shortcuts.png) \ No newline at end of file From 43a11f4b53881caa455290e903307fe48b307c62 Mon Sep 17 00:00:00 2001 From: Karen Carias Date: Fri, 22 May 2015 23:11:36 +0000 Subject: [PATCH 066/255] Added link to keyboard shortcuts --- doc/workflow/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/workflow/README.md b/doc/workflow/README.md index b90a6a50af..3f71abf45a 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -13,4 +13,5 @@ - [Project importing from GitLab.com to your private GitLab instance](import_projects_from_gitlab_com.md) - [Protected branches](protected_branches.md) - [Change your time zone](timezone.md) +- [Keyboard shortcuts](keyboard_shortcuts.md) - [Web Editor](web_editor.md) \ No newline at end of file From 0c140442038004935cbb06d4117ef86ad4eee910 Mon Sep 17 00:00:00 2001 From: Karen Carias Date: Fri, 22 May 2015 23:12:12 +0000 Subject: [PATCH 067/255] fixed link --- doc/workflow/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 3f71abf45a..0fca68f364 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -13,5 +13,5 @@ - [Project importing from GitLab.com to your private GitLab instance](import_projects_from_gitlab_com.md) - [Protected branches](protected_branches.md) - [Change your time zone](timezone.md) -- [Keyboard shortcuts](keyboard_shortcuts.md) +- [Keyboard shortcuts](shortcuts.md) - [Web Editor](web_editor.md) \ No newline at end of file From cfc9bff45e82f14c2f5a6653c4832f105b0ea365 Mon Sep 17 00:00:00 2001 From: Martins Polakovs Date: Sat, 23 May 2015 13:11:23 +0300 Subject: [PATCH 068/255] Fix upgrader script --- CHANGELOG | 1 + lib/gitlab/upgrader.rb | 11 ++++++++--- spec/lib/gitlab/upgrader_spec.rb | 15 +++++++++++++++ 3 files changed, 24 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d1ecfb4035..762f1bfe41 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 7.12.0 (unreleased) - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) - Use the user list from the target project in a merge request (Stan Hu) + - Fix upgrader script (Martins Polakovs) v 7.11.2 - no changes diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb index 0570c2fbeb..cf040971c6 100644 --- a/lib/gitlab/upgrader.rb +++ b/lib/gitlab/upgrader.rb @@ -43,10 +43,15 @@ module Gitlab end def latest_version_raw + git_tags = fetch_git_tags + git_tags = git_tags.select { |version| version =~ /v\d+\.\d+\.\d+\Z/ } + git_versions = git_tags.map { |tag| Gitlab::VersionInfo.parse(tag.match(/v\d+\.\d+\.\d+/).to_s) } + "v#{git_versions.sort.last.to_s}" + end + + def fetch_git_tags remote_tags, _ = Gitlab::Popen.popen(%W(git ls-remote --tags https://gitlab.com/gitlab-org/gitlab-ce.git)) - git_tags = remote_tags.split("\n").grep(/tags\/v#{current_version.major}/) - git_tags = git_tags.select { |version| version =~ /v\d\.\d\.\d\Z/ } - last_tag = git_tags.last.match(/v\d\.\d\.\d/).to_s + remote_tags.split("\n").grep(/tags\/v#{current_version.major}/) end def update_commands diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb index ce3ea6c260..8baa1662f3 100644 --- a/spec/lib/gitlab/upgrader_spec.rb +++ b/spec/lib/gitlab/upgrader_spec.rb @@ -20,5 +20,20 @@ describe Gitlab::Upgrader do upgrader.stub(current_version_raw: "5.3.0") expect(upgrader.latest_version_raw).to eq("v5.4.2") end + + it 'should get the latest version from tags' do + upgrader.stub(fetch_git_tags: [ + '6f0733310546402c15d3ae6128a95052f6c8ea96 refs/tags/v7.1.1', + 'facfec4b242ce151af224e20715d58e628aa5e74 refs/tags/v7.1.1^{}', + 'f7068d99c79cf79befbd388030c051bb4b5e86d4 refs/tags/v7.10.4', + '337225a4fcfa9674e2528cb6d41c46556bba9dfa refs/tags/v7.10.4^{}', + '880e0ba0adbed95d087f61a9a17515e518fc6440 refs/tags/v7.11.1', + '6584346b604f981f00af8011cd95472b2776d912 refs/tags/v7.11.1^{}', + '43af3e65a486a9237f29f56d96c3b3da59c24ae0 refs/tags/v7.11.2', + 'dac18e7728013a77410e926a1e64225703754a2d refs/tags/v7.11.2^{}', + '0bf21fd4b46c980c26fd8c90a14b86a4d90cc950 refs/tags/v7.9.4', + 'b10de29edbaff7219547dc506cb1468ee35065c3 refs/tags/v7.9.4^{}']) + expect(upgrader.latest_version_raw).to eq("v7.11.2") + end end end From e73ea12695c5b15c950a40adfceae83141124c20 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 22 May 2015 18:33:44 -0400 Subject: [PATCH 069/255] Add support for manually entering 2FA details --- app/assets/stylesheets/pages/profile.scss | 14 +++++++++++++ .../profiles/two_factor_auths_controller.rb | 3 ++- .../profiles/two_factor_auths/new.html.haml | 20 +++++++++++++++++-- .../two_factor_auths_controller_spec.rb | 7 +++++-- spec/factories.rb | 2 +- 5 files changed, 40 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 280e8b5717..5b528b38d3 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -93,3 +93,17 @@ } } } + +// Profile > Account > Two Factor Authentication +.two-factor-new { + .manual-instructions { + h3 { + margin-top: 0; + } + + // Slightly increase the size of the details so they're easier to read + dl { + font-size: 1.1em; + } + } +} diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb index 30ee689173..17abcea206 100644 --- a/app/controllers/profiles/two_factor_auths_controller.rb +++ b/app/controllers/profiles/two_factor_auths_controller.rb @@ -1,7 +1,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController def new unless current_user.otp_secret - current_user.otp_secret = User.generate_otp_secret + current_user.otp_secret = User.generate_otp_secret(16) current_user.save! end @@ -18,6 +18,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController else @error = 'Invalid pin code' @qr_code = build_qr_code + render 'new' end end diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml index fe03a259a1..b9f3e2380f 100644 --- a/app/views/profiles/two_factor_auths/new.html.haml +++ b/app/views/profiles/two_factor_auths/new.html.haml @@ -7,14 +7,30 @@ %hr -= form_tag profile_two_factor_auth_path, method: :post, class: 'form-horizontal' do |f| += form_tag profile_two_factor_auth_path, method: :post, class: 'form-horizontal two-factor-new' do |f| - if @error .alert.alert-danger = @error .form-group .col-sm-2 - .col-sm-10 + .col-sm-2 = raw @qr_code + .col-sm-8.manual-instructions + %h3 Can't scan the code? + + %p + To add the entry manually, provide the following details to the + application on your phone. + + %dl + %dt Account + %dd= current_user.email + %dl + %dt Key + %dd= current_user.otp_secret.scan(/.{4}/).join(' ') + %dl + %dt Time based + %dd Yes .form-group = label_tag :pin_code, nil, class: "control-label" .col-sm-10 diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb index f05d1f5fbe..b7e8583523 100644 --- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb +++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb @@ -11,8 +11,11 @@ describe Profiles::TwoFactorAuthsController do describe 'GET new' do let(:user) { create(:user) } - it 'generates otp_secret' do - expect { get :new }.to change { user.otp_secret } + it 'generates otp_secret for user' do + expect(User).to receive(:generate_otp_secret).with(16).and_return('secret').once + + get :new + get :new # Second hit shouldn't re-generate it end it 'assigns qr_code' do diff --git a/spec/factories.rb b/spec/factories.rb index 26e8a795fa..0f353b842f 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -31,7 +31,7 @@ FactoryGirl.define do trait :two_factor do before(:create) do |user| user.otp_required_for_login = true - user.otp_secret = User.generate_otp_secret + user.otp_secret = User.generate_otp_secret(16) end end From 7b879bb8bded3aa7577133a9bc2be0c7fc97d855 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 23 May 2015 18:47:53 -0400 Subject: [PATCH 070/255] Bump secret key length to 32 --- app/controllers/profiles/two_factor_auths_controller.rb | 2 +- spec/controllers/profiles/two_factor_auths_controller_spec.rb | 2 +- spec/factories.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb index 17abcea206..42579b3eb4 100644 --- a/app/controllers/profiles/two_factor_auths_controller.rb +++ b/app/controllers/profiles/two_factor_auths_controller.rb @@ -1,7 +1,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController def new unless current_user.otp_secret - current_user.otp_secret = User.generate_otp_secret(16) + current_user.otp_secret = User.generate_otp_secret(32) current_user.save! end diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb index b7e8583523..65415f21e5 100644 --- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb +++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb @@ -12,7 +12,7 @@ describe Profiles::TwoFactorAuthsController do let(:user) { create(:user) } it 'generates otp_secret for user' do - expect(User).to receive(:generate_otp_secret).with(16).and_return('secret').once + expect(User).to receive(:generate_otp_secret).with(32).and_return('secret').once get :new get :new # Second hit shouldn't re-generate it diff --git a/spec/factories.rb b/spec/factories.rb index 0f353b842f..e66ea3ce95 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -31,7 +31,7 @@ FactoryGirl.define do trait :two_factor do before(:create) do |user| user.otp_required_for_login = true - user.otp_secret = User.generate_otp_secret(16) + user.otp_secret = User.generate_otp_secret(32) end end From 310e08dc2cd743952ef9a6f3bf7300448147dc77 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 24 May 2015 09:00:00 -0400 Subject: [PATCH 071/255] Fix clone URL losing selection after a single click in Safari and Chrome Closes #9326 --- CHANGELOG | 1 + app/assets/javascripts/application.js.coffee | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index d1ecfb4035..45e2ee0eb7 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 7.12.0 (unreleased) + - Fix clone URL losing selection after a single click in Safari and Chrome (Stan Hu) - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) - Disabled expansion of top/bottom blobs for new file diffs - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index caf18c0d86..ea2a4b9710 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -116,7 +116,10 @@ window.addEventListener "hashchange", shiftWindow $ -> # Click a .js-select-on-focus field, select the contents - $(".js-select-on-focus").on "focusin", -> $(this).select() + $(".js-select-on-focus").on "focusin", -> + # Prevent a mouseup event from deselecting the input + $(this).select().one 'mouseup', (e) -> + e.preventDefault() $('.remove-row').bind 'ajax:success', -> $(this).closest('li').fadeOut() From 608bd4bb4cd8a05e6506918c428f8933cd9920de Mon Sep 17 00:00:00 2001 From: Robert Schilling Date: Mon, 25 May 2015 10:15:36 +0000 Subject: [PATCH 072/255] Let's start 7.12 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index e85691e6ff..5f0902c7c6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.11.0.pre +7.12.0.pre \ No newline at end of file From a7d8a7bd3d4b6f7b388d8976201cbdf3b36ab7aa Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 22 May 2015 06:17:37 -0400 Subject: [PATCH 073/255] Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings Closes #1676 --- CHANGELOG | 1 + app/models/ability.rb | 27 ++++++++++++++ app/views/projects/_dropdown.html.haml | 37 -------------------- app/views/projects/milestones/show.html.haml | 2 +- features/project/project.feature | 6 ++++ features/steps/project/project.rb | 8 +++++ features/steps/shared/project.rb | 6 ++++ 7 files changed, 49 insertions(+), 38 deletions(-) delete mode 100644 app/views/projects/_dropdown.html.haml diff --git a/CHANGELOG b/CHANGELOG index d1ecfb4035..c78e7f9b62 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 7.12.0 (unreleased) + - Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu) - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) - Disabled expansion of top/bottom blobs for new file diffs - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) diff --git a/app/models/ability.rb b/app/models/ability.rb index 85a15596f8..04d9dccf91 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -101,6 +101,22 @@ class Ability rules -= project_archived_rules end + unless project.issues_enabled + rules -= named_abilities('issue') + end + + unless project.merge_requests_enabled + rules -= named_abilities('merge_request') + end + + unless project.snippets_enabled + rules -= named_abilities('snippet') + end + + unless project.wiki_enabled + rules -= named_abilities('wiki') + end + rules end end @@ -272,5 +288,16 @@ class Ability abilities end end + + private + + def named_abilities(name) + [ + :"read_#{name}", + :"write_#{name}", + :"modify_#{name}", + :"admin_#{name}" + ] + end end end diff --git a/app/views/projects/_dropdown.html.haml b/app/views/projects/_dropdown.html.haml deleted file mode 100644 index d623a3716e..0000000000 --- a/app/views/projects/_dropdown.html.haml +++ /dev/null @@ -1,37 +0,0 @@ -- if current_user - .dropdown.pull-right - %a.dropdown-toggle.btn.btn-sm{href: '#', "data-toggle" => "dropdown"} - %i.fa.fa-bars - %ul.dropdown-menu - - if @project.issues_enabled && can?(current_user, :write_issue, @project) - %li - = link_to url_for_new_issue(@project, only_path: true), title: "New Issue" do - %i.fa.fa-fw.fa-exclamation-circle - New issue - - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) - %li - = link_to new_namespace_project_merge_request_path(@project.namespace, @project), title: "New Merge Request" do - %i.fa.fa-fw.fa-tasks - New merge request - - if @project.snippets_enabled && can?(current_user, :write_snippet, @project) - %li - = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do - %i.fa.fa-fw.fa-file-text-o - New snippet - - if can?(current_user, :admin_project_member, @project) - %li - = link_to namespace_project_project_members_path(@project.namespace, @project), title: "New project member" do - %i.fa.fa-fw.fa-users - New project member - - if can? current_user, :push_code, @project - %li.divider - %li - = link_to new_namespace_project_branch_path(@project.namespace, @project) do - %i.fa.fa-fw.fa-code-fork - New branch - %li - = link_to new_namespace_project_tag_path(@project.namespace, @project) do - %i.fa.fa-fw.fa-tag - New tag - - diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index bba2b8764a..22172a3128 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -61,7 +61,7 @@ Participants %span.badge= @users.count - - if @project.issues_enabled + - if can?(current_user, :write_issue, @project) .pull-right = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do %i.fa.fa-plus diff --git a/features/project/project.feature b/features/project/project.feature index ae28312a69..ef11bceed1 100644 --- a/features/project/project.feature +++ b/features/project/project.feature @@ -62,3 +62,9 @@ Feature: Project And I add project tags And I save project Then I should see project tags + + Scenario: I should not see "New Issue" or "New Merge Request" buttons + Given I disable issues and merge requests in project + When I visit project "Shop" page + Then I should not see "New Issue" button + And I should not see "New Merge Request" button diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 00706ab30e..93fea693f8 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -102,4 +102,12 @@ class Spinach::Features::Project < Spinach::FeatureSteps step 'I should see project tags' do expect(find_field('Tags').value).to eq 'tag1, tag2' end + + step 'I should not see "New Issue" button' do + page.should_not have_link 'New Issue' + end + + step 'I should not see "New Merge Request" button' do + page.should_not have_link 'New Merge Request' + end end diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index b60ac5e342..24136fe421 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -14,6 +14,12 @@ module SharedProject @project.team << [@user, :master] end + step 'I disable issues and merge requests in project' do + @project.issues_enabled = false + @project.merge_requests_enabled = false + @project.save + end + # Add another user to project "Shop" step 'I add a user to project "Shop"' do @project = Project.find_by(name: "Shop") From 2fa5c7513e77ecf990cdcf2d4fe7ecad82d8d7c9 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 25 May 2015 15:43:22 +0200 Subject: [PATCH 074/255] Group project contributions by both name and email. --- CHANGELOG | 1 + .../stat_graph_contributors_util.js.coffee | 20 ++++++++++++------- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9e4033fcc3..f4f1c7603d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ v 7.12.0 (unreleased) - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) - Use the user list from the target project in a merge request (Stan Hu) + - Group project contributions by both name and email. v 7.11.2 - no changes diff --git a/app/assets/javascripts/stat_graph_contributors_util.js.coffee b/app/assets/javascripts/stat_graph_contributors_util.js.coffee index 1670f5c7bc..cfe5508290 100644 --- a/app/assets/javascripts/stat_graph_contributors_util.js.coffee +++ b/app/assets/javascripts/stat_graph_contributors_util.js.coffee @@ -2,11 +2,15 @@ window.ContributorsStatGraphUtil = parse_log: (log) -> total = {} by_author = {} + by_email = {} for entry in log @add_date(entry.date, total) unless total[entry.date]? - @add_author(entry, by_author) unless by_author[entry.author_name]? - @add_date(entry.date, by_author[entry.author_name]) unless by_author[entry.author_name][entry.date] - @store_data(entry, total[entry.date], by_author[entry.author_name][entry.date]) + + data = by_author[entry.author_name] #|| by_email[entry.author_email] + data ?= @add_author(entry, by_author, by_email) + + @add_date(entry.date, data) unless data[entry.date] + @store_data(entry, total[entry.date], data[entry.date]) total = _.toArray(total) by_author = _.toArray(by_author) total: total, by_author: by_author @@ -15,10 +19,12 @@ window.ContributorsStatGraphUtil = collection[date] = {} collection[date].date = date - add_author: (author, by_author) -> - by_author[author.author_name] = {} - by_author[author.author_name].author_name = author.author_name - by_author[author.author_name].author_email = author.author_email + add_author: (author, by_author, by_email) -> + data = {} + data.author_name = author.author_name + data.author_email = author.author_email + by_author[author.author_name] = data + by_email[author.author_email] = data store_data: (entry, total, by_author) -> @store_commits(total, by_author) From a9e2686139fa4c7d5cd5f567854dc95627cc26a7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 25 May 2015 16:31:20 +0200 Subject: [PATCH 075/255] Update specs. --- spec/javascripts/stat_graph_contributors_util_spec.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/javascripts/stat_graph_contributors_util_spec.js b/spec/javascripts/stat_graph_contributors_util_spec.js index ee90892eb4..dbafe782b7 100644 --- a/spec/javascripts/stat_graph_contributors_util_spec.js +++ b/spec/javascripts/stat_graph_contributors_util_spec.js @@ -118,9 +118,11 @@ describe("ContributorsStatGraphUtil", function () { describe("#add_author", function () { it("adds an author field to the collection", function () { var fake_author = { author_name: "Author", author_email: 'fake@email.com' } - var fake_collection = {} - ContributorsStatGraphUtil.add_author(fake_author, fake_collection) - expect(fake_collection[fake_author.author_name].author_name).toEqual("Author") + var fake_author_collection = {} + var fake_email_collection = {} + ContributorsStatGraphUtil.add_author(fake_author, fake_author_collection, fake_email_collection) + expect(fake_author_collection[fake_author.author_name].author_name).toEqual("Author") + expect(fake_email_collection[fake_author.author_email].author_name).toEqual("Author") }) }) From 59f0d91a20663cc6ddd82f716606bb2216a1e20a Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 25 May 2015 16:35:37 +0200 Subject: [PATCH 076/255] Prefix EmailsOnPush email subject with `[Git]`. --- CHANGELOG | 1 + app/mailers/emails/projects.rb | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 9e4033fcc3..9fddef2a31 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ v 7.12.0 (unreleased) - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) - Use the user list from the target project in a merge request (Stan Hu) + - Prefix EmailsOnPush email subject with `[Git]`. v 7.11.2 - no changes diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 9cb7077e59..4a6e18e6a7 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -93,7 +93,8 @@ module Emails "pushed to" end - @subject = "[#{@project.path_with_namespace}]" + @subject = "[Git]" + @subject << "[#{@project.path_with_namespace}]" @subject << "[#{@ref_name}]" if action == :push @subject << " " From ebe9c89082d1536d3f4c7e2071692277e7957d57 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 25 May 2015 17:00:27 +0200 Subject: [PATCH 077/255] Consistently refer to MRs as either Accepted or Rejected. --- CHANGELOG | 1 + app/models/merge_request.rb | 2 +- app/views/projects/merge_requests/_merge_request.html.haml | 6 +++--- app/views/projects/merge_requests/show/_mr_title.html.haml | 4 ++-- .../projects/merge_requests/show/_state_widget.html.haml | 4 ++-- app/views/projects/milestones/show.html.haml | 4 ++-- app/views/search/results/_merge_request.html.haml | 4 ++-- 7 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 9e4033fcc3..d2110c1256 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -7,6 +7,7 @@ v 7.12.0 (unreleased) - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) - Use the user list from the target project in a merge request (Stan Hu) + - Consistently refer to MRs as either Accepted or Rejected. v 7.11.2 - no changes diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 64f3c39f13..d164076730 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -133,7 +133,7 @@ class MergeRequest < ActiveRecord::Base # Closed scope for merge request should return # both merged and closed mr's scope :closed, -> { with_states(:closed, :merged) } - scope :declined, -> { with_states(:closed) } + scope :rejected, -> { with_states(:closed) } def validate_branches if target_project == source_project && target_branch == source_branch diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 5d5a23b540..534f20ce54 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -10,11 +10,11 @@ - if merge_request.merged? %span %i.fa.fa-check - MERGED + ACCEPTED - elsif merge_request.closed? %span - %i.fa.fa-close - CLOSED + %i.fa.fa-ban + REJECTED - else %span.hidden-xs.hidden-sm %span.label-branch< 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 46e92a9c55..0690fdb769 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -1,9 +1,9 @@ %h4.page-title .issue-box{ class: issue_box_class(@merge_request) } - if @merge_request.merged? - Merged + Accepted - elsif @merge_request.closed? - Closed + Rejected - else Open = "Merge Request ##{@merge_request.iid}" diff --git a/app/views/projects/merge_requests/show/_state_widget.html.haml b/app/views/projects/merge_requests/show/_state_widget.html.haml index 44bd9347f5..e4c71bfc1b 100644 --- a/app/views/projects/merge_requests/show/_state_widget.html.haml +++ b/app/views/projects/merge_requests/show/_state_widget.html.haml @@ -11,7 +11,7 @@ - if @merge_request.closed? %h4 - Closed + Rejected - if @merge_request.closed_event by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)} #{time_ago_with_tooltip(@merge_request.closed_event.created_at)} @@ -19,7 +19,7 @@ - if @merge_request.merged? %h4 - Merged + Accepted - if @merge_request.merge_event by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)} #{time_ago_with_tooltip(@merge_request.merge_event.created_at)} diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index bba2b8764a..0581c3a1b4 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -85,10 +85,10 @@ .col-md-3 = render('merge_requests', title: 'Waiting for merge (open and assigned)', merge_requests: @merge_requests.opened.assigned, id: 'ongoing') .col-md-3 - = render('merge_requests', title: 'Declined (closed)', merge_requests: @merge_requests.declined, id: 'closed') + = render('merge_requests', title: 'Rejected (closed)', merge_requests: @merge_requests.rejected, id: 'closed') .col-md-3 .panel.panel-primary - .panel-heading Merged + .panel-heading Accepted %ul.well-list - @merge_requests.merged.each do |merge_request| = render 'merge_request', merge_request: merge_request diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml index 2efa616d66..adfdd1c750 100644 --- a/app/views/search/results/_merge_request.html.haml +++ b/app/views/search/results/_merge_request.html.haml @@ -11,6 +11,6 @@ #{merge_request.project.name_with_namespace} .pull-right - if merge_request.merged? - %span.label.label-primary Merged + %span.label.label-primary Accepted - elsif merge_request.closed? - %span.label.label-danger Closed + %span.label.label-danger Rejected From d25026a512cd0f8137ef6685a23a1d2ce898ffa7 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 25 May 2015 17:01:27 +0200 Subject: [PATCH 078/255] Add Accepted and Rejected tabs to MR lists. --- CHANGELOG | 1 + app/finders/issuable_finder.rb | 4 +++ app/helpers/application_helper.rb | 7 +++++- app/views/dashboard/issues.html.haml | 2 +- app/views/dashboard/merge_requests.html.haml | 2 +- app/views/groups/issues.html.haml | 2 +- app/views/groups/merge_requests.html.haml | 2 +- app/views/projects/issues/index.html.haml | 2 +- .../projects/merge_requests/index.html.haml | 2 +- app/views/shared/_issuable_filter.html.haml | 25 ++++++++++++++----- 10 files changed, 36 insertions(+), 13 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d2110c1256..793ecb5e59 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 7.12.0 (unreleased) - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) - Use the user list from the target project in a merge request (Stan Hu) - Consistently refer to MRs as either Accepted or Rejected. + - Add Accepted and Rejected tabs to MR lists. v 7.11.2 - no changes diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index b8f367c633..e658e14115 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -75,6 +75,10 @@ class IssuableFinder case params[:state] when 'closed' items.closed + when 'rejected' + items.respond_to?(:rejected) ? items.rejected : items.closed + when 'merged' + items.respond_to?(:merged) ? items.merged : items.closed when 'all' items when 'opened' diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index bcd400b7e7..89dcdf5779 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -330,7 +330,12 @@ module ApplicationHelper end def state_filters_text_for(entity, project) - entity_title = entity.to_s.humanize + titles = { + opened: "Open", + merged: "Accepted" + } + + entity_title = titles[entity] || entity.to_s.humanize count = if project.nil? diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index dfdf0d68c8..0dd2edbb1b 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -17,5 +17,5 @@ = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do %i.fa.fa-rss - = render 'shared/issuable_filter' + = render 'shared/issuable_filter', type: :issues = render 'shared/issues' diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index a7e1b08a0a..61d2fbe538 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -7,5 +7,5 @@ List all merge requests from all projects you have access to. %hr .append-bottom-20 - = render 'shared/issuable_filter' + = render 'shared/issuable_filter', type: :merge_requests = render 'shared/merge_requests' diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 6a3da6adac..e0756e909b 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -21,5 +21,5 @@ = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do %i.fa.fa-rss - = render 'shared/issuable_filter' + = render 'shared/issuable_filter', type: :issues = render 'shared/issues' diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index 268f33d576..3d9e857cc5 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -10,5 +10,5 @@ To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. %hr .append-bottom-20 - = render 'shared/issuable_filter' + = render 'shared/issuable_filter', type: :merge_requests = render 'shared/merge_requests' diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 709ea1f789..a378b37f4a 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -18,7 +18,7 @@ %i.fa.fa-plus New Issue - = render 'shared/issuable_filter' + = render 'shared/issuable_filter', type: :issues .issues-holder = render "issues" diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index ab845a7e71..841d1e1cfe 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -7,6 +7,6 @@ = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-new pull-left", title: "New Merge Request" do %i.fa.fa-plus New Merge Request - = render 'shared/issuable_filter' + = render 'shared/issuable_filter', type: :merge_requests .merge-requests-holder = render 'merge_requests' diff --git a/app/views/shared/_issuable_filter.html.haml b/app/views/shared/_issuable_filter.html.haml index 4ab9421f01..a5187fa4ea 100644 --- a/app/views/shared/_issuable_filter.html.haml +++ b/app/views/shared/_issuable_filter.html.haml @@ -3,15 +3,28 @@ %ul.nav.nav-tabs %li{class: ("active" if params[:state] == 'opened')} = link_to page_filter_path(state: 'opened') do - %i.fa.fa-exclamation-circle + = icon('exclamation-circle') #{state_filters_text_for(:opened, @project)} - %li{class: ("active" if params[:state] == 'closed')} - = link_to page_filter_path(state: 'closed') do - %i.fa.fa-check-circle - #{state_filters_text_for(:closed, @project)} + + - if defined?(type) && type == :merge_requests + %li{class: ("active" if params[:state] == 'merged')} + = link_to page_filter_path(state: 'merged') do + = icon('check-circle') + #{state_filters_text_for(:merged, @project)} + + %li{class: ("active" if params[:state] == 'rejected')} + = link_to page_filter_path(state: 'rejected') do + = icon('ban') + #{state_filters_text_for(:rejected, @project)} + - else + %li{class: ("active" if params[:state] == 'closed')} + = link_to page_filter_path(state: 'closed') do + = icon('check-circle') + #{state_filters_text_for(:closed, @project)} + %li{class: ("active" if params[:state] == 'all')} = link_to page_filter_path(state: 'all') do - %i.fa.fa-compass + = icon('compass') #{state_filters_text_for(:all, @project)} .issues-details-filters From 0c8d8b91e0a9f39c67c0f10c73cd9a0a08913a54 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 25 May 2015 17:18:24 +0200 Subject: [PATCH 079/255] Clarify navigation labels for Project Settings and Group Settings. --- CHANGELOG | 1 + app/views/layouts/nav/_group.html.haml | 2 +- app/views/layouts/nav/_project_settings.html.haml | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ed9ffefb67..31691cd731 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 7.12.0 (unreleased) - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) - Use the user list from the target project in a merge request (Stan Hu) + - Clarify navigation labels for Project Settings and Group Settings. v 7.11.2 - no changes diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 62f0579d48..9f1654b25b 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -44,7 +44,7 @@ = link_to edit_group_path(@group), title: 'Group', data: {placement: 'right'} do = icon('pencil-square-o') %span - Group + Group Settings = nav_link(path: 'groups#projects') do = link_to projects_group_path(@group), title: 'Projects', data: {placement: 'right'} do = icon('folder') diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml index 21260302a0..7dd14449de 100644 --- a/app/views/layouts/nav/_project_settings.html.haml +++ b/app/views/layouts/nav/_project_settings.html.haml @@ -12,7 +12,7 @@ = link_to edit_project_path(@project), title: 'Project', class: 'stat-tab tab', data: {placement: 'right'} do = icon('pencil-square-o') %span - Project + Project Settings = nav_link(controller: [:project_members, :teams]) do = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab', data: {placement: 'right'} do = icon('users') From 7df45882a93220c6250294fb653f5e64464ebb64 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 25 May 2015 17:36:17 +0200 Subject: [PATCH 080/255] Update specs. --- features/project/merge_requests.feature | 4 ++-- features/steps/project/merge_requests.rb | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 60caf783fe..7a83190160 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -10,8 +10,8 @@ Feature: Project Merge Requests Then I should see "Bug NS-04" in merge requests And I should not see "Feature NS-03" in merge requests - Scenario: I should see closed merge requests - Given I click link "Closed" + Scenario: I should see rejected merge requests + Given I click link "Rejected" Then I should see "Feature NS-03" in merge requests And I should not see "Bug NS-04" in merge requests diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index f67e6e3d8c..92de94a75d 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -19,8 +19,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps click_link "All" end - step 'I click link "Closed"' do - click_link "Closed" + step 'I click link "Rejected"' do + click_link "Rejected" end step 'I should see merge request "Wiki Feature"' do @@ -32,7 +32,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I should see closed merge request "Bug NS-04"' do merge_request = MergeRequest.find_by!(title: "Bug NS-04") merge_request.closed?.should be_true - page.should have_content "Closed by" + page.should have_content "Rejected by" end step 'I should see merge request "Bug NS-04"' do @@ -202,7 +202,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps step 'I should see merged request' do within '.issue-box' do - page.should have_content "Merged" + page.should have_content "Accepted" end end From 2651c8a9aaa27e3b66a820469cd5dafd4aec39d3 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Mon, 25 May 2015 17:27:02 +0000 Subject: [PATCH 081/255] Make clear that it are database migrations --- CONTRIBUTING.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 895202b58e..949493a930 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -160,8 +160,8 @@ If you add a dependency in GitLab (such as an operating system package) please c 1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style#coffeescript) 1. [Shell commands](doc/development/shell_commands.md) created by GitLab contributors to enhance security 1. [Markdown](http://www.cirosantilli.com/markdown-styleguide) +1. [Database Migrations](doc/development/migration_style_guide.md) 1. Interface text should be written subjectively instead of objectively. It should be the gitlab core team addressing a person. It should be written in present time and never use past tense (has been/was). For example instead of "prohibited this user from being saved due to the following errors:" the text should be "sorry, we could not create your account because:". Also these [excellent writing guidelines](https://github.com/NARKOZ/guides#writing). -1. [Migrations](doc/development/migration_style_guide.md) This is also the style used by linting tools such as [RuboCop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com). @@ -177,4 +177,4 @@ Project maintainers have the right and responsibility to remove, edit, or reject Instances of abusive, harassing, or otherwise unacceptable behavior can be reported by emailing contact@gitlab.com -This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) +This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) \ No newline at end of file From 01c4683473abf60fa5a982627e93820655e11b1b Mon Sep 17 00:00:00 2001 From: Karen Carias Date: Mon, 25 May 2015 19:56:11 +0000 Subject: [PATCH 082/255] small fix --- doc/workflow/timezone.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/workflow/timezone.md b/doc/workflow/timezone.md index b513088dbc..8540ccfcab 100644 --- a/doc/workflow/timezone.md +++ b/doc/workflow/timezone.md @@ -1,6 +1,6 @@ # Changing your time zone -GitLab defaults its time zone to UTC. It has a global timezone configuration parameter in config/application.rb. +GitLab defaults its time zone to UTC. It has a global timezone configuration parameter in /etc/gitlab/gitlab.rb To update, add the time zone that best applies to your location. Here are two examples: ``` From b263a33c80a04be4b126c203f0c0d1439f1d5739 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 25 May 2015 16:06:34 -0400 Subject: [PATCH 083/255] Better handle label references that aren't actually references Fixes #1690 --- lib/gitlab/markdown/label_reference_filter.rb | 6 +++--- spec/lib/gitlab/markdown/label_reference_filter_spec.rb | 7 +++++++ 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb index a357f28458..1a77becee8 100644 --- a/lib/gitlab/markdown/label_reference_filter.rb +++ b/lib/gitlab/markdown/label_reference_filter.rb @@ -84,11 +84,11 @@ module Gitlab # # Returns a Hash. def label_params(id, name) - if id > 0 - { id: id } - else + if name # TODO (rspeicher): Don't strip single quotes if we decide to only use double quotes for surrounding. { name: name.tr('\'"', '') } + else + { id: id } end end end diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb index 9f89883746..c4548e7431 100644 --- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb @@ -149,5 +149,12 @@ module Gitlab::Markdown end end end + + describe 'edge cases' do + it 'gracefully handles non-references matching the pattern' do + exp = act = '(format nil "~0f" 3.0) ; 3.0' + expect(filter(act).to_html).to eq exp + end + end end end From fdde284c97b2563b097c0cc903d99aaaface9604 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 25 May 2015 16:22:46 -0400 Subject: [PATCH 084/255] Document expanded autolinking in doc/markdown/markdown.md --- doc/markdown/markdown.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index 30c29084e3..9c7f723c06 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -66,16 +66,26 @@ It is not reasonable to italicize just _part_ of a word, especially when you're perform_complicated_task do_this_and_do_that_and_another_thing -perform_complicated_task +perform_complicated_task do_this_and_do_that_and_another_thing ## URL auto-linking -GFM will autolink standard URLs you copy and paste into your text. So if you want to link to a URL (instead of a textural link), you can simply put the URL in verbatim and it will be turned into a link to that URL. +GFM will autolink almost any URL you copy and paste into your text. - http://www.google.com + * http://www.google.com + * https://google.com/ + * ftp://ftp.us.debian.org/debian/ + * smb://foo/bar/baz + * irc://irc.freenode.net/gitlab + * http://localhost:3000 -http://www.google.com +* http://www.google.com +* https://google.com/ +* ftp://ftp.us.debian.org/debian/ +* smb://foo/bar/baz +* irc://irc.freenode.net/gitlab +* http://localhost:3000 ## Code and Syntax Highlighting From dfa1d96a1fa372b292c83424f62ae10d2c053fbd Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Mon, 25 May 2015 23:00:23 +0000 Subject: [PATCH 085/255] Update reason for the merge window. --- CONTRIBUTING.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 949493a930..6b4a6102af 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -86,7 +86,9 @@ If you can, please submit a merge request with the fix or improvements including 1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines]( doc/development/shell_commands.md). 1. Also have a look at the [shell command guidelines](doc/development/shell_commands.md) if your code reads or opens files, or handles paths to files on disk. -The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab B.V. team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features. +The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. +Before this time the GitLab B.V. team is still dealing with work that is created by the monthly release such as regressions requiring patch releases. +After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features. Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a minimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it. From 9bcd36396b9b71467f66dd4ed79ab709bb5d027a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 25 May 2015 10:42:41 -0400 Subject: [PATCH 086/255] Refactor permission checks to use `can?` instead of `issues_enabled` and `merge_requests_enabled` --- CHANGELOG | 1 + app/helpers/projects_helper.rb | 16 ++++++++++++---- app/models/ability.rb | 5 +++++ app/views/projects/milestones/show.html.haml | 5 +++-- 4 files changed, 21 insertions(+), 6 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ed9ffefb67..d1ba75a0dc 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 7.12.0 (unreleased) + - Refactor permission checks with issues and merge requests project settings (Stan Hu) - Add web hook support for note events (Stan Hu) - Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu) - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 96d2606f1a..f8df39d236 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -148,7 +148,7 @@ module ProjectsHelper nav_tabs << [:files, :commits, :network, :graphs] end - if project.repo_exists? && project.merge_requests_enabled + if project.repo_exists? && can?(current_user, :read_merge_request, project) nav_tabs << :merge_requests end @@ -156,11 +156,19 @@ module ProjectsHelper nav_tabs << :settings end - [:issues, :wiki, :snippets].each do |feature| - nav_tabs << feature if project.send :"#{feature}_enabled" + if can?(current_user, :read_issue, project) + nav_tabs << :issues end - if project.issues_enabled || project.merge_requests_enabled + if can?(current_user, :read_wiki, project) + nav_tabs << :wiki + end + + if can?(current_user, :read_project_snippet, project) + nav_tabs << :snippets + end + + if can?(current_user, :read_milestone, project) nav_tabs << [:milestones, :labels] end diff --git a/app/models/ability.rb b/app/models/ability.rb index 04d9dccf91..e166b4197f 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -109,6 +109,11 @@ class Ability rules -= named_abilities('merge_request') end + unless project.issues_enabled or project.merge_requests_enabled + rules -= named_abilities('label') + rules -= named_abilities('milestone') + end + unless project.snippets_enabled rules -= named_abilities('snippet') end diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 22172a3128..5845fd744f 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -61,11 +61,12 @@ Participants %span.badge= @users.count - - if can?(current_user, :write_issue, @project) - .pull-right + .pull-right + - if can?(current_user, :write_issue, @project) = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do %i.fa.fa-plus New Issue + - if can?(current_user, :read_issue, @project) = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link btn-grouped" .tab-content From f1ae6807eb1828ed3b3d2335b0b49bb507b761ea Mon Sep 17 00:00:00 2001 From: Karen Date: Tue, 26 May 2015 00:18:07 +0000 Subject: [PATCH 087/255] created a documentation style guide --- doc_styleguide.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 doc_styleguide.md diff --git a/doc_styleguide.md b/doc_styleguide.md new file mode 100644 index 0000000000..c1ad7a1520 --- /dev/null +++ b/doc_styleguide.md @@ -0,0 +1,29 @@ +# Documentation styleguide + +This styleguide recommends best practices to improve documentation and to keep it organized and easy to find. + +## Text (when using markdown) + +* Make sure that the documentation is added in the correct directory and that there's a link to it somewhere useful. + +* Add only one H1 or title, by adding '#' at the begining of it. + +* For subtitles, use '##', '###' and so on. + +* Do not duplicate information. + +* Be brief and clear. + +* To add images use +´´´ +!['NAME OF LINK']('WHERE THE LINK IS LOCATED') +´´´ + + +## When adding images to a document + +* Create a directory to store the images with the specific name of the document where the images belong. It could be in the same directory where the .md document that you're working on is located. + +* Images should have a specific, non-generic name that will differentiate them. + +* Keep all file names in lower case. From cbeef2f5a86b2129cdc4f4ac4614ce9f133a5c59 Mon Sep 17 00:00:00 2001 From: Karen Date: Tue, 26 May 2015 00:18:54 +0000 Subject: [PATCH 088/255] fixed info box --- doc_styleguide.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/doc_styleguide.md b/doc_styleguide.md index c1ad7a1520..d4d815bd20 100644 --- a/doc_styleguide.md +++ b/doc_styleguide.md @@ -16,7 +16,7 @@ This styleguide recommends best practices to improve documentation and to keep i * To add images use ´´´ -!['NAME OF LINK']('WHERE THE LINK IS LOCATED') +'!['NAME OF LINK']('WHERE THE LINK IS LOCATED')' ´´´ @@ -26,4 +26,4 @@ This styleguide recommends best practices to improve documentation and to keep i * Images should have a specific, non-generic name that will differentiate them. -* Keep all file names in lower case. +* Keep all file names in lower case. \ No newline at end of file From 4034af81314d650c78e0bb6b40c99b6eb20c4eb2 Mon Sep 17 00:00:00 2001 From: Karen Date: Tue, 26 May 2015 00:20:18 +0000 Subject: [PATCH 089/255] removed unnecessary information --- doc_styleguide.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/doc_styleguide.md b/doc_styleguide.md index d4d815bd20..f047593f10 100644 --- a/doc_styleguide.md +++ b/doc_styleguide.md @@ -14,11 +14,6 @@ This styleguide recommends best practices to improve documentation and to keep i * Be brief and clear. -* To add images use -´´´ -'!['NAME OF LINK']('WHERE THE LINK IS LOCATED')' -´´´ - ## When adding images to a document From 5314d6ff88ac3cd6ebf49a0a94950d8697275762 Mon Sep 17 00:00:00 2001 From: Karen Date: Tue, 26 May 2015 00:23:25 +0000 Subject: [PATCH 090/255] added link to documentation style guide --- CONTRIBUTING.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 6b4a6102af..c704f5ec61 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -163,6 +163,7 @@ If you add a dependency in GitLab (such as an operating system package) please c 1. [Shell commands](doc/development/shell_commands.md) created by GitLab contributors to enhance security 1. [Markdown](http://www.cirosantilli.com/markdown-styleguide) 1. [Database Migrations](doc/development/migration_style_guide.md) +1. [Documentation styleguide](gitlab-ce/doc_styleguide.md) 1. Interface text should be written subjectively instead of objectively. It should be the gitlab core team addressing a person. It should be written in present time and never use past tense (has been/was). For example instead of "prohibited this user from being saved due to the following errors:" the text should be "sorry, we could not create your account because:". Also these [excellent writing guidelines](https://github.com/NARKOZ/guides#writing). This is also the style used by linting tools such as [RuboCop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com). From ef32c60a5417f5379a9af4a693dc4d31196cbe52 Mon Sep 17 00:00:00 2001 From: Karen Date: Tue, 26 May 2015 00:24:40 +0000 Subject: [PATCH 091/255] fixed link --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c704f5ec61..8059b95609 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -163,7 +163,7 @@ If you add a dependency in GitLab (such as an operating system package) please c 1. [Shell commands](doc/development/shell_commands.md) created by GitLab contributors to enhance security 1. [Markdown](http://www.cirosantilli.com/markdown-styleguide) 1. [Database Migrations](doc/development/migration_style_guide.md) -1. [Documentation styleguide](gitlab-ce/doc_styleguide.md) +1. [Documentation styleguide](doc_styleguide.md) 1. Interface text should be written subjectively instead of objectively. It should be the gitlab core team addressing a person. It should be written in present time and never use past tense (has been/was). For example instead of "prohibited this user from being saved due to the following errors:" the text should be "sorry, we could not create your account because:". Also these [excellent writing guidelines](https://github.com/NARKOZ/guides#writing). This is also the style used by linting tools such as [RuboCop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com). From 4e264076797316e0ae241f6e0c9bc18ad176587f Mon Sep 17 00:00:00 2001 From: Karen Date: Tue, 26 May 2015 00:26:57 +0000 Subject: [PATCH 092/255] fixed info --- doc_styleguide.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/doc_styleguide.md b/doc_styleguide.md index f047593f10..670af765f3 100644 --- a/doc_styleguide.md +++ b/doc_styleguide.md @@ -2,13 +2,11 @@ This styleguide recommends best practices to improve documentation and to keep it organized and easy to find. -## Text (when using markdown) +## Text * Make sure that the documentation is added in the correct directory and that there's a link to it somewhere useful. -* Add only one H1 or title, by adding '#' at the begining of it. - -* For subtitles, use '##', '###' and so on. +* Add only one H1 or title in each document, by adding '#' at the begining of it (when using markdown). For subtitles, use '##', '###' and so on. * Do not duplicate information. From 2b4eb7869edec4db1eb4022800899c7beb0b81ce Mon Sep 17 00:00:00 2001 From: Karen Carias Date: Tue, 26 May 2015 01:18:24 +0000 Subject: [PATCH 093/255] Explained info better --- doc/raketasks/backup_restore.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 2c858ed780..ae2d465e0c 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -301,4 +301,5 @@ If you are running GitLab on a virtualized server you can possibly also create V It is not uncommon however for a VM snapshot to require you to power down the server, so this approach is probably of limited practical use. ### Note -This documentation is for GitLab CE. Users can't create backups for gitlab.com. \ No newline at end of file +This documentation is for GitLab CE. +We backup GitLab.com and make sure your data is secure, but you can't use these methods to export / backup your data yourself from GitLab.com. \ No newline at end of file From 710627fbd89560f40fb37e25f474859a3914a381 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 25 May 2015 20:27:20 -0700 Subject: [PATCH 094/255] Fix Zen Mode not closing with ESC key Closes #1025 --- CHANGELOG | 1 + app/assets/javascripts/zen_mode.js.coffee | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ed9ffefb67..f8a748c940 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 7.12.0 (unreleased) + - Fix Zen Mode not closing with ESC key (Stan Hu) - Add web hook support for note events (Stan Hu) - Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu) - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) diff --git a/app/assets/javascripts/zen_mode.js.coffee b/app/assets/javascripts/zen_mode.js.coffee index 0fb8f7ed75..e2516b4ae7 100644 --- a/app/assets/javascripts/zen_mode.js.coffee +++ b/app/assets/javascripts/zen_mode.js.coffee @@ -12,11 +12,11 @@ class @ZenMode $('body').on 'click', '.zen-enter-link', (e) => e.preventDefault() - $(e.currentTarget).closest('.zennable').find('.zen-toggle-comment').prop('checked', true) + $(e.currentTarget).closest('.zennable').find('.zen-toggle-comment').prop('checked', true).change() $('body').on 'click', '.zen-leave-link', (e) => e.preventDefault() - $(e.currentTarget).closest('.zennable').find('.zen-toggle-comment').prop('checked', false) + $(e.currentTarget).closest('.zennable').find('.zen-toggle-comment').prop('checked', false).change() $('body').on 'change', '.zen-toggle-comment', (e) => checkbox = e.currentTarget From ae552ec3158c88b6eb6d61cd4281442eff8d63b4 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 25 May 2015 20:51:08 -0700 Subject: [PATCH 095/255] Fix Markdown preview not working in Edit Milestone page Closes #1687 Closes https://github.com/gitlabhq/gitlabhq/issues/9325 --- CHANGELOG | 1 + app/assets/javascripts/zen_mode.js.coffee | 23 ++--------------------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ed9ffefb67..de0eb74059 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 7.12.0 (unreleased) + - Fix Markdown preview not working in Edit Milestone page (Stan Hu) - Add web hook support for note events (Stan Hu) - Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu) - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) diff --git a/app/assets/javascripts/zen_mode.js.coffee b/app/assets/javascripts/zen_mode.js.coffee index 0fb8f7ed75..26efc374f7 100644 --- a/app/assets/javascripts/zen_mode.js.coffee +++ b/app/assets/javascripts/zen_mode.js.coffee @@ -1,6 +1,4 @@ class @ZenMode - @fullscreen_prefix = 'fullscreen_' - constructor: -> @active_zen_area = null @active_checkbox = null @@ -23,7 +21,7 @@ class @ZenMode if checkbox.checked # Disable other keyboard shortcuts in ZEN mode Mousetrap.pause() - @udpateActiveZenArea(checkbox) + @updateActiveZenArea(checkbox) else @exitZenMode() @@ -32,14 +30,11 @@ class @ZenMode @exitZenMode() e.preventDefault() - $(window).on 'hashchange', @updateZenModeFromLocationHash - - udpateActiveZenArea: (checkbox) => + updateActiveZenArea: (checkbox) => @active_checkbox = $(checkbox) @active_checkbox.prop('checked', true) @active_zen_area = @active_checkbox.parent().find('textarea') @active_zen_area.focus() - window.location.hash = ZenMode.fullscreen_prefix + @active_checkbox.prop('id') exitZenMode: => if @active_zen_area isnt null @@ -51,17 +46,3 @@ class @ZenMode window.scrollTo(window.pageXOffset, @scroll_position) # Enable dropzone when leaving ZEN mode Dropzone.forElement('.div-dropzone').enable() - - checkboxFromLocationHash: (e) -> - id = $.trim(window.location.hash.replace('#' + ZenMode.fullscreen_prefix, '')) - if id - return $('.zennable input[type=checkbox]#' + id)[0] - else - return null - - updateZenModeFromLocationHash: (e) => - checkbox = @checkboxFromLocationHash() - if checkbox - @udpateActiveZenArea(checkbox) - else - @exitZenMode() From 8c3c5afba2e5b15cc170d51b9aab16362ab7c595 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 25 May 2015 21:41:47 -0700 Subject: [PATCH 096/255] Add file attachment support in Milestone description Closes #1648 --- CHANGELOG | 1 + app/assets/javascripts/dispatcher.js.coffee | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index ed9ffefb67..168b54edc6 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 7.12.0 (unreleased) + - Add file attachment support in Milestone description (Stan Hu) - Add web hook support for note events (Stan Hu) - Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu) - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index 2baaf430d9..da56e3cdbc 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -27,6 +27,7 @@ class Dispatcher new Milestone() when 'projects:milestones:new', 'projects:milestones:edit' new ZenMode() + new DropzoneInput($('.milestone-form')) when 'projects:compare:show' new Diff() when 'projects:issues:new','projects:issues:edit' From fa2aee5048d4f6acd393fdddf119bca74a4b94d7 Mon Sep 17 00:00:00 2001 From: Jeroen van Baarsen Date: Thu, 21 May 2015 16:37:50 +0200 Subject: [PATCH 097/255] Use .md as extention for wiki pages **What does this do?** It makes sure that when you create a wiki page via the web interface, the extention is .md instead of .markdown **Why is this needed?** When you're using Gollum locally, it will create pages with the .md extention. Also .md is the best known extention for markdown. This fix will make sure that if you're using gollum or the webinterface, the extention will be the same. **What issues does this fix?** Fixes https://github.com/gitlabhq/gitlabhq/issues/5204 Signed-off-by: Jeroen van Baarsen --- CHANGELOG | 1 + app/models/project_wiki.rb | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index a168342fce..ca8d1ec2ef 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -10,6 +10,7 @@ v 7.12.0 (unreleased) - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) - Use the user list from the target project in a merge request (Stan Hu) + - Default extention for wiki pages is now .md instead of .markdown (Jeroen van Baarsen) v 7.11.2 - no changes diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index 0706a1ca0d..231973fa54 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -2,7 +2,7 @@ class ProjectWiki include Gitlab::ShellAdapter MARKUPS = { - 'Markdown' => :markdown, + 'Markdown' => :md, 'RDoc' => :rdoc, 'AsciiDoc' => :asciidoc } unless defined?(MARKUPS) From b16aad9dd1de60585f8265eff11cdea19982740d Mon Sep 17 00:00:00 2001 From: Jeroen van Baarsen Date: Thu, 21 May 2015 11:38:13 +0200 Subject: [PATCH 098/255] Validate wiki page creation **What does this do?** It adds validation to the creation of a wiki page, that way the user gets real feedback instead of just a 404 page if the name of the wiki page was invalid **Why is this needed?** There are a lot of characters that are not allowed in the creation of a wiki page, there is even a small text that is saying: Please don't use spaces. Although we have that text there, we don't actually validate on this. This commit adds validation on the title and gives the user actual feedback. **What issues does this fix?** Fixes http://github.com/gitlabhq/gitlabhq/issues/5357 Fixes https://github.com/gitlabhq/gitlabhq/issues/8565 Fixes https://github.com/gitlabhq/gitlabhq/issues/3913 Fixes https://github.com/gitlabhq/gitlabhq/issues/8166 Signed-off-by: Jeroen van Baarsen --- CHANGELOG | 1 + app/assets/javascripts/wikis.js.coffee | 18 +++++++++++++----- app/views/projects/wikis/_new.html.haml | 2 ++ features/project/wiki.feature | 5 +++++ features/steps/project/wiki.rb | 10 ++++++++++ 5 files changed, 31 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ca8d1ec2ef..032a809820 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ v 7.12.0 (unreleased) - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) - Use the user list from the target project in a merge request (Stan Hu) - Default extention for wiki pages is now .md instead of .markdown (Jeroen van Baarsen) + - Add validation to wiki page creation (only [a-zA-Z0-9/_-] are allowed) (Jeroen van Baarsen) v 7.11.2 - no changes diff --git a/app/assets/javascripts/wikis.js.coffee b/app/assets/javascripts/wikis.js.coffee index 66757565d3..81cfc37b95 100644 --- a/app/assets/javascripts/wikis.js.coffee +++ b/app/assets/javascripts/wikis.js.coffee @@ -1,9 +1,17 @@ class @Wikis constructor: -> - $('.build-new-wiki').bind "click", -> + $('.build-new-wiki').bind "click", (e) -> + $('[data-error~=slug]').addClass("hidden") + $('p.hint').show() field = $('#new_wiki_path') - slug = field.val() - path = field.attr('data-wikis-path') + valid_slug_pattern = /^[\w\/-]+$/ - if(slug.length > 0) - location.href = path + "/" + slug + slug = field.val() + if slug.match valid_slug_pattern + path = field.attr('data-wikis-path') + if(slug.length > 0) + location.href = path + "/" + slug + else + e.preventDefault() + $('p.hint').hide() + $('[data-error~=slug]').removeClass("hidden") diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml index 6834969de8..b2c085f34b 100644 --- a/app/views/projects/wikis/_new.html.haml +++ b/app/views/projects/wikis/_new.html.haml @@ -8,6 +8,8 @@ = label_tag :new_wiki_path do %span Page slug = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project) + %p.hidden.text-danger{data: { error: "slug" }} + The page slug is invalid. Please don't use characters other then: a-z 0-9 _ - and / %p.hint Please don't use spaces. .modal-footer diff --git a/features/project/wiki.feature b/features/project/wiki.feature index 977cd609a1..7a70f34875 100644 --- a/features/project/wiki.feature +++ b/features/project/wiki.feature @@ -69,6 +69,11 @@ Feature: Project Wiki And I click on the "Pages" button Then I should see non-escaped link in the pages list + @javascript @focus + Scenario: Creating an invalid new page + Given I create a New page with an invalid name + Then I should see an error message + @javascript Scenario: Edit Wiki page that has a path Given I create a New page with paths diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb index 717132da45..58cb0ceb3f 100644 --- a/features/steps/project/wiki.rb +++ b/features/steps/project/wiki.rb @@ -133,6 +133,16 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps current_path.should include 'one/two/three' end + step 'I create a New page with an invalid name' do + click_on 'New Page' + fill_in 'Page slug', with: 'invalid name' + click_on 'Build' + end + + step 'I should see an error message' do + expect(page).to have_content "The page slug is invalid" + end + step 'I should see non-escaped link in the pages list' do page.should have_xpath("//a[@href='/#{project.path_with_namespace}/wikis/one/two/three']") end From 199135e567cec775dac8f11374324de0f51fed31 Mon Sep 17 00:00:00 2001 From: Sytse Sijbrandij Date: Tue, 26 May 2015 11:12:29 -0700 Subject: [PATCH 099/255] Point people to the issue tracker on GitLab.com to prevent duplication. --- CONTRIBUTING.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8059b95609..38fa66816a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,11 +29,9 @@ You can also sign up on [CodeTriage](http://www.codetriage.com/gitlabhq/gitlabhq ## Issue tracker -To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://about.gitlab.com/subscription/) and [consulting services](http://about.gitlab.com/consultancy/) are available from [GitLab.com](http://about.gitlab.com/). +To get support for your particular problem please use the [getting help channels](https://about.gitlab.com/getting-help/). -The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md). If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request. When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue. - -Issues can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues) or [github.com](https://github.com/gitlabhq/gitlabhq/issues). +The [GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md). If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request. When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue. Do not use the issue tracker for feature requests. We have a specific [feature request forum](http://feedback.gitlab.com) for this purpose. Please keep feature requests as small and simple as possible, complex ones might be edited to make them small and simple. From b11bcb8a74956fc6f690e9c12ea4c2327605e993 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 26 May 2015 20:32:11 +0200 Subject: [PATCH 100/255] Use default control for search field in header Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/generic/header.scss | 12 ------------ app/views/layouts/_search.html.haml | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss index 362b217a44..c4bafad690 100644 --- a/app/assets/stylesheets/generic/header.scss +++ b/app/assets/stylesheets/generic/header.scss @@ -184,29 +184,17 @@ header { padding: 4px 6px; padding-left: 25px; font-size: 13px; - @include border-radius(3px); - border: 1px solid #DDD; - box-shadow: none; - @include transition(all 0.15s ease-in 0s); - background-color: #f9f9f9; } } } .search .search-input { width: 300px; - &:focus { - width: 330px; - background-color: #FFF; - } } @media (max-width: 1200px) { .search .search-input { width: 200px; - &:focus { - width: 230px; - } } } diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index 04f7984685..e2d2dec7ab 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -1,6 +1,6 @@ .search = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| - = search_field_tag "search", nil, placeholder: search_placeholder, class: "search-input" + = search_field_tag "search", nil, placeholder: search_placeholder, class: "search-input form-control" = hidden_field_tag :group_id, @group.try(:id) - if @project && @project.persisted? = hidden_field_tag :project_id, @project.id From 8b92946b5407be42caa1e32a978555b94465905c Mon Sep 17 00:00:00 2001 From: Jonah Bishop Date: Tue, 26 May 2015 14:44:04 -0400 Subject: [PATCH 101/255] Change percent_complete rescue value from 100 to 0 The percent_complete method returns a value of 100 when a ZeroDivisionError occurs. That seems like a very strange default for an error case, and results in a bug when a milestone has no corresponding issues (new, empty milestones show 100% completion). This commit changes the rescue value to 0, and subsequently fixes #1656, which reported this problem. --- CHANGELOG | 1 + app/models/group_milestone.rb | 2 +- app/models/milestone.rb | 2 +- spec/models/milestone_spec.rb | 2 +- 4 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1b5427b5ad..356673cd5a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -11,6 +11,7 @@ v 7.12.0 (unreleased) - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) - Fix resolving of relative links to repository files in AsciiDoc documents. (Jakub Jirutka) - Use the user list from the target project in a merge request (Stan Hu) + - Fix new/empty milestones showing 100% completion value (Jonah Bishop) v 7.11.2 - no changes diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb index 7e4f16ebf1..ab055f6b80 100644 --- a/app/models/group_milestone.rb +++ b/app/models/group_milestone.rb @@ -44,7 +44,7 @@ class GroupMilestone def percent_complete ((closed_items_count * 100) / total_items_count).abs rescue ZeroDivisionError - 100 + 0 end def state diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 9bbb2bafb9..9c543b3702 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -66,7 +66,7 @@ class Milestone < ActiveRecord::Base def percent_complete ((closed_items_count * 100) / total_items_count).abs rescue ZeroDivisionError - 100 + 0 end def expires_at diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 45171e1bf6..eb73aa763f 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -47,7 +47,7 @@ describe Milestone do it "should recover from dividing by zero" do expect(milestone.issues).to receive(:count).and_return(0) - expect(milestone.percent_complete).to eq(100) + expect(milestone.percent_complete).to eq(0) end end From 38cd3d64514655c508eba980b7abc216ef2a1c0b Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 2 May 2015 22:41:37 -0400 Subject: [PATCH 102/255] Add Commit#== Prior, comparison would use the Ruby object's ID, which got out of sync after a Spring fork and would result in erroneous test failures. Now we just check that the compared object is a Commit and then compare their underlying raw commit objects. --- app/models/commit.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/commit.rb b/app/models/commit.rb index be5a118bfe..5dea7dda51 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -56,6 +56,10 @@ class Commit @raw.id end + def ==(other) + (self.class === other) && (raw == other.raw) + end + def diff_line_count @diff_line_count ||= Commit::diff_line_count(self.diffs) @diff_line_count From b06dc74d611192744d34acda944d7ed9e554342a Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 9 May 2015 19:02:59 -0400 Subject: [PATCH 103/255] Add Referable concern --- app/models/concerns/referable.rb | 52 ++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 app/models/concerns/referable.rb diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb new file mode 100644 index 0000000000..b41df301c3 --- /dev/null +++ b/app/models/concerns/referable.rb @@ -0,0 +1,52 @@ +# == Referable concern +# +# Contains functionality related to making a model referable in Markdown, such +# as "#1", "!2", "~3", etc. +module Referable + extend ActiveSupport::Concern + + # Returns the String necessary to reference this object in Markdown + # + # from_project - Refering Project object + # + # This should be overridden by the including class. + # + # Examples: + # + # Issue.first.to_reference # => "#1" + # Issue.last.to_reference(other_project) # => "cross-project#1" + # + # Returns a String + def to_reference(_from_project = nil) + '' + end + + module ClassMethods + # The character that prefixes the actual reference identifier + # + # This should be overridden by the including class. + # + # Examples: + # + # Issue.reference_prefix # => '#' + # MergeRequest.reference_prefix # => '!' + # + # Returns a String + def reference_prefix + '' + end + end + + private + + # Check if a reference is being done cross-project + # + # from_project - Refering Project object + def cross_project_reference?(from_project) + if Project === self + self != from_project + else + from_project && project && project != from_project + end + end +end From c0faf91ff23815404a95cf4510b43dcf5e331c4f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 2 May 2015 23:11:21 -0400 Subject: [PATCH 104/255] Add `to_reference` for models that support references Now there is a single source of information for which attribute a model uses to be referenced, and its special character. --- app/models/commit.rb | 16 +++++++-- app/models/commit_range.rb | 9 +++++ app/models/external_issue.rb | 6 ++++ app/models/group.rb | 10 ++++++ app/models/issue.rb | 21 +++++++++-- app/models/label.rb | 27 ++++++++++++++ app/models/merge_request.rb | 21 +++++++++-- app/models/project.rb | 9 +++-- app/models/snippet.rb | 19 ++++++++-- app/models/user.rb | 16 +++++++-- spec/models/commit_range_spec.rb | 23 ++++++++++++ spec/models/commit_spec.rb | 24 +++++++++++-- spec/models/external_issue_spec.rb | 24 +++++++++++++ spec/models/group_spec.rb | 26 ++++++++++---- spec/models/issue_spec.rb | 21 +++++++++-- spec/models/label_spec.rb | 56 +++++++++++++++++++++--------- spec/models/merge_request_spec.rb | 32 +++++++++++++++-- spec/models/project_spec.rb | 21 +++++++++-- spec/models/snippet_spec.rb | 26 +++++++++++++- spec/models/user_spec.rb | 20 ++++++++++- 20 files changed, 376 insertions(+), 51 deletions(-) create mode 100644 spec/models/external_issue_spec.rb diff --git a/app/models/commit.rb b/app/models/commit.rb index 5dea7dda51..3cc8d11a4a 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -1,9 +1,11 @@ class Commit - include ActiveModel::Conversion - include StaticModel extend ActiveModel::Naming + + include ActiveModel::Conversion include Mentionable include Participable + include Referable + include StaticModel attr_mentionable :safe_message participant :author, :committer, :notes, :mentioned_users @@ -60,6 +62,14 @@ class Commit (self.class === other) && (raw == other.raw) end + def to_reference(from_project = nil) + if cross_project_reference?(from_project) + "#{project.to_reference}@#{id}" + else + id + end + end + def diff_line_count @diff_line_count ||= Commit::diff_line_count(self.diffs) @diff_line_count @@ -132,7 +142,7 @@ class Commit # Mentionable override. def gfm_reference - "commit #{id}" + "commit #{to_reference}" end def author diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index e645619826..b98f939a11 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -19,6 +19,7 @@ # class CommitRange include ActiveModel::Conversion + include Referable attr_reader :sha_from, :notation, :sha_to @@ -59,6 +60,14 @@ class CommitRange "#{sha_from[0..7]}#{notation}#{sha_to[0..7]}" end + def to_reference(from_project = nil) + if cross_project_reference?(from_project) + "#{project.to_reference}@#{to_s}" + else + to_s + end + end + # Returns a String for use in a link's title attribute def reference_title "Commits #{suffixed_sha_from} through #{sha_to}" diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb index 85fdb12bfd..6fda4a2ab7 100644 --- a/app/models/external_issue.rb +++ b/app/models/external_issue.rb @@ -1,4 +1,6 @@ class ExternalIssue + include Referable + def initialize(issue_identifier, project) @issue_identifier, @project = issue_identifier, project end @@ -7,6 +9,10 @@ class ExternalIssue @issue_identifier.to_s end + def to_reference(_from_project = nil) + id + end + def id @issue_identifier.to_s end diff --git a/app/models/group.rb b/app/models/group.rb index 687458adac..33d72e0d9e 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -17,6 +17,8 @@ require 'carrierwave/orm/activerecord' require 'file_size_validator' class Group < Namespace + include Referable + has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' has_many :users, through: :group_members @@ -36,6 +38,14 @@ class Group < Namespace def sort(method) order_by(method) end + + def reference_prefix + '@' + end + end + + def to_reference(_from_project = nil) + "#{self.class.reference_prefix}#{name}" end def human_name diff --git a/app/models/issue.rb b/app/models/issue.rb index 6e10205138..ff13cbca84 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -21,10 +21,11 @@ require 'carrierwave/orm/activerecord' require 'file_size_validator' class Issue < ActiveRecord::Base - include Issuable include InternalId - include Taskable + include Issuable + include Referable include Sortable + include Taskable ActsAsTaggableOn.strict_case_match = true @@ -49,14 +50,28 @@ class Issue < ActiveRecord::Base state :closed end + def self.reference_prefix + '#' + end + def hook_attrs attributes end + def to_reference(from_project = nil) + reference = "#{self.class.reference_prefix}#{iid}" + + if cross_project_reference?(from_project) + reference = project.to_reference + reference + end + + reference + end + # Mentionable overrides. def gfm_reference - "issue ##{iid}" + "issue #{to_reference}" end # Reset issue events cache diff --git a/app/models/label.rb b/app/models/label.rb index eee28acefc..013e6bf597 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -11,6 +11,8 @@ # class Label < ActiveRecord::Base + include Referable + DEFAULT_COLOR = '#428BCA' default_value_for :color, DEFAULT_COLOR @@ -34,6 +36,31 @@ class Label < ActiveRecord::Base alias_attribute :name, :title + def self.reference_prefix + '~' + end + + # Returns the String necessary to reference this Label in Markdown + # + # format - Symbol format to use (default: :id, optional: :name) + # + # Note that its argument differs from other objects implementing Referable. If + # a non-Symbol argument is given (such as a Project), it will default to :id. + # + # Examples: + # + # Label.first.to_reference # => "~1" + # Label.first.to_reference(:name) # => "~\"bug\"" + # + # Returns a String + def to_reference(format = :id) + if format == :name + %(#{self.class.reference_prefix}"#{name}") + else + "#{self.class.reference_prefix}#{id}" + end + end + def open_issues_count issues.opened.count end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 64f3c39f13..bfbf498591 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -25,10 +25,11 @@ require Rails.root.join("app/models/commit") require Rails.root.join("lib/static_model") class MergeRequest < ActiveRecord::Base - include Issuable - include Taskable include InternalId + include Issuable + include Referable include Sortable + include Taskable belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project" belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" @@ -135,6 +136,20 @@ class MergeRequest < ActiveRecord::Base scope :closed, -> { with_states(:closed, :merged) } scope :declined, -> { with_states(:closed) } + def self.reference_prefix + '!' + end + + def to_reference(from_project = nil) + reference = "#{self.class.reference_prefix}#{iid}" + + if cross_project_reference?(from_project) + reference = project.to_reference + reference + end + + reference + end + def validate_branches if target_project == source_project && target_branch == source_branch errors.add :branch_conflict, "You can not use same project/branch for source and target" @@ -291,7 +306,7 @@ class MergeRequest < ActiveRecord::Base # Mentionable override. def gfm_reference - "merge request !#{iid}" + "merge request #{to_reference}" end def target_project_path diff --git a/app/models/project.rb b/app/models/project.rb index 09d3ffd22f..c943114449 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -33,11 +33,12 @@ require 'carrierwave/orm/activerecord' require 'file_size_validator' class Project < ActiveRecord::Base - include Sortable + include Gitlab::ConfigHelper include Gitlab::ShellAdapter include Gitlab::VisibilityLevel - include Gitlab::ConfigHelper include Rails.application.routes.url_helpers + include Referable + include Sortable extend Gitlab::ConfigHelper extend Enumerize @@ -305,6 +306,10 @@ class Project < ActiveRecord::Base path end + def to_reference(_from_project = nil) + path_with_namespace + end + def web_url [gitlab_config.url, path_with_namespace].join('/') end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index d2af26539b..90fada3c11 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -16,10 +16,11 @@ # class Snippet < ActiveRecord::Base - include Sortable - include Linguist::BlobHelper include Gitlab::VisibilityLevel + include Linguist::BlobHelper include Participable + include Referable + include Sortable default_value_for :visibility_level, Snippet::PRIVATE @@ -50,6 +51,20 @@ class Snippet < ActiveRecord::Base participant :author, :notes + def self.reference_prefix + '$' + end + + def to_reference(from_project = nil) + reference = "#{self.class.reference_prefix}#{id}" + + if cross_project_reference?(from_project) + reference = project.to_reference + reference + end + + reference + end + def self.content_types [ ".rb", ".py", ".pl", ".scala", ".c", ".cpp", ".java", diff --git a/app/models/user.rb b/app/models/user.rb index 4dd37e7356..f546dc015c 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -62,11 +62,13 @@ require 'carrierwave/orm/activerecord' require 'file_size_validator' class User < ActiveRecord::Base - include Sortable - include Gitlab::ConfigHelper - include TokenAuthenticatable extend Gitlab::ConfigHelper + + include Gitlab::ConfigHelper include Gitlab::CurrentSettings + include Referable + include Sortable + include TokenAuthenticatable default_value_for :admin, false default_value_for :can_create_group, gitlab_config.default_can_create_group @@ -247,6 +249,10 @@ class User < ActiveRecord::Base def build_user(attrs = {}) User.new(attrs) end + + def reference_prefix + '@' + end end # @@ -257,6 +263,10 @@ class User < ActiveRecord::Base username end + def to_reference(_from_project = nil) + "#{self.class.reference_prefix}#{username}" + end + def notification @notification ||= Notification.new(self) end diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index 31ee3e99ca..2d347a335a 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -11,6 +11,29 @@ describe CommitRange do expect { described_class.new("Foo") }.to raise_error end + describe 'modules' do + subject { described_class } + + it { is_expected.to include_module(Referable) } + end + + describe '#to_reference' do + let(:project) { double('project', to_reference: 'namespace1/project') } + + before do + range.project = project + end + + it 'returns a String reference to the object' do + expect(range.to_reference).to eq range.to_s + end + + it 'supports a cross-project reference' do + cross = double('project') + expect(range.to_reference(cross)).to eq "#{project.to_reference}@#{range.to_s}" + end + end + describe '#to_s' do it 'is correct for three-dot syntax' do expect(range.to_s).to eq "#{sha_from[0..7]}...#{sha_to[0..7]}" diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index ad2ac143d9..27eb02a870 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -1,8 +1,28 @@ require 'spec_helper' describe Commit do - let(:project) { create :project } - let(:commit) { project.commit } + let(:project) { create(:project) } + let(:commit) { project.commit } + + describe 'modules' do + subject { described_class } + + it { is_expected.to include_module(Mentionable) } + it { is_expected.to include_module(Participable) } + it { is_expected.to include_module(Referable) } + it { is_expected.to include_module(StaticModel) } + end + + describe '#to_reference' do + it 'returns a String reference to the object' do + expect(commit.to_reference).to eq commit.id + end + + it 'supports a cross-project reference' do + cross = double('project') + expect(commit.to_reference(cross)).to eq "#{project.to_reference}@#{commit.id}" + end + end describe '#title' do it "returns no_commit_message when safe_message is blank" do diff --git a/spec/models/external_issue_spec.rb b/spec/models/external_issue_spec.rb new file mode 100644 index 0000000000..7744610db7 --- /dev/null +++ b/spec/models/external_issue_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +describe ExternalIssue do + let(:project) { double('project', to_reference: 'namespace1/project1') } + let(:issue) { described_class.new('EXT-1234', project) } + + describe 'modules' do + subject { described_class } + + it { is_expected.to include_module(Referable) } + end + + describe '#to_reference' do + it 'returns a String reference to the object' do + expect(issue.to_reference).to eq issue.id + end + end + + describe '#title' do + it 'returns a title' do + expect(issue.title).to eq "External Issue #{issue}" + end + end +end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 9428224a64..80638fc8db 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -18,16 +18,30 @@ require 'spec_helper' describe Group do let!(:group) { create(:group) } - describe "Associations" do + describe 'associations' do it { is_expected.to have_many :projects } it { is_expected.to have_many :group_members } end - it { is_expected.to validate_presence_of :name } - it { is_expected.to validate_uniqueness_of(:name) } - it { is_expected.to validate_presence_of :path } - it { is_expected.to validate_uniqueness_of(:path) } - it { is_expected.not_to validate_presence_of :owner } + describe 'modules' do + subject { described_class } + + it { is_expected.to include_module(Referable) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of :name } + it { is_expected.to validate_uniqueness_of(:name) } + it { is_expected.to validate_presence_of :path } + it { is_expected.to validate_uniqueness_of(:path) } + it { is_expected.not_to validate_presence_of :owner } + end + + describe '#to_reference' do + it 'returns a String reference to the object' do + expect(group.to_reference).to eq "@#{group.name}" + end + end describe :users do it { expect(group.users).to eq(group.owners) } diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 20d823b40e..4e4f816a26 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -24,15 +24,30 @@ describe Issue do it { is_expected.to belong_to(:milestone) } end - describe "Mass assignment" do - end - describe 'modules' do + subject { described_class } + + it { is_expected.to include_module(InternalId) } it { is_expected.to include_module(Issuable) } + it { is_expected.to include_module(Referable) } + it { is_expected.to include_module(Sortable) } + it { is_expected.to include_module(Taskable) } end subject { create(:issue) } + describe '#to_reference' do + it 'returns a String reference to the object' do + expect(subject.to_reference).to eq "##{subject.iid}" + end + + it 'supports a cross-project reference' do + cross = double('project') + expect(subject.to_reference(cross)). + to eq "#{subject.project.to_reference}##{subject.iid}" + end + end + describe '#is_being_reassigned?' do it 'returns true if the issue assignee has changed' do subject.assignee = create(:user) diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 8644ac4660..a13f9ac926 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -14,30 +14,54 @@ require 'spec_helper' describe Label do let(:label) { create(:label) } - it { expect(label).to be_valid } - it { is_expected.to belong_to(:project) } + describe 'associations' do + it { is_expected.to belong_to(:project) } + it { is_expected.to have_many(:label_links).dependent(:destroy) } + it { is_expected.to have_many(:issues).through(:label_links).source(:target) } + end + + describe 'modules' do + subject { described_class } + + it { is_expected.to include_module(Referable) } + end + + describe 'validation' do + it { is_expected.to validate_presence_of(:project) } - describe 'Validation' do it 'should validate color code' do - expect(build(:label, color: 'G-ITLAB')).not_to be_valid - expect(build(:label, color: 'AABBCC')).not_to be_valid - expect(build(:label, color: '#AABBCCEE')).not_to be_valid - expect(build(:label, color: '#GGHHII')).not_to be_valid - expect(build(:label, color: '#')).not_to be_valid - expect(build(:label, color: '')).not_to be_valid + expect(label).not_to allow_value('G-ITLAB').for(:color) + expect(label).not_to allow_value('AABBCC').for(:color) + expect(label).not_to allow_value('#AABBCCEE').for(:color) + expect(label).not_to allow_value('GGHHII').for(:color) + expect(label).not_to allow_value('#').for(:color) + expect(label).not_to allow_value('').for(:color) - expect(build(:label, color: '#AABBCC')).to be_valid + expect(label).to allow_value('#AABBCC').for(:color) + expect(label).to allow_value('#abcdef').for(:color) end it 'should validate title' do - expect(build(:label, title: 'G,ITLAB')).not_to be_valid - expect(build(:label, title: 'G?ITLAB')).not_to be_valid - expect(build(:label, title: 'G&ITLAB')).not_to be_valid - expect(build(:label, title: '')).not_to be_valid + expect(label).not_to allow_value('G,ITLAB').for(:title) + expect(label).not_to allow_value('G?ITLAB').for(:title) + expect(label).not_to allow_value('G&ITLAB').for(:title) + expect(label).not_to allow_value('').for(:title) - expect(build(:label, title: 'GITLAB')).to be_valid - expect(build(:label, title: 'gitlab')).to be_valid + expect(label).to allow_value('GITLAB').for(:title) + expect(label).to allow_value('gitlab').for(:title) + expect(label).to allow_value("customer's request").for(:title) + end + end + + describe '#to_reference' do + it 'returns a String reference to the object' do + expect(label.to_reference).to eq "~#{label.id}" + expect(label.to_reference(double)).to eq "~#{label.id}" + end + + it 'returns a String reference to the object using its name' do + expect(label.to_reference(:name)).to eq %(~"#{label.name}") end end end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 97b8abc49d..757d8bdfae 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -24,7 +24,26 @@ require 'spec_helper' describe MergeRequest do - describe "Validation" do + subject { create(:merge_request) } + + describe 'modules' do + subject { described_class } + + it { is_expected.to include_module(InternalId) } + it { is_expected.to include_module(Issuable) } + it { is_expected.to include_module(Referable) } + it { is_expected.to include_module(Sortable) } + it { is_expected.to include_module(Taskable) } + end + + describe 'associations' do + it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') } + it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') } + + it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) } + end + + describe 'validation' do it { is_expected.to validate_presence_of(:target_branch) } it { is_expected.to validate_presence_of(:source_branch) } end @@ -38,8 +57,15 @@ describe MergeRequest do it { is_expected.to respond_to(:cannot_be_merged?) } end - describe 'modules' do - it { is_expected.to include_module(Issuable) } + describe '#to_reference' do + it 'returns a String reference to the object' do + expect(subject.to_reference).to eq "!#{subject.iid}" + end + + it 'supports a cross-project reference' do + cross = double('project') + expect(subject.to_reference(cross)).to eq "#{subject.source_project.to_reference}!#{subject.iid}" + end end describe "#mr_and_commit_notes" do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 37e21a9081..48568e2a3f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -32,7 +32,7 @@ require 'spec_helper' describe Project do - describe 'Associations' do + describe 'associations' do it { is_expected.to belong_to(:group) } it { is_expected.to belong_to(:namespace) } it { is_expected.to belong_to(:creator).class_name('User') } @@ -54,10 +54,17 @@ describe Project do it { is_expected.to have_one(:asana_service).dependent(:destroy) } end - describe 'Mass assignment' do + describe 'modules' do + subject { described_class } + + it { is_expected.to include_module(Gitlab::ConfigHelper) } + it { is_expected.to include_module(Gitlab::ShellAdapter) } + it { is_expected.to include_module(Gitlab::VisibilityLevel) } + it { is_expected.to include_module(Referable) } + it { is_expected.to include_module(Sortable) } end - describe 'Validation' do + describe 'validation' do let!(:project) { create(:project) } it { is_expected.to validate_presence_of(:name) } @@ -91,6 +98,14 @@ describe Project do it { is_expected.to respond_to(:path_with_namespace) } end + describe '#to_reference' do + let(:project) { create(:empty_project) } + + it 'returns a String reference to the object' do + expect(project.to_reference).to eq project.path_with_namespace + end + end + it 'should return valid url to repo' do project = Project.new(path: 'somewhere') expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git') diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index e37dcc7523..252320b798 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -18,7 +18,17 @@ require 'spec_helper' describe Snippet do - describe "Associations" do + describe 'modules' do + subject { described_class } + + it { is_expected.to include_module(Gitlab::VisibilityLevel) } + it { is_expected.to include_module(Linguist::BlobHelper) } + it { is_expected.to include_module(Participable) } + it { is_expected.to include_module(Referable) } + it { is_expected.to include_module(Sortable) } + end + + describe 'associations' do it { is_expected.to belong_to(:author).class_name('User') } it { is_expected.to have_many(:notes).dependent(:destroy) } end @@ -37,4 +47,18 @@ describe Snippet do it { is_expected.to validate_presence_of(:content) } end + + describe '#to_reference' do + let(:project) { create(:empty_project) } + let(:snippet) { create(:snippet, project: project) } + + it 'returns a String reference to the object' do + expect(snippet.to_reference).to eq "$#{snippet.id}" + end + + it 'supports a cross-project reference' do + cross = double('project') + expect(snippet.to_reference(cross)).to eq "#{project.to_reference}$#{snippet.id}" + end + end end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 0dddcd5bda..87f95f9af8 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -63,7 +63,17 @@ require 'spec_helper' describe User do include Gitlab::CurrentSettings - describe "Associations" do + describe 'modules' do + subject { described_class } + + it { is_expected.to include_module(Gitlab::ConfigHelper) } + it { is_expected.to include_module(Gitlab::CurrentSettings) } + it { is_expected.to include_module(Referable) } + it { is_expected.to include_module(Sortable) } + it { is_expected.to include_module(TokenAuthenticatable) } + end + + describe 'associations' do it { is_expected.to have_one(:namespace) } it { is_expected.to have_many(:snippets).class_name('Snippet').dependent(:destroy) } it { is_expected.to have_many(:project_members).dependent(:destroy) } @@ -175,6 +185,14 @@ describe User do it { is_expected.to respond_to(:private_token) } end + describe '#to_reference' do + let(:user) { create(:user) } + + it 'returns a String reference to the object' do + expect(user.to_reference).to eq "@#{user.username}" + end + end + describe '#generate_password' do it "should execute callback when force_random_password specified" do user = build(:user, force_random_password: true) From 8773f339a33cf31f979013cf306e5fca5fe66a89 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 2 May 2015 23:14:31 -0400 Subject: [PATCH 105/255] Minor model spec cleanups Snippet model was missing project association --- app/models/snippet.rb | 3 ++- spec/models/issue_spec.rb | 7 ++----- spec/models/merge_request_spec.rb | 21 ++++++++------------- spec/models/snippet_spec.rb | 10 +++++----- spec/models/user_spec.rb | 3 --- 5 files changed, 17 insertions(+), 27 deletions(-) diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 90fada3c11..8c3167833a 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -24,7 +24,8 @@ class Snippet < ActiveRecord::Base default_value_for :visibility_level, Snippet::PRIVATE - belongs_to :author, class_name: "User" + belongs_to :author, class_name: 'User' + belongs_to :project has_many :notes, as: :noteable, dependent: :destroy diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 4e4f816a26..614b648bb5 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -60,11 +60,8 @@ describe Issue do describe '#is_being_reassigned?' do it 'returns issues assigned to user' do - user = create :user - - 2.times do - issue = create :issue, assignee: user - end + user = create(:user) + create_list(:issue, 2, assignee: user) expect(Issue.open_for(user).count).to eq 2 end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 757d8bdfae..57b1b9dfcf 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -26,6 +26,13 @@ require 'spec_helper' describe MergeRequest do subject { create(:merge_request) } + describe 'associations' do + it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') } + it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') } + + it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) } + end + describe 'modules' do subject { described_class } @@ -36,22 +43,12 @@ describe MergeRequest do it { is_expected.to include_module(Taskable) } end - describe 'associations' do - it { is_expected.to belong_to(:target_project).with_foreign_key(:target_project_id).class_name('Project') } - it { is_expected.to belong_to(:source_project).with_foreign_key(:source_project_id).class_name('Project') } - - it { is_expected.to have_one(:merge_request_diff).dependent(:destroy) } - end - describe 'validation' do it { is_expected.to validate_presence_of(:target_branch) } it { is_expected.to validate_presence_of(:source_branch) } end - describe "Mass assignment" do - end - - describe "Respond to" do + describe 'respond to' do it { is_expected.to respond_to(:unchecked?) } it { is_expected.to respond_to(:can_be_merged?) } it { is_expected.to respond_to(:cannot_be_merged?) } @@ -83,8 +80,6 @@ describe MergeRequest do end end - subject { create(:merge_request) } - describe '#is_being_reassigned?' do it 'returns true if the merge_request assignee has changed' do subject.assignee = create(:user) diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 252320b798..c81dd36ef4 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -30,22 +30,22 @@ describe Snippet do describe 'associations' do it { is_expected.to belong_to(:author).class_name('User') } + it { is_expected.to belong_to(:project) } it { is_expected.to have_many(:notes).dependent(:destroy) } end - describe "Mass assignment" do - end - - describe "Validation" do + describe 'validation' do it { is_expected.to validate_presence_of(:author) } it { is_expected.to validate_presence_of(:title) } it { is_expected.to ensure_length_of(:title).is_within(0..255) } it { is_expected.to validate_presence_of(:file_name) } - it { is_expected.to ensure_length_of(:title).is_within(0..255) } + it { is_expected.to ensure_length_of(:file_name).is_within(0..255) } it { is_expected.to validate_presence_of(:content) } + + it { is_expected.to validate_inclusion_of(:visibility_level).in_array(Gitlab::VisibilityLevel.values) } end describe '#to_reference' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 87f95f9af8..e1205c18a8 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -89,9 +89,6 @@ describe User do it { is_expected.to have_many(:identities).dependent(:destroy) } end - describe "Mass assignment" do - end - describe 'validations' do it { is_expected.to validate_presence_of(:username) } it { is_expected.to validate_presence_of(:projects_limit) } From 3b80cf524c0969495b602b600c2c1e3b52e1c78c Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 2 May 2015 23:44:46 -0400 Subject: [PATCH 106/255] Use to_reference in Mentionable shared examples --- spec/support/mentionable_shared_examples.rb | 32 +++++++++------------ 1 file changed, 14 insertions(+), 18 deletions(-) diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index 53fb654555..ede62e8f37 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -10,12 +10,12 @@ def common_mentionable_setup let(:mentioned_issue) { create(:issue, project: project) } let(:mentioned_mr) { create(:merge_request, :simple, source_project: project) } - let(:mentioned_commit) { project.repository.commit } + let(:mentioned_commit) { project.commit } let(:ext_proj) { create(:project, :public) } let(:ext_issue) { create(:issue, project: ext_proj) } let(:ext_mr) { create(:merge_request, :simple, source_project: ext_proj) } - let(:ext_commit) { ext_proj.repository.commit } + let(:ext_commit) { ext_proj.commit } # Override to add known commits to the repository stub. let(:extra_commits) { [] } @@ -23,21 +23,19 @@ def common_mentionable_setup # A string that mentions each of the +mentioned_.*+ objects above. Mentionables should add a self-reference # to this string and place it in their +mentionable_text+. let(:ref_string) do - cross = ext_proj.path_with_namespace - <<-MSG.strip_heredoc These references are new: - Issue: ##{mentioned_issue.iid} - Merge: !#{mentioned_mr.iid} - Commit: #{mentioned_commit.id} + Issue: #{mentioned_issue.to_reference} + Merge: #{mentioned_mr.to_reference} + Commit: #{mentioned_commit.to_reference} This reference is a repeat and should only be mentioned once: - Repeat: ##{mentioned_issue.iid} + Repeat: #{mentioned_issue.to_reference} These references are cross-referenced: - Issue: #{cross}##{ext_issue.iid} - Merge: #{cross}!#{ext_mr.iid} - Commit: #{cross}@#{ext_commit.short_id} + Issue: #{ext_issue.to_reference(project)} + Merge: #{ext_mr.to_reference(project)} + Commit: #{ext_commit.to_reference(project)} This is a self-reference and should not be mentioned at all: Self: #{backref_text} @@ -109,19 +107,17 @@ shared_examples 'an editable mentionable' do it 'creates new cross-reference notes when the mentionable text is edited' do subject.save - cross = ext_proj.path_with_namespace - new_text = <<-MSG These references already existed: - Issue: ##{mentioned_issue.iid} - Commit: #{mentioned_commit.id} + Issue: #{mentioned_issue.to_reference} + Commit: #{mentioned_commit.to_reference} This cross-project reference already existed: - Issue: #{cross}##{ext_issue.iid} + Issue: #{ext_issue.to_reference(project)} These two references are introduced in an edit: - Issue: ##{new_issues[0].iid} - Cross: #{cross}##{new_issues[1].iid} + Issue: #{new_issues[0].to_reference} + Cross: #{new_issues[1].to_reference(project)} MSG # These three objects were already referenced, and should not receive new From ca268b85f62448584eb8455048069669efdcc990 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 2 May 2015 23:59:55 -0400 Subject: [PATCH 107/255] Use to_reference in Markdown feature spec --- spec/features/markdown_spec.rb | 17 ++++----- spec/fixtures/markdown.md.erb | 64 +++++++++++++++++----------------- 2 files changed, 38 insertions(+), 43 deletions(-) diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index 8f3dfc8d5a..0fc144462f 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -344,13 +344,13 @@ class MarkdownFeature end def commit - @commit ||= project.repository.commit + @commit ||= project.commit end def commit_range unless @commit_range - commit2 = project.repository.commit('HEAD~3') - @commit_range = CommitRange.new("#{commit.id}...#{commit2.id}") + commit2 = project.commit('HEAD~3') + @commit_range = CommitRange.new("#{commit.id}...#{commit2.id}", project) end @commit_range @@ -376,11 +376,6 @@ class MarkdownFeature @xproject end - # Shortcut to "cross-reference/project" - def xref - xproject.path_with_namespace - end - def xissue @xissue ||= create(:issue, project: xproject) end @@ -394,13 +389,13 @@ class MarkdownFeature end def xcommit - @xcommit ||= xproject.repository.commit + @xcommit ||= xproject.commit end def xcommit_range unless @xcommit_range - xcommit2 = xproject.repository.commit('HEAD~2') - @xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}") + xcommit2 = xproject.commit('HEAD~2') + @xcommit_range = CommitRange.new("#{xcommit.id}...#{xcommit2.id}", xproject) end @xcommit_range diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 64817ec670..26fc4e38e5 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -127,61 +127,61 @@ But it shouldn't autolink text inside certain tags: - http://about.gitlab.com/ - http://about.gitlab.com/ -### Reference Filters (e.g., #<%= issue.iid %>) +### Reference Filters (e.g., <%= issue.to_reference %>) -References should be parseable even inside _!<%= merge_request.iid %>_ emphasis. +References should be parseable even inside _<%= merge_request.to_reference %>_ emphasis. #### UserReferenceFilter - All: @all -- User: @<%= user.username %> -- Group: @<%= group.name %> -- Ignores invalid: @fake_user -- Ignored in code: `@<%= user.username %>` -- Ignored in links: [Link to @<%= user.username %>](#user-link) +- User: <%= user.to_reference %> +- Group: <%= group.to_reference %> +- Ignores invalid: <%= User.reference_prefix %>fake_user +- Ignored in code: `<%= user.to_reference %>` +- Ignored in links: [Link to <%= user.to_reference %>](#user-link) #### IssueReferenceFilter -- Issue: #<%= issue.iid %> -- Issue in another project: <%= xref %>#<%= xissue.iid %> -- Ignored in code: `#<%= issue.iid %>` -- Ignored in links: [Link to #<%= issue.iid %>](#issue-link) +- Issue: <%= issue.to_reference %> +- Issue in another project: <%= xissue.to_reference(project) %> +- Ignored in code: `<%= issue.to_reference %>` +- Ignored in links: [Link to <%= issue.to_reference %>](#issue-link) #### MergeRequestReferenceFilter -- Merge request: !<%= merge_request.iid %> -- Merge request in another project: <%= xref %>!<%= xmerge_request.iid %> -- Ignored in code: `!<%= merge_request.iid %>` -- Ignored in links: [Link to !<%= merge_request.iid %>](#merge-request-link) +- Merge request: <%= merge_request.to_reference %> +- Merge request in another project: <%= xmerge_request.to_reference(project) %> +- Ignored in code: `<%= merge_request.to_reference %>` +- Ignored in links: [Link to <%= merge_request.to_reference %>](#merge-request-link) #### SnippetReferenceFilter -- Snippet: $<%= snippet.id %> -- Snippet in another project: <%= xref %>$<%= xsnippet.id %> -- Ignored in code: `$<%= snippet.id %>` -- Ignored in links: [Link to $<%= snippet.id %>](#snippet-link) +- Snippet: <%= snippet.to_reference %> +- Snippet in another project: <%= xsnippet.to_reference(project) %> +- Ignored in code: `<%= snippet.to_reference %>` +- Ignored in links: [Link to <%= snippet.to_reference %>](#snippet-link) #### CommitRangeReferenceFilter -- Range: <%= commit_range %> -- Range in another project: <%= xref %>@<%= xcommit_range %> -- Ignored in code: `<%= commit_range %>` -- Ignored in links: [Link to <%= commit_range %>](#commit-range-link) +- Range: <%= commit_range.to_reference %> +- Range in another project: <%= xcommit_range.to_reference(project) %> +- Ignored in code: `<%= commit_range.to_reference %>` +- Ignored in links: [Link to <%= commit_range.to_reference %>](#commit-range-link) #### CommitReferenceFilter -- Commit: <%= commit.id %> -- Commit in another project: <%= xref %>@<%= xcommit.id %> -- Ignored in code: `<%= commit.id %>` -- Ignored in links: [Link to <%= commit.id %>](#commit-link) +- Commit: <%= commit.to_reference %> +- Commit in another project: <%= xcommit.to_reference(project) %> +- Ignored in code: `<%= commit.to_reference %>` +- Ignored in links: [Link to <%= commit.to_reference %>](#commit-link) #### LabelReferenceFilter -- Label by ID: ~<%= simple_label.id %> -- Label by name: ~<%= simple_label.name %> -- Label by name in quotes: ~"<%= label.name %>" -- Ignored in code: `~<%= simple_label.name %>` -- Ignored in links: [Link to ~<%= simple_label.id %>](#label-link) +- Label by ID: <%= simple_label.to_reference %> +- Label by name: <%= Label.reference_prefix %><%= simple_label.name %> +- Label by name in quotes: <%= label.to_reference(:name) %> +- Ignored in code: `<%= simple_label.to_reference %>` +- Ignored in links: [Link to <%= simple_label.to_reference %>](#label-link) ### Task Lists From 38fb6279f9709e43f80c70c1fd4cccef77963b21 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 20 Apr 2015 18:47:22 -0400 Subject: [PATCH 108/255] Simplify `cross_project_reference` with `to_reference` --- app/helpers/gitlab_markdown_helper.rb | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 7bcc011fd5..d89f7b4a28 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -135,15 +135,25 @@ module GitlabMarkdownHelper end end + # Returns the text necessary to reference `entity` across projects + # + # project - Project to reference + # entity - Object that responds to `to_reference` + # + # Examples: + # + # cross_project_reference(project, project.issues.first) + # # => 'namespace1/project1#123' + # + # cross_project_reference(project, project.merge_requests.first) + # # => 'namespace1/project1!345' + # + # Returns a String def cross_project_reference(project, entity) - path = project.path_with_namespace - - if entity.kind_of?(Issue) - [path, entity.iid].join('#') - elsif entity.kind_of?(MergeRequest) - [path, entity.iid].join('!') + if entity.respond_to?(:to_reference) + "#{project.to_reference}#{entity.to_reference}" else - raise 'Not supported type' + '' end end end From 0359d41b064e9f7680d0017013e011103747b614 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Mon, 11 May 2015 15:56:00 -0400 Subject: [PATCH 109/255] Implement gfm_reference directly in Mentionable Except for Note, which still overrides it. --- app/models/commit.rb | 5 ----- app/models/concerns/mentionable.rb | 11 ++++++++--- app/models/issue.rb | 6 ------ app/models/merge_request.rb | 5 ----- 4 files changed, 8 insertions(+), 19 deletions(-) diff --git a/app/models/commit.rb b/app/models/commit.rb index 3cc8d11a4a..085f4e6398 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -140,11 +140,6 @@ class Commit Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message) end - # Mentionable override. - def gfm_reference - "commit #{to_reference}" - end - def author User.find_for_commit(author_email, author_name) end diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index b7c39df885..f28b20afd8 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -20,10 +20,15 @@ module Mentionable end end - # Generate a GFM back-reference that will construct a link back to this Mentionable when rendered. Must - # be overridden if this model object can be referenced directly by GFM notation. + # Returns the text used as the body of a Note when this object is referenced + # + # By default this will be the class name and the result of calling + # `to_reference` on the object. def gfm_reference - raise NotImplementedError.new("#{self.class} does not implement #gfm_reference") + # Convert "MergeRequest" to "merge request" + friendly_name = self.class.to_s.underscore.humanize.downcase + + "#{friendly_name} #{to_reference}" end # Construct a String that contains possible GFM references. diff --git a/app/models/issue.rb b/app/models/issue.rb index ff13cbca84..31803b57b3 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -68,12 +68,6 @@ class Issue < ActiveRecord::Base reference end - # Mentionable overrides. - - def gfm_reference - "issue #{to_reference}" - end - # Reset issue events cache # # Since we do cache @event we need to reset cache in special cases: diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index bfbf498591..60b0ce6c01 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -304,11 +304,6 @@ class MergeRequest < ActiveRecord::Base end end - # Mentionable override. - def gfm_reference - "merge request #{to_reference}" - end - def target_project_path if target_project target_project.path_with_namespace From 9d032cddf5575e2233c81142a3ebc609cd43a47e Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 12 May 2015 16:26:53 -0400 Subject: [PATCH 110/255] Correct the ReferenceFilter html/pipeline/filter require --- lib/gitlab/markdown/reference_filter.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb index a4303d96be..be4d26af0f 100644 --- a/lib/gitlab/markdown/reference_filter.rb +++ b/lib/gitlab/markdown/reference_filter.rb @@ -1,5 +1,5 @@ require 'active_support/core_ext/string/output_safety' -require 'html/pipeline' +require 'html/pipeline/filter' module Gitlab module Markdown From 136ab73803850c10588b369862b1e5524849d31c Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 12 May 2015 17:58:29 -0400 Subject: [PATCH 111/255] Update CommitRange#to_reference to use full SHAs We only want them shortened by the filter, which calls to_s --- app/models/commit_range.rb | 9 ++++--- .../commit_range_reference_filter_spec.rb | 3 +-- spec/models/commit_range_spec.rb | 26 +++++++++---------- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index b98f939a11..fb1f6d09be 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -61,11 +61,14 @@ class CommitRange end def to_reference(from_project = nil) + # Not using to_s because we want the full SHAs + reference = sha_from + notation + sha_to + if cross_project_reference?(from_project) - "#{project.to_reference}@#{to_s}" - else - to_s + reference = project.to_reference + '@' + reference end + + reference end # Returns a String for use in a link's title attribute diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb index 7274cb309a..1593088a09 100644 --- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb @@ -9,8 +9,7 @@ module Gitlab::Markdown let(:commit2) { project.commit("HEAD~2") } it 'requires project context' do - expect { described_class.call('Commit Range 1c002d..d200c1', {}) }. - to raise_error(ArgumentError, /:project/) + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) end %w(pre code a style).each do |elem| diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb index 2d347a335a..e7fb43ff33 100644 --- a/spec/models/commit_range_spec.rb +++ b/spec/models/commit_range_spec.rb @@ -1,6 +1,12 @@ require 'spec_helper' describe CommitRange do + describe 'modules' do + subject { described_class } + + it { is_expected.to include_module(Referable) } + end + let(:sha_from) { 'f3f85602' } let(:sha_to) { 'e86e1013' } @@ -11,10 +17,14 @@ describe CommitRange do expect { described_class.new("Foo") }.to raise_error end - describe 'modules' do - subject { described_class } + describe '#to_s' do + it 'is correct for three-dot syntax' do + expect(range.to_s).to eq "#{sha_from[0..7]}...#{sha_to[0..7]}" + end - it { is_expected.to include_module(Referable) } + it 'is correct for two-dot syntax' do + expect(range2.to_s).to eq "#{sha_from[0..7]}..#{sha_to[0..7]}" + end end describe '#to_reference' do @@ -34,16 +44,6 @@ describe CommitRange do end end - describe '#to_s' do - it 'is correct for three-dot syntax' do - expect(range.to_s).to eq "#{sha_from[0..7]}...#{sha_to[0..7]}" - end - - it 'is correct for two-dot syntax' do - expect(range2.to_s).to eq "#{sha_from[0..7]}..#{sha_to[0..7]}" - end - end - describe '#reference_title' do it 'returns the correct String for three-dot ranges' do expect(range.reference_title).to eq "Commits #{sha_from} through #{sha_to}" From 91eb346de6abd56fae469417a224bc2b2e7c7364 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 12 May 2015 17:59:30 -0400 Subject: [PATCH 112/255] Add invalidate_reference to ReferenceFilterSpecHelper --- spec/support/reference_filter_spec_helper.rb | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/spec/support/reference_filter_spec_helper.rb b/spec/support/reference_filter_spec_helper.rb index 06c39e1ada..d2766615cc 100644 --- a/spec/support/reference_filter_spec_helper.rb +++ b/spec/support/reference_filter_spec_helper.rb @@ -10,6 +10,25 @@ module ReferenceFilterSpecHelper Rails.application.routes.url_helpers end + # Modify a reference to make it invalid + # + # Commit SHAs get reversed, IDs get incremented by 1 + # + # reference - String reference to modify + # + # Returns a String + def invalidate_reference(reference) + if reference =~ /\A(.+)?.\d+\z/ + # Integer-based reference with optional project prefix + reference.gsub(/\d+\z/) { |i| i.to_i + 1 } + elsif reference =~ /\A(.+@)?(\h{6,40}\z)/ + # SHA-based reference with optional prefix + reference.gsub(/\h{6,40}\z/) { |v| v.reverse } + else + reference + end + end + # Perform `call` on the described class # # Automatically passes the current `project` value to the context if none is From 94af050117df20a661e03055a5002cae90282d6d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 12 May 2015 18:35:00 -0400 Subject: [PATCH 113/255] Use `to_reference` in reference filter specs --- .../commit_range_reference_filter_spec.rb | 33 ++++++++------ .../markdown/commit_reference_filter_spec.rb | 16 +++---- .../external_issue_reference_filter_spec.rb | 16 ++----- .../markdown/issue_reference_filter_spec.rb | 19 ++++---- .../markdown/label_reference_filter_spec.rb | 21 +++++---- .../merge_request_reference_filter_spec.rb | 13 +++--- .../markdown/snippet_reference_filter_spec.rb | 11 +++-- .../markdown/user_reference_filter_spec.rb | 45 +++++++++---------- spec/support/reference_filter_spec_helper.rb | 7 +-- 9 files changed, 86 insertions(+), 95 deletions(-) diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb index 1593088a09..d3695ee46d 100644 --- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb @@ -8,33 +8,36 @@ module Gitlab::Markdown let(:commit1) { project.commit } let(:commit2) { project.commit("HEAD~2") } + let(:range) { CommitRange.new("#{commit1.id}...#{commit2.id}") } + let(:range2) { CommitRange.new("#{commit1.id}..#{commit2.id}") } + it 'requires project context' do expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) end %w(pre code a style).each do |elem| it "ignores valid references contained inside '#{elem}' element" do - exp = act = "<#{elem}>Commit Range #{commit1.id}..#{commit2.id}" + exp = act = "<#{elem}>Commit Range #{range.to_reference}" expect(filter(act).to_html).to eq exp end end context 'internal reference' do - let(:reference) { "#{commit1.id}...#{commit2.id}" } - let(:reference2) { "#{commit1.id}..#{commit2.id}" } + let(:reference) { range.to_reference } + let(:reference2) { range2.to_reference } it 'links to a valid two-dot reference' do doc = filter("See #{reference2}") expect(doc.css('a').first.attr('href')). - to eq urls.namespace_project_compare_url(project.namespace, project, from: "#{commit1.id}^", to: commit2.id) + to eq urls.namespace_project_compare_url(project.namespace, project, range2.to_param) end it 'links to a valid three-dot reference' do doc = filter("See #{reference}") expect(doc.css('a').first.attr('href')). - to eq urls.namespace_project_compare_url(project.namespace, project, from: commit1.id, to: commit2.id) + to eq urls.namespace_project_compare_url(project.namespace, project, range.to_param) end it 'links to a valid short ID' do @@ -50,7 +53,7 @@ module Gitlab::Markdown it 'links with adjacent text' do doc = filter("See (#{reference}.)") - exp = Regexp.escape("#{commit1.short_id}...#{commit2.short_id}") + exp = Regexp.escape(range.to_s) expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/) end @@ -64,7 +67,7 @@ module Gitlab::Markdown it 'includes a title attribute' do doc = filter("See #{reference}") - expect(doc.css('a').first.attr('title')).to eq "Commits #{commit1.id} through #{commit2.id}" + expect(doc.css('a').first.attr('title')).to eq range.reference_title end it 'includes default classes' do @@ -94,9 +97,11 @@ module Gitlab::Markdown context 'cross-project reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:project, namespace: namespace) } - let(:commit1) { project.commit } - let(:commit2) { project.commit("HEAD~2") } - let(:reference) { "#{project2.path_with_namespace}@#{commit1.id}...#{commit2.id}" } + let(:reference) { range.to_reference(project) } + + before do + range.project = project2 + end context 'when user can access reference' do before { allow_cross_reference! } @@ -105,21 +110,21 @@ module Gitlab::Markdown doc = filter("See #{reference}") expect(doc.css('a').first.attr('href')). - to eq urls.namespace_project_compare_url(project2.namespace, project2, from: commit1.id, to: commit2.id) + to eq urls.namespace_project_compare_url(project2.namespace, project2, range.to_param) end it 'links with adjacent text' do doc = filter("Fixed (#{reference}.)") - exp = Regexp.escape("#{project2.path_with_namespace}@#{commit1.short_id}...#{commit2.short_id}") + exp = Regexp.escape("#{project2.to_reference}@#{range.to_s}") expect(doc.to_html).to match(/\(#{exp}<\/a>\.\)/) end it 'ignores invalid commit IDs on the referenced project' do - exp = act = "Fixed #{project2.path_with_namespace}##{commit1.id.reverse}...#{commit2.id}" + exp = act = "Fixed #{project2.to_reference}@#{commit1.id.reverse}...#{commit2.id}" expect(filter(act).to_html).to eq exp - exp = act = "Fixed #{project2.path_with_namespace}##{commit1.id}...#{commit2.id.reverse}" + exp = act = "Fixed #{project2.to_reference}@#{commit1.id}...#{commit2.id.reverse}" expect(filter(act).to_html).to eq exp end diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb index cc32a4fcf0..a0d2cd7e22 100644 --- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb @@ -8,8 +8,7 @@ module Gitlab::Markdown let(:commit) { project.commit } it 'requires project context' do - expect { described_class.call('Commit 1c002d', {}) }. - to raise_error(ArgumentError, /:project/) + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) end %w(pre code a style).each do |elem| @@ -47,10 +46,11 @@ module Gitlab::Markdown end it 'ignores invalid commit IDs' do - exp = act = "See #{reference.reverse}" + invalid = invalidate_reference(reference) + exp = act = "See #{invalid}" expect(project).to receive(:valid_repo?).and_return(true) - expect(project.repository).to receive(:commit).with(reference.reverse) + expect(project.repository).to receive(:commit).with(invalid) expect(filter(act).to_html).to eq exp end @@ -93,8 +93,8 @@ module Gitlab::Markdown context 'cross-project reference' do let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:project, namespace: namespace) } - let(:commit) { project.commit } - let(:reference) { "#{project2.path_with_namespace}@#{commit.id}" } + let(:commit) { project2.commit } + let(:reference) { commit.to_reference(project) } context 'when user can access reference' do before { allow_cross_reference! } @@ -109,12 +109,12 @@ module Gitlab::Markdown it 'links with adjacent text' do doc = filter("Fixed (#{reference}.)") - exp = Regexp.escape(project2.path_with_namespace) + exp = Regexp.escape(project2.to_reference) expect(doc.to_html).to match(/\(#{exp}@#{commit.short_id}<\/a>\.\)/) end it 'ignores invalid commit IDs on the referenced project' do - exp = act = "Committed #{project2.path_with_namespace}##{commit.id.reverse}" + exp = act = "Committed #{invalidate_reference(reference)}" expect(filter(act).to_html).to eq exp end diff --git a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb index b19bc125b9..bf9409589f 100644 --- a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb @@ -9,19 +9,18 @@ module Gitlab::Markdown end let(:project) { create(:jira_project) } - let(:issue) { double('issue', iid: 123) } context 'JIRA issue references' do - let(:reference) { "JIRA-#{issue.iid}" } + let(:issue) { ExternalIssue.new('JIRA-123', project) } + let(:reference) { issue.to_reference } it 'requires project context' do - expect { described_class.call('Issue JIRA-123', {}) }. - to raise_error(ArgumentError, /:project/) + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) end %w(pre code a style).each do |elem| it "ignores valid references contained inside '#{elem}' element" do - exp = act = "<#{elem}>Issue JIRA-#{issue.iid}" + exp = act = "<#{elem}>Issue #{reference}" expect(filter(act).to_html).to eq exp end end @@ -33,13 +32,6 @@ module Gitlab::Markdown expect(filter(act).to_html).to eq exp end - %w(pre code a style).each do |elem| - it "ignores references contained inside '#{elem}' element" do - exp = act = "<#{elem}>Issue #{reference}" - expect(filter(act).to_html).to eq exp - end - end - it 'links to a valid reference' do doc = filter("Issue #{reference}") expect(doc.css('a').first.attr('href')) diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb index 08382b3e7e..a838d7570c 100644 --- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb @@ -12,24 +12,23 @@ module Gitlab::Markdown let(:issue) { create(:issue, project: project) } it 'requires project context' do - expect { described_class.call('Issue #123', {}) }. - to raise_error(ArgumentError, /:project/) + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) end %w(pre code a style).each do |elem| it "ignores valid references contained inside '#{elem}' element" do - exp = act = "<#{elem}>Issue ##{issue.iid}" + exp = act = "<#{elem}>Issue #{issue.to_reference}" expect(filter(act).to_html).to eq exp end end context 'internal reference' do - let(:reference) { "##{issue.iid}" } + let(:reference) { issue.to_reference } it 'ignores valid references when using non-default tracker' do expect(project).to receive(:get_issue).with(issue.iid).and_return(nil) - exp = act = "Issue ##{issue.iid}" + exp = act = "Issue #{reference}" expect(filter(act).to_html).to eq exp end @@ -46,9 +45,9 @@ module Gitlab::Markdown end it 'ignores invalid issue IDs' do - exp = act = "Fixed ##{issue.iid + 1}" + invalid = invalidate_reference(reference) + exp = act = "Fixed #{invalid}" - expect(project).to receive(:get_issue).with(issue.iid + 1).and_return(nil) expect(filter(act).to_html).to eq exp end @@ -92,7 +91,7 @@ module Gitlab::Markdown let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:empty_project, namespace: namespace) } let(:issue) { create(:issue, project: project2) } - let(:reference) { "#{project2.path_with_namespace}##{issue.iid}" } + let(:reference) { issue.to_reference(project) } context 'when user can access reference' do before { allow_cross_reference! } @@ -101,7 +100,7 @@ module Gitlab::Markdown expect_any_instance_of(Project).to receive(:get_issue). with(issue.iid).and_return(nil) - exp = act = "Issue ##{issue.iid}" + exp = act = "Issue #{reference}" expect(filter(act).to_html).to eq exp end @@ -118,7 +117,7 @@ module Gitlab::Markdown end it 'ignores invalid issue IDs on the referenced project' do - exp = act = "Fixed #{project2.path_with_namespace}##{issue.iid + 1}" + exp = act = "Fixed #{invalidate_reference(reference)}" expect(filter(act).to_html).to eq exp end diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb index c4548e7431..250a44d575 100644 --- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb @@ -7,11 +7,10 @@ module Gitlab::Markdown let(:project) { create(:empty_project) } let(:label) { create(:label, project: project) } - let(:reference) { "~#{label.id}" } + let(:reference) { label.to_reference } it 'requires project context' do - expect { described_class.call('Label ~123', {}) }. - to raise_error(ArgumentError, /:project/) + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) end %w(pre code a style).each do |elem| @@ -36,7 +35,7 @@ module Gitlab::Markdown link = doc.css('a').first.attr('href') expect(link).not_to match %r(https?://) - expect(link).to eq urls.namespace_project_issues_url(project.namespace, project, label_name: label.name, only_path: true) + expect(link).to eq urls.namespace_project_issues_path(project.namespace, project, label_name: label.name) end it 'adds to the results hash' do @@ -70,7 +69,7 @@ module Gitlab::Markdown end it 'ignores invalid label IDs' do - exp = act = "Label ~#{label.id + 1}" + exp = act = "Label #{invalidate_reference(reference)}" expect(filter(act).to_html).to eq exp end @@ -78,7 +77,7 @@ module Gitlab::Markdown context 'String-based single-word references' do let(:label) { create(:label, name: 'gfm', project: project) } - let(:reference) { "~#{label.name}" } + let(:reference) { "#{Label.reference_prefix}#{label.name}" } it 'links to a valid reference' do doc = filter("See #{reference}") @@ -94,7 +93,7 @@ module Gitlab::Markdown end it 'ignores invalid label names' do - exp = act = "Label ~#{label.name.reverse}" + exp = act = "Label #{Label.reference_prefix}#{label.name.reverse}" expect(filter(act).to_html).to eq exp end @@ -104,7 +103,7 @@ module Gitlab::Markdown let(:label) { create(:label, name: 'gfm references', project: project) } context 'in single quotes' do - let(:reference) { "~'#{label.name}'" } + let(:reference) { "#{Label.reference_prefix}'#{label.name}'" } it 'links to a valid reference' do doc = filter("See #{reference}") @@ -120,14 +119,14 @@ module Gitlab::Markdown end it 'ignores invalid label names' do - exp = act = "Label ~'#{label.name.reverse}'" + exp = act = "Label #{Label.reference_prefix}'#{label.name.reverse}'" expect(filter(act).to_html).to eq exp end end context 'in double quotes' do - let(:reference) { %(~"#{label.name}") } + let(:reference) { %(#{Label.reference_prefix}"#{label.name}") } it 'links to a valid reference' do doc = filter("See #{reference}") @@ -143,7 +142,7 @@ module Gitlab::Markdown end it 'ignores invalid label names' do - exp = act = %(Label ~"#{label.name.reverse}") + exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}") expect(filter(act).to_html).to eq exp end diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb index d6e745114f..6aeb109360 100644 --- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb @@ -8,19 +8,18 @@ module Gitlab::Markdown let(:merge) { create(:merge_request, source_project: project) } it 'requires project context' do - expect { described_class.call('MergeRequest !123', {}) }. - to raise_error(ArgumentError, /:project/) + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) end %w(pre code a style).each do |elem| it "ignores valid references contained inside '#{elem}' element" do - exp = act = "<#{elem}>Merge !#{merge.iid}" + exp = act = "<#{elem}>Merge #{merge.to_reference}" expect(filter(act).to_html).to eq exp end end context 'internal reference' do - let(:reference) { "!#{merge.iid}" } + let(:reference) { merge.to_reference } it 'links to a valid reference' do doc = filter("See #{reference}") @@ -35,7 +34,7 @@ module Gitlab::Markdown end it 'ignores invalid merge IDs' do - exp = act = "Merge !#{merge.iid + 1}" + exp = act = "Merge #{invalidate_reference(reference)}" expect(filter(act).to_html).to eq exp end @@ -80,7 +79,7 @@ module Gitlab::Markdown let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:project, namespace: namespace) } let(:merge) { create(:merge_request, source_project: project2) } - let(:reference) { "#{project2.path_with_namespace}!#{merge.iid}" } + let(:reference) { merge.to_reference(project) } context 'when user can access reference' do before { allow_cross_reference! } @@ -99,7 +98,7 @@ module Gitlab::Markdown end it 'ignores invalid merge IDs on the referenced project' do - exp = act = "Merge #{project2.path_with_namespace}!#{merge.iid + 1}" + exp = act = "Merge #{invalidate_reference(reference)}" expect(filter(act).to_html).to eq exp end diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb index a4b331157a..07ece66e90 100644 --- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb @@ -6,11 +6,10 @@ module Gitlab::Markdown let(:project) { create(:empty_project) } let(:snippet) { create(:project_snippet, project: project) } - let(:reference) { "$#{snippet.id}" } + let(:reference) { snippet.to_reference } it 'requires project context' do - expect { described_class.call('Snippet $123', {}) }. - to raise_error(ArgumentError, /:project/) + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) end %w(pre code a style).each do |elem| @@ -34,7 +33,7 @@ module Gitlab::Markdown end it 'ignores invalid snippet IDs' do - exp = act = "Snippet $#{snippet.id + 1}" + exp = act = "Snippet #{invalidate_reference(reference)}" expect(filter(act).to_html).to eq exp end @@ -79,7 +78,7 @@ module Gitlab::Markdown let(:namespace) { create(:namespace, name: 'cross-reference') } let(:project2) { create(:empty_project, namespace: namespace) } let(:snippet) { create(:project_snippet, project: project2) } - let(:reference) { "#{project2.path_with_namespace}$#{snippet.id}" } + let(:reference) { snippet.to_reference(project) } context 'when user can access reference' do before { allow_cross_reference! } @@ -97,7 +96,7 @@ module Gitlab::Markdown end it 'ignores invalid snippet IDs on the referenced project' do - exp = act = "See #{project2.path_with_namespace}$#{snippet.id + 1}" + exp = act = "See #{invalidate_reference(reference)}" expect(filter(act).to_html).to eq exp end diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb index 922502ada3..0ecbdee9b9 100644 --- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb @@ -4,65 +4,63 @@ module Gitlab::Markdown describe UserReferenceFilter do include ReferenceFilterSpecHelper - let(:project) { create(:empty_project) } - let(:user) { create(:user) } + let(:project) { create(:empty_project) } + let(:user) { create(:user) } + let(:reference) { user.to_reference } it 'requires project context' do - expect { described_class.call('Example @mention', {}) }. - to raise_error(ArgumentError, /:project/) + expect { described_class.call('') }.to raise_error(ArgumentError, /:project/) end it 'ignores invalid users' do - exp = act = 'Hey @somebody' + exp = act = "Hey #{invalidate_reference(reference)}" expect(filter(act).to_html).to eq(exp) end %w(pre code a style).each do |elem| it "ignores valid references contained inside '#{elem}' element" do - exp = act = "<#{elem}>Hey @#{user.username}" + exp = act = "<#{elem}>Hey #{reference}" expect(filter(act).to_html).to eq exp end end context 'mentioning @all' do + let(:reference) { User.reference_prefix + 'all' } + before do project.team << [project.creator, :developer] end it 'supports a special @all mention' do - doc = filter("Hey @all") + doc = filter("Hey #{reference}") expect(doc.css('a').length).to eq 1 expect(doc.css('a').first.attr('href')) .to eq urls.namespace_project_url(project.namespace, project) end it 'adds to the results hash' do - result = pipeline_result('Hey @all') + result = pipeline_result("Hey #{reference}") expect(result[:references][:user]).to eq [project.creator] end end context 'mentioning a user' do - let(:reference) { "@#{user.username}" } - it 'links to a User' do doc = filter("Hey #{reference}") expect(doc.css('a').first.attr('href')).to eq urls.user_url(user) end - # TODO (rspeicher): This test might be overkill it 'links to a User with a period' do user = create(:user, name: 'alphA.Beta') - doc = filter("Hey @#{user.username}") + doc = filter("Hey #{user.to_reference}") expect(doc.css('a').length).to eq 1 end - # TODO (rspeicher): This test might be overkill it 'links to a User with an underscore' do user = create(:user, name: 'ping_pong_king') - doc = filter("Hey @#{user.username}") + doc = filter("Hey #{user.to_reference}") expect(doc.css('a').length).to eq 1 end @@ -73,10 +71,9 @@ module Gitlab::Markdown end context 'mentioning a group' do - let(:group) { create(:group) } - let(:user) { create(:user) } - - let(:reference) { "@#{group.name}" } + let(:group) { create(:group) } + let(:user) { create(:user) } + let(:reference) { group.to_reference } context 'that the current user can read' do before do @@ -108,23 +105,23 @@ module Gitlab::Markdown end it 'links with adjacent text' do - skip 'TODO (rspeicher): Re-enable when usernames can\'t end in periods.' - doc = filter("Mention me (@#{user.username}.)") - expect(doc.to_html).to match(/\(@#{user.username}<\/a>\.\)/) + skip "TODO (rspeicher): Re-enable when usernames can't end in periods." + doc = filter("Mention me (#{reference}.)") + expect(doc.to_html).to match(/\(#{reference}<\/a>\.\)/) end it 'includes default classes' do - doc = filter("Hey @#{user.username}") + doc = filter("Hey #{reference}") expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-project_member' end it 'includes an optional custom class' do - doc = filter("Hey @#{user.username}", reference_class: 'custom') + doc = filter("Hey #{reference}", reference_class: 'custom') expect(doc.css('a').first.attr('class')).to include 'custom' end it 'supports an :only_path context' do - doc = filter("Hey @#{user.username}", only_path: true) + doc = filter("Hey #{reference}", only_path: true) link = doc.css('a').first.attr('href') expect(link).not_to match %r(https?://) diff --git a/spec/support/reference_filter_spec_helper.rb b/spec/support/reference_filter_spec_helper.rb index d2766615cc..afbea55ab9 100644 --- a/spec/support/reference_filter_spec_helper.rb +++ b/spec/support/reference_filter_spec_helper.rb @@ -10,9 +10,10 @@ module ReferenceFilterSpecHelper Rails.application.routes.url_helpers end - # Modify a reference to make it invalid + # Modify a String reference to make it invalid # - # Commit SHAs get reversed, IDs get incremented by 1 + # Commit SHAs get reversed, IDs get incremented by 1, all other Strings get + # their word characters reversed. # # reference - String reference to modify # @@ -25,7 +26,7 @@ module ReferenceFilterSpecHelper # SHA-based reference with optional prefix reference.gsub(/\h{6,40}\z/) { |v| v.reverse } else - reference + reference.gsub(/\w+\z/) { |v| v.reverse } end end From b88da58cb6272a86b6df2e4efe392f10e689a6b2 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 14 May 2015 16:59:39 -0400 Subject: [PATCH 114/255] Add `reference_pattern` to Referable models --- app/models/commit.rb | 13 +++++++++++++ app/models/commit_range.rb | 18 ++++++++++++++++-- app/models/concerns/referable.rb | 10 ++++++++++ app/models/external_issue.rb | 13 +++++++++---- app/models/group.rb | 6 +++++- app/models/issue.rb | 14 ++++++++++++-- app/models/label.rb | 16 ++++++++++++++++ app/models/merge_request.rb | 10 ++++++++++ app/models/project.rb | 5 +++++ app/models/snippet.rb | 10 ++++++++++ app/models/user.rb | 8 ++++++++ .../markdown/commit_range_reference_filter.rb | 9 ++------- lib/gitlab/markdown/commit_reference_filter.rb | 11 ++--------- lib/gitlab/markdown/cross_project_reference.rb | 3 --- .../external_issue_reference_filter.rb | 7 ++----- lib/gitlab/markdown/issue_reference_filter.rb | 9 ++------- lib/gitlab/markdown/label_reference_filter.rb | 17 ++--------------- .../markdown/merge_request_reference_filter.rb | 9 ++------- .../markdown/snippet_reference_filter.rb | 9 ++------- lib/gitlab/markdown/user_reference_filter.rb | 7 ++----- 20 files changed, 130 insertions(+), 74 deletions(-) diff --git a/app/models/commit.rb b/app/models/commit.rb index 085f4e6398..2c244fc041 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -62,6 +62,19 @@ class Commit (self.class === other) && (raw == other.raw) end + def self.reference_prefix + '@' + end + + # Pattern used to extract commit references from text + # + # The SHA can be between 6 and 40 hex characters. + # + # This pattern supports cross-project references. + def self.reference_pattern + %r{(?:#{Project.reference_pattern}#{reference_prefix})?(?\h{6,40})} + end + def to_reference(from_project = nil) if cross_project_reference?(from_project) "#{project.to_reference}@#{id}" diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb index fb1f6d09be..86fc9eb01a 100644 --- a/app/models/commit_range.rb +++ b/app/models/commit_range.rb @@ -29,10 +29,24 @@ class CommitRange # See `exclude_start?` attr_reader :exclude_start - # The beginning and ending SHA sums can be between 6 and 40 hex characters, - # and the range selection can be double- or triple-dot. + # The beginning and ending SHAs can be between 6 and 40 hex characters, and + # the range notation can be double- or triple-dot. PATTERN = /\h{6,40}\.{2,3}\h{6,40}/ + def self.reference_prefix + '@' + end + + # Pattern used to extract commit range references from text + # + # This pattern supports cross-project references. + def self.reference_pattern + %r{ + (?:#{Project.reference_pattern}#{reference_prefix})? + (?#{PATTERN}) + }x + end + # Initialize a CommitRange # # range_string - The String commit range. diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb index b41df301c3..e3c1c6d268 100644 --- a/app/models/concerns/referable.rb +++ b/app/models/concerns/referable.rb @@ -35,6 +35,16 @@ module Referable def reference_prefix '' end + + # Regexp pattern used to match references to this object + # + # This must be overridden by the including class. + # + # Returns Regexp + def reference_pattern + raise NotImplementedError, + %Q{#{self} does not implement "reference_pattern"} + end end private diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb index 6fda4a2ab7..49f6c95e04 100644 --- a/app/models/external_issue.rb +++ b/app/models/external_issue.rb @@ -9,10 +9,6 @@ class ExternalIssue @issue_identifier.to_s end - def to_reference(_from_project = nil) - id - end - def id @issue_identifier.to_s end @@ -32,4 +28,13 @@ class ExternalIssue def project @project end + + # Pattern used to extract `JIRA-123` issue references from text + def self.reference_pattern + %r{(?([A-Z\-]+-)\d+)} + end + + def to_reference(_from_project = nil) + id + end end diff --git a/app/models/group.rb b/app/models/group.rb index 33d72e0d9e..b4e908c560 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -40,7 +40,11 @@ class Group < Namespace end def reference_prefix - '@' + User.reference_prefix + end + + def reference_pattern + User.reference_pattern end end diff --git a/app/models/issue.rb b/app/models/issue.rb index 31803b57b3..ea6b9329b0 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -50,12 +50,22 @@ class Issue < ActiveRecord::Base state :closed end + def hook_attrs + attributes + end + def self.reference_prefix '#' end - def hook_attrs - attributes + # Pattern used to extract `#123` issue references from text + # + # This pattern supports cross-project references. + def self.reference_pattern + %r{ + #{Project.reference_pattern}? + #{Regexp.escape(reference_prefix)}(?\d+) + }x end def to_reference(from_project = nil) diff --git a/app/models/label.rb b/app/models/label.rb index 013e6bf597..8980049cef 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -40,6 +40,22 @@ class Label < ActiveRecord::Base '~' end + # Pattern used to extract label references from text + # + # TODO (rspeicher): Limit to double quotes (meh) or disallow single quotes in label names (bad). + def self.reference_pattern + %r{ + #{reference_prefix} + (?: + (?\d+) | # Integer-based label ID, or + (? + [A-Za-z0-9_-]+ | # String-based single-word label title + ['"][^&\?,]+['"] # String-based multi-word label surrounded in quotes + ) + ) + }x + end + # Returns the String necessary to reference this Label in Markdown # # format - Symbol format to use (default: :id, optional: :name) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 60b0ce6c01..6c90d09b86 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -140,6 +140,16 @@ class MergeRequest < ActiveRecord::Base '!' end + # Pattern used to extract `!123` merge request references from text + # + # This pattern supports cross-project references. + def self.reference_pattern + %r{ + #{Project.reference_pattern}? + #{Regexp.escape(reference_prefix)}(?\d+) + }x + end + def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{iid}" diff --git a/app/models/project.rb b/app/models/project.rb index c943114449..3c9f0dad28 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -248,6 +248,11 @@ class Project < ActiveRecord::Base order_by(method) end end + + def reference_pattern + name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR + %r{(?#{name_pattern}/#{name_pattern})} + end end def team diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 8c3167833a..d1619071f4 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -56,6 +56,16 @@ class Snippet < ActiveRecord::Base '$' end + # Pattern used to extract `$123` snippet references from text + # + # This pattern supports cross-project references. + def self.reference_pattern + %r{ + #{Project.reference_pattern}? + #{Regexp.escape(reference_prefix)}(?\d+) + }x + end + def to_reference(from_project = nil) reference = "#{self.class.reference_prefix}#{id}" diff --git a/app/models/user.rb b/app/models/user.rb index f546dc015c..50ca4bc5ac 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -253,6 +253,14 @@ class User < ActiveRecord::Base def reference_prefix '@' end + + # Pattern used to extract `@user` user references from text + def reference_pattern + %r{ + #{Regexp.escape(reference_prefix)} + (?#{Gitlab::Regex::NAMESPACE_REGEX_STR}) + }x + end end # diff --git a/lib/gitlab/markdown/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb index 8764f7e474..61591a9914 100644 --- a/lib/gitlab/markdown/commit_range_reference_filter.rb +++ b/lib/gitlab/markdown/commit_range_reference_filter.rb @@ -19,7 +19,7 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(COMMIT_RANGE_PATTERN) do |match| + text.gsub(CommitRange.reference_pattern) do |match| yield match, $~[:commit_range], $~[:project] end end @@ -30,13 +30,8 @@ module Gitlab @commit_map = {} end - # Pattern used to extract commit range references from text - # - # This pattern supports cross-project references. - COMMIT_RANGE_PATTERN = /(#{PROJECT_PATTERN}@)?(?#{CommitRange::PATTERN})/ - def call - replace_text_nodes_matching(COMMIT_RANGE_PATTERN) do |content| + replace_text_nodes_matching(CommitRange.reference_pattern) do |content| commit_range_link_filter(content) end end diff --git a/lib/gitlab/markdown/commit_reference_filter.rb b/lib/gitlab/markdown/commit_reference_filter.rb index b20b29f5d0..f6932e76e7 100644 --- a/lib/gitlab/markdown/commit_reference_filter.rb +++ b/lib/gitlab/markdown/commit_reference_filter.rb @@ -19,20 +19,13 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(COMMIT_PATTERN) do |match| + text.gsub(Commit.reference_pattern) do |match| yield match, $~[:commit], $~[:project] end end - # Pattern used to extract commit references from text - # - # The SHA1 sum can be between 6 and 40 hex characters. - # - # This pattern supports cross-project references. - COMMIT_PATTERN = /(#{PROJECT_PATTERN}@)?(?\h{6,40})/ - def call - replace_text_nodes_matching(COMMIT_PATTERN) do |content| + replace_text_nodes_matching(Commit.reference_pattern) do |content| commit_link_filter(content) end end diff --git a/lib/gitlab/markdown/cross_project_reference.rb b/lib/gitlab/markdown/cross_project_reference.rb index c436fabd65..66c256c510 100644 --- a/lib/gitlab/markdown/cross_project_reference.rb +++ b/lib/gitlab/markdown/cross_project_reference.rb @@ -3,9 +3,6 @@ module Gitlab # Common methods for ReferenceFilters that support an optional cross-project # reference. module CrossProjectReference - NAMING_PATTERN = Gitlab::Regex::NAMESPACE_REGEX_STR - PROJECT_PATTERN = "(?#{NAMING_PATTERN}/#{NAMING_PATTERN})" - # Given a cross-project reference string, get the Project record # # Defaults to value of `context[:project]` if: diff --git a/lib/gitlab/markdown/external_issue_reference_filter.rb b/lib/gitlab/markdown/external_issue_reference_filter.rb index 0fc3f4cca0..2e74c6e45e 100644 --- a/lib/gitlab/markdown/external_issue_reference_filter.rb +++ b/lib/gitlab/markdown/external_issue_reference_filter.rb @@ -16,19 +16,16 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(ISSUE_PATTERN) do |match| + text.gsub(ExternalIssue.reference_pattern) do |match| yield match, $~[:issue] end end - # Pattern used to extract `JIRA-123` issue references from text - ISSUE_PATTERN = /(?([A-Z\-]+-)\d+)/ - def call # Early return if the project isn't using an external tracker return doc if project.nil? || project.default_issues_tracker? - replace_text_nodes_matching(ISSUE_PATTERN) do |content| + replace_text_nodes_matching(ExternalIssue.reference_pattern) do |content| issue_link_filter(content) end end diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/issue_reference_filter.rb index 1e88561516..2815626e24 100644 --- a/lib/gitlab/markdown/issue_reference_filter.rb +++ b/lib/gitlab/markdown/issue_reference_filter.rb @@ -20,18 +20,13 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(ISSUE_PATTERN) do |match| + text.gsub(Issue.reference_pattern) do |match| yield match, $~[:issue].to_i, $~[:project] end end - # Pattern used to extract `#123` issue references from text - # - # This pattern supports cross-project references. - ISSUE_PATTERN = /#{PROJECT_PATTERN}?\#(?([a-zA-Z\-]+-)?\d+)/ - def call - replace_text_nodes_matching(ISSUE_PATTERN) do |content| + replace_text_nodes_matching(Issue.reference_pattern) do |content| issue_link_filter(content) end end diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb index 1a77becee8..9f8c85b701 100644 --- a/lib/gitlab/markdown/label_reference_filter.rb +++ b/lib/gitlab/markdown/label_reference_filter.rb @@ -15,26 +15,13 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(LABEL_PATTERN) do |match| + text.gsub(Label.reference_pattern) do |match| yield match, $~[:label_id].to_i, $~[:label_name] end end - # Pattern used to extract label references from text - # - # TODO (rspeicher): Limit to double quotes (meh) or disallow single quotes in label names (bad). - LABEL_PATTERN = %r{ - ~( - (?\d+) | # Integer-based label ID, or - (? - [A-Za-z0-9_-]+ | # String-based single-word label title - ['"][^&\?,]+['"] # String-based multi-word label surrounded in quotes - ) - ) - }x - def call - replace_text_nodes_matching(LABEL_PATTERN) do |content| + replace_text_nodes_matching(Label.reference_pattern) do |content| label_link_filter(content) end end diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb index 740d72abb3..fddc050635 100644 --- a/lib/gitlab/markdown/merge_request_reference_filter.rb +++ b/lib/gitlab/markdown/merge_request_reference_filter.rb @@ -20,18 +20,13 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(MERGE_REQUEST_PATTERN) do |match| + text.gsub(MergeRequest.reference_pattern) do |match| yield match, $~[:merge_request].to_i, $~[:project] end end - # Pattern used to extract `!123` merge request references from text - # - # This pattern supports cross-project references. - MERGE_REQUEST_PATTERN = /#{PROJECT_PATTERN}?!(?\d+)/ - def call - replace_text_nodes_matching(MERGE_REQUEST_PATTERN) do |content| + replace_text_nodes_matching(MergeRequest.reference_pattern) do |content| merge_request_link_filter(content) end end diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/gitlab/markdown/snippet_reference_filter.rb index 64a0a2696f..f22f08de27 100644 --- a/lib/gitlab/markdown/snippet_reference_filter.rb +++ b/lib/gitlab/markdown/snippet_reference_filter.rb @@ -20,18 +20,13 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(SNIPPET_PATTERN) do |match| + text.gsub(Snippet.reference_pattern) do |match| yield match, $~[:snippet].to_i, $~[:project] end end - # Pattern used to extract `$123` snippet references from text - # - # This pattern supports cross-project references. - SNIPPET_PATTERN = /#{PROJECT_PATTERN}?\$(?\d+)/ - def call - replace_text_nodes_matching(SNIPPET_PATTERN) do |content| + replace_text_nodes_matching(Snippet.reference_pattern) do |content| snippet_link_filter(content) end end diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb index 28ec041b1d..ca7fd7b033 100644 --- a/lib/gitlab/markdown/user_reference_filter.rb +++ b/lib/gitlab/markdown/user_reference_filter.rb @@ -16,16 +16,13 @@ module Gitlab # # Returns a String replaced with the return of the block. def self.references_in(text) - text.gsub(USER_PATTERN) do |match| + text.gsub(User.reference_pattern) do |match| yield match, $~[:user] end end - # Pattern used to extract `@user` user references from text - USER_PATTERN = /@(?#{Gitlab::Regex::NAMESPACE_REGEX_STR})/ - def call - replace_text_nodes_matching(USER_PATTERN) do |content| + replace_text_nodes_matching(User.reference_pattern) do |content| user_link_filter(content) end end From 1a277c502590fbdb652f73ec3c0974ff70ea5416 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 14 May 2015 17:09:02 -0400 Subject: [PATCH 115/255] Minor documentation updates --- app/models/concerns/mentionable.rb | 2 +- app/models/concerns/referable.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index f28b20afd8..9b29988947 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -25,7 +25,7 @@ module Mentionable # By default this will be the class name and the result of calling # `to_reference` on the object. def gfm_reference - # Convert "MergeRequest" to "merge request" + # "MergeRequest" > "merge_request" > "Merge request" > "merge request" friendly_name = self.class.to_s.underscore.humanize.downcase "#{friendly_name} #{to_reference}" diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb index e3c1c6d268..5f57846b58 100644 --- a/app/models/concerns/referable.rb +++ b/app/models/concerns/referable.rb @@ -40,7 +40,7 @@ module Referable # # This must be overridden by the including class. # - # Returns Regexp + # Returns a Regexp def reference_pattern raise NotImplementedError, %Q{#{self} does not implement "reference_pattern"} From 35853033b9516aeffb6d341589406b00016947c3 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 14 May 2015 20:15:06 -0400 Subject: [PATCH 116/255] Fix for Snippets with a project --- app/views/shared/snippets/_form.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index 9610f9ce41..2feeeecc48 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -29,7 +29,7 @@ - else = f.submit 'Save', class: "btn-save btn" - - if @snippet.respond_to?(:project) + - if @snippet.project_id = link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel" - else = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel" From 81a09bc74cb997d3465f98cdcb72cacd413c31cd Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 15 May 2015 16:07:25 -0400 Subject: [PATCH 117/255] Support only double quotes for multi-word label references --- app/models/label.rb | 10 ++-- lib/gitlab/markdown/label_reference_filter.rb | 3 +- .../markdown/label_reference_filter_spec.rb | 54 +++++-------------- spec/models/label_spec.rb | 19 +++++-- 4 files changed, 33 insertions(+), 53 deletions(-) diff --git a/app/models/label.rb b/app/models/label.rb index 8980049cef..230631b518 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -41,16 +41,14 @@ class Label < ActiveRecord::Base end # Pattern used to extract label references from text - # - # TODO (rspeicher): Limit to double quotes (meh) or disallow single quotes in label names (bad). def self.reference_pattern %r{ #{reference_prefix} (?: - (?\d+) | # Integer-based label ID, or + (?\d+) | # Integer-based label ID, or (? - [A-Za-z0-9_-]+ | # String-based single-word label title - ['"][^&\?,]+['"] # String-based multi-word label surrounded in quotes + [A-Za-z0-9_-]+ | # String-based single-word label title, or + "[^&\?,]+" # String-based multi-word label surrounded in quotes ) ) }x @@ -70,7 +68,7 @@ class Label < ActiveRecord::Base # # Returns a String def to_reference(format = :id) - if format == :name + if format == :name && !name.include?('"') %(#{self.class.reference_prefix}"#{name}") else "#{self.class.reference_prefix}#{id}" diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb index 9f8c85b701..e022ca69c9 100644 --- a/lib/gitlab/markdown/label_reference_filter.rb +++ b/lib/gitlab/markdown/label_reference_filter.rb @@ -72,8 +72,7 @@ module Gitlab # Returns a Hash. def label_params(id, name) if name - # TODO (rspeicher): Don't strip single quotes if we decide to only use double quotes for surrounding. - { name: name.tr('\'"', '') } + { name: name.tr('"', '') } else { id: id } end diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb index 250a44d575..41987f57bc 100644 --- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb @@ -100,52 +100,26 @@ module Gitlab::Markdown end context 'String-based multi-word references in quotes' do - let(:label) { create(:label, name: 'gfm references', project: project) } + let(:label) { create(:label, name: 'gfm references', project: project) } + let(:reference) { label.to_reference(:name) } - context 'in single quotes' do - let(:reference) { "#{Label.reference_prefix}'#{label.name}'" } + it 'links to a valid reference' do + doc = filter("See #{reference}") - it 'links to a valid reference' do - doc = filter("See #{reference}") - - expect(doc.css('a').first.attr('href')).to eq urls. - namespace_project_issues_url(project.namespace, project, label_name: label.name) - expect(doc.text).to eq 'See gfm references' - end - - it 'links with adjacent text' do - doc = filter("Label (#{reference}.)") - expect(doc.to_html).to match(%r(\(#{label.name}\.\))) - end - - it 'ignores invalid label names' do - exp = act = "Label #{Label.reference_prefix}'#{label.name.reverse}'" - - expect(filter(act).to_html).to eq exp - end + expect(doc.css('a').first.attr('href')).to eq urls. + namespace_project_issues_url(project.namespace, project, label_name: label.name) + expect(doc.text).to eq 'See gfm references' end - context 'in double quotes' do - let(:reference) { %(#{Label.reference_prefix}"#{label.name}") } + it 'links with adjacent text' do + doc = filter("Label (#{reference}.)") + expect(doc.to_html).to match(%r(\(#{label.name}\.\))) + end - it 'links to a valid reference' do - doc = filter("See #{reference}") + it 'ignores invalid label names' do + exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}") - expect(doc.css('a').first.attr('href')).to eq urls. - namespace_project_issues_url(project.namespace, project, label_name: label.name) - expect(doc.text).to eq 'See gfm references' - end - - it 'links with adjacent text' do - doc = filter("Label (#{reference}.)") - expect(doc.to_html).to match(%r(\(#{label.name}\.\))) - end - - it 'ignores invalid label names' do - exp = act = %(Label #{Label.reference_prefix}"#{label.name.reverse}") - - expect(filter(act).to_html).to eq exp - end + expect(filter(act).to_html).to eq exp end end diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index a13f9ac926..6518213d71 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -55,13 +55,22 @@ describe Label do end describe '#to_reference' do - it 'returns a String reference to the object' do - expect(label.to_reference).to eq "~#{label.id}" - expect(label.to_reference(double)).to eq "~#{label.id}" + context 'using id' do + it 'returns a String reference to the object' do + expect(label.to_reference).to eq "~#{label.id}" + expect(label.to_reference(double('project'))).to eq "~#{label.id}" + end end - it 'returns a String reference to the object using its name' do - expect(label.to_reference(:name)).to eq %(~"#{label.name}") + context 'using name' do + it 'returns a String reference to the object' do + expect(label.to_reference(:name)).to eq %(~"#{label.name}") + end + + it 'uses id when name contains double quote' do + label = create(:label, name: %q{"irony"}) + expect(label.to_reference(:name)).to eq "~#{label.id}" + end end end end From 5cc9b17b8a7d7a8081fa60ea75f6cf423fbddbc5 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 15 May 2015 16:09:17 -0400 Subject: [PATCH 118/255] Make `cross_project_reference?` less magical --- app/models/concerns/referable.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb index 5f57846b58..cced66cc1e 100644 --- a/app/models/concerns/referable.rb +++ b/app/models/concerns/referable.rb @@ -42,8 +42,7 @@ module Referable # # Returns a Regexp def reference_pattern - raise NotImplementedError, - %Q{#{self} does not implement "reference_pattern"} + raise NotImplementedError, "#{self} does not implement #{__method__}" end end @@ -53,10 +52,10 @@ module Referable # # from_project - Refering Project object def cross_project_reference?(from_project) - if Project === self + if self.is_a?(Project) self != from_project else - from_project && project && project != from_project + from_project && self.project && self.project != from_project end end end From 1a9da9178cfd25190997b621e428a5c7ce467cd1 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 15 May 2015 16:10:55 -0400 Subject: [PATCH 119/255] Surround Project.reference_pattern in parenthesis inside other patterns --- app/models/commit.rb | 5 ++++- app/models/issue.rb | 2 +- app/models/merge_request.rb | 2 +- app/models/snippet.rb | 2 +- 4 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/models/commit.rb b/app/models/commit.rb index 2c244fc041..f02fe24054 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -72,7 +72,10 @@ class Commit # # This pattern supports cross-project references. def self.reference_pattern - %r{(?:#{Project.reference_pattern}#{reference_prefix})?(?\h{6,40})} + %r{ + (?:#{Project.reference_pattern}#{reference_prefix})? + (?\h{6,40}) + }x end def to_reference(from_project = nil) diff --git a/app/models/issue.rb b/app/models/issue.rb index ea6b9329b0..2456b7d0dc 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -63,7 +63,7 @@ class Issue < ActiveRecord::Base # This pattern supports cross-project references. def self.reference_pattern %r{ - #{Project.reference_pattern}? + (#{Project.reference_pattern})? #{Regexp.escape(reference_prefix)}(?\d+) }x end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 6c90d09b86..c57016dd6a 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -145,7 +145,7 @@ class MergeRequest < ActiveRecord::Base # This pattern supports cross-project references. def self.reference_pattern %r{ - #{Project.reference_pattern}? + (#{Project.reference_pattern})? #{Regexp.escape(reference_prefix)}(?\d+) }x end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index d1619071f4..3ab9e834c6 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -61,7 +61,7 @@ class Snippet < ActiveRecord::Base # This pattern supports cross-project references. def self.reference_pattern %r{ - #{Project.reference_pattern}? + (#{Project.reference_pattern})? #{Regexp.escape(reference_prefix)}(?\d+) }x end From 68f74aa82690ca83faf81654737f7d0caefdfbd3 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 15 May 2015 16:12:23 -0400 Subject: [PATCH 120/255] Add a note about the commented-out test in Markdown Feature --- spec/features/markdown_spec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index 0fc144462f..d695417466 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -66,6 +66,10 @@ describe 'GitLab Markdown' do @doc.at_css("##{id}").parent.next_element end + # Sometimes it can be useful to see the parsed output of the Markdown document + # for debugging. Uncomment this block to write the output to + # tmp/capybara/markdown_spec.html. + # # it 'writes to a file' do # File.open(Rails.root.join('tmp/capybara/markdown_spec.html'), 'w') do |file| # file.puts @md From 5a9c5520d9e63a38ed8b839be8430a5b5815da67 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 21 May 2015 16:35:15 -0400 Subject: [PATCH 121/255] Make use of to_reference in more specs --- lib/gitlab/closing_issue_extractor.rb | 2 +- .../external_issue_reference_filter.rb | 2 +- lib/gitlab/markdown/issue_reference_filter.rb | 2 +- .../merge_request_reference_filter.rb | 2 +- .../markdown/snippet_reference_filter.rb | 2 +- lib/gitlab/markdown/user_reference_filter.rb | 9 ++- .../features/gitlab_flavored_markdown_spec.rb | 34 ++++----- spec/helpers/gitlab_markdown_helper_spec.rb | 10 +-- .../gitlab/closing_issue_extractor_spec.rb | 70 +++++++++---------- spec/lib/gitlab/reference_extractor_spec.rb | 4 +- spec/models/merge_request_spec.rb | 2 +- spec/services/system_note_service_spec.rb | 10 +-- 12 files changed, 76 insertions(+), 73 deletions(-) diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb index ab184d95c0..aeec595782 100644 --- a/lib/gitlab/closing_issue_extractor.rb +++ b/lib/gitlab/closing_issue_extractor.rb @@ -8,7 +8,7 @@ module Gitlab def closed_by_message(message) return [] if message.nil? - + closing_statements = message.scan(ISSUE_CLOSING_REGEX). map { |ref| ref[0] }.join(" ") diff --git a/lib/gitlab/markdown/external_issue_reference_filter.rb b/lib/gitlab/markdown/external_issue_reference_filter.rb index 2e74c6e45e..afd28dd8cf 100644 --- a/lib/gitlab/markdown/external_issue_reference_filter.rb +++ b/lib/gitlab/markdown/external_issue_reference_filter.rb @@ -48,7 +48,7 @@ module Gitlab %(#{issue}) + class="#{klass}">#{match}) end end diff --git a/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/issue_reference_filter.rb index 2815626e24..dea04761ea 100644 --- a/lib/gitlab/markdown/issue_reference_filter.rb +++ b/lib/gitlab/markdown/issue_reference_filter.rb @@ -52,7 +52,7 @@ module Gitlab %(#{project_ref}##{id}) + class="#{klass}">#{match}) else match end diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb index fddc050635..8077981948 100644 --- a/lib/gitlab/markdown/merge_request_reference_filter.rb +++ b/lib/gitlab/markdown/merge_request_reference_filter.rb @@ -52,7 +52,7 @@ module Gitlab %(#{project_ref}!#{id}) + class="#{klass}">#{match}) else match end diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/gitlab/markdown/snippet_reference_filter.rb index f22f08de27..174ba58af6 100644 --- a/lib/gitlab/markdown/snippet_reference_filter.rb +++ b/lib/gitlab/markdown/snippet_reference_filter.rb @@ -52,7 +52,7 @@ module Gitlab %(#{project_ref}$#{id}) + class="#{klass}">#{match}) else match end diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb index ca7fd7b033..c997295718 100644 --- a/lib/gitlab/markdown/user_reference_filter.rb +++ b/lib/gitlab/markdown/user_reference_filter.rb @@ -65,7 +65,8 @@ module Gitlab url = urls.namespace_project_url(project.namespace, project, only_path: context[:only_path]) - %(@all) + text = User.reference_prefix + 'all' + %(#{text}) end def link_to_namespace(namespace) @@ -83,7 +84,8 @@ module Gitlab url = urls.group_url(group, only_path: context[:only_path]) - %(@#{group}) + text = Group.reference_prefix + group + %(#{text}) end def link_to_user(user, namespace) @@ -91,7 +93,8 @@ module Gitlab url = urls.user_url(user, only_path: context[:only_path]) - %(@#{user}) + text = User.reference_prefix + user + %(#{text}) end def user_can_reference_group?(group) diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb index 133beba7b9..16d1ca55f8 100644 --- a/spec/features/gitlab_flavored_markdown_spec.rb +++ b/spec/features/gitlab_flavored_markdown_spec.rb @@ -11,7 +11,7 @@ describe "GitLab Flavored Markdown", feature: true do end before do - Commit.any_instance.stub(title: "fix ##{issue.iid}\n\nask @#{fred.username} for details") + Commit.any_instance.stub(title: "fix #{issue.to_reference}\n\nask #{fred.to_reference} for details") end let(:commit) { project.commit } @@ -25,25 +25,25 @@ describe "GitLab Flavored Markdown", feature: true do it "should render title in commits#index" do visit namespace_project_commits_path(project.namespace, project, 'master', limit: 1) - expect(page).to have_link("##{issue.iid}") + expect(page).to have_link(issue.to_reference) end it "should render title in commits#show" do visit namespace_project_commit_path(project.namespace, project, commit) - expect(page).to have_link("##{issue.iid}") + expect(page).to have_link(issue.to_reference) end it "should render description in commits#show" do visit namespace_project_commit_path(project.namespace, project, commit) - expect(page).to have_link("@#{fred.username}") + expect(page).to have_link(fred.to_reference) end it "should render title in repositories#branches" do visit namespace_project_branches_path(project.namespace, project) - expect(page).to have_link("##{issue.iid}") + expect(page).to have_link(issue.to_reference) end end @@ -57,20 +57,20 @@ describe "GitLab Flavored Markdown", feature: true do author: @user, assignee: @user, project: project, - title: "fix ##{@other_issue.iid}", - description: "ask @#{fred.username} for details") + title: "fix #{@other_issue.to_reference}", + description: "ask #{fred.to_reference} for details") end it "should render subject in issues#index" do visit namespace_project_issues_path(project.namespace, project) - expect(page).to have_link("##{@other_issue.iid}") + expect(page).to have_link(@other_issue.to_reference) end it "should render subject in issues#show" do visit namespace_project_issue_path(project.namespace, project, @issue) - expect(page).to have_link("##{@other_issue.iid}") + expect(page).to have_link(@other_issue.to_reference) end it "should render details in issues#show" do @@ -83,19 +83,19 @@ describe "GitLab Flavored Markdown", feature: true do describe "for merge requests" do before do - @merge_request = create(:merge_request, source_project: project, target_project: project, title: "fix ##{issue.iid}") + @merge_request = create(:merge_request, source_project: project, target_project: project, title: "fix #{issue.to_reference}") end it "should render title in merge_requests#index" do visit namespace_project_merge_requests_path(project.namespace, project) - expect(page).to have_link("##{issue.iid}") + expect(page).to have_link(issue.to_reference) end it "should render title in merge_requests#show" do visit namespace_project_merge_request_path(project.namespace, project, @merge_request) - expect(page).to have_link("##{issue.iid}") + expect(page).to have_link(issue.to_reference) end end @@ -104,26 +104,26 @@ describe "GitLab Flavored Markdown", feature: true do before do @milestone = create(:milestone, project: project, - title: "fix ##{issue.iid}", - description: "ask @#{fred.username} for details") + title: "fix #{issue.to_reference}", + description: "ask #{fred.to_reference} for details") end it "should render title in milestones#index" do visit namespace_project_milestones_path(project.namespace, project) - expect(page).to have_link("##{issue.iid}") + expect(page).to have_link(issue.to_reference) end it "should render title in milestones#show" do visit namespace_project_milestone_path(project.namespace, project, @milestone) - expect(page).to have_link("##{issue.iid}") + expect(page).to have_link(issue.to_reference) end it "should render description in milestones#show" do visit namespace_project_milestone_path(project.namespace, project, @milestone) - expect(page).to have_link("@#{fred.username}") + expect(page).to have_link(fred.to_reference) end end end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index 0d0418f84a..d0b200a9ff 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -26,7 +26,7 @@ describe GitlabMarkdownHelper do end describe "referencing multiple objects" do - let(:actual) { "!#{merge_request.iid} -> #{commit.id} -> ##{issue.iid}" } + let(:actual) { "#{merge_request.to_reference} -> #{commit.to_reference} -> #{issue.to_reference}" } it "should link to the merge request" do expected = namespace_project_merge_request_path(project.namespace, project, merge_request) @@ -50,7 +50,7 @@ describe GitlabMarkdownHelper do let(:issues) { create_list(:issue, 2, project: project) } it 'should handle references nested in links with all the text' do - actual = link_to_gfm("This should finally fix ##{issues[0].iid} and ##{issues[1].iid} for real", commit_path) + actual = link_to_gfm("This should finally fix #{issues[0].to_reference} and #{issues[1].to_reference} for real", commit_path) doc = Nokogiri::HTML.parse(actual) # Make sure we didn't create invalid markup @@ -63,7 +63,7 @@ describe GitlabMarkdownHelper do # First issue link expect(doc.css('a')[1].attr('href')). to eq namespace_project_issue_path(project.namespace, project, issues[0]) - expect(doc.css('a')[1].text).to eq "##{issues[0].iid}" + expect(doc.css('a')[1].text).to eq issues[0].to_reference # Internal commit link expect(doc.css('a')[2].attr('href')).to eq commit_path @@ -72,7 +72,7 @@ describe GitlabMarkdownHelper do # Second issue link expect(doc.css('a')[3].attr('href')). to eq namespace_project_issue_path(project.namespace, project, issues[1]) - expect(doc.css('a')[3].text).to eq "##{issues[1].iid}" + expect(doc.css('a')[3].text).to eq issues[1].to_reference # Trailing commit link expect(doc.css('a')[4].attr('href')).to eq commit_path @@ -90,7 +90,7 @@ describe GitlabMarkdownHelper do end it "escapes HTML passed in as the body" do - actual = "This is a

test

- see ##{issues[0].iid}" + actual = "This is a

test

- see #{issues[0].to_reference}" expect(link_to_gfm(actual, commit_path)). to match('<h1>test</h1>') end diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index cb7b0fbb89..63d474c0d1 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -1,131 +1,131 @@ require 'spec_helper' describe Gitlab::ClosingIssueExtractor do - let(:project) { create(:project) } - let(:issue) { create(:issue, project: project) } - let(:iid1) { issue.iid } + let(:project) { create(:project) } + let(:issue) { create(:issue, project: project) } + let(:reference) { issue.to_reference } subject { described_class.new(project, project.creator) } describe "#closed_by_message" do context 'with a single reference' do it do - message = "Awesome commit (Closes ##{iid1})" + message = "Awesome commit (Closes #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "Awesome commit (closes ##{iid1})" + message = "Awesome commit (closes #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "Closed ##{iid1}" + message = "Closed #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "closed ##{iid1}" + message = "closed #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "Closing ##{iid1}" + message = "Closing #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "closing ##{iid1}" + message = "closing #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "Close ##{iid1}" + message = "Close #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "close ##{iid1}" + message = "close #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "Awesome commit (Fixes ##{iid1})" + message = "Awesome commit (Fixes #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "Awesome commit (fixes ##{iid1})" + message = "Awesome commit (fixes #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "Fixed ##{iid1}" + message = "Fixed #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "fixed ##{iid1}" + message = "fixed #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "Fixing ##{iid1}" + message = "Fixing #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "fixing ##{iid1}" + message = "fixing #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "Fix ##{iid1}" + message = "Fix #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "fix ##{iid1}" + message = "fix #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "Awesome commit (Resolves ##{iid1})" + message = "Awesome commit (Resolves #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "Awesome commit (resolves ##{iid1})" + message = "Awesome commit (resolves #{reference})" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "Resolved ##{iid1}" + message = "Resolved #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "resolved ##{iid1}" + message = "resolved #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "Resolving ##{iid1}" + message = "Resolving #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "resolving ##{iid1}" + message = "resolving #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "Resolve ##{iid1}" + message = "Resolve #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end it do - message = "resolve ##{iid1}" + message = "resolve #{reference}" expect(subject.closed_by_message(message)).to eq([issue]) end end @@ -133,40 +133,40 @@ describe Gitlab::ClosingIssueExtractor do context 'with multiple references' do let(:other_issue) { create(:issue, project: project) } let(:third_issue) { create(:issue, project: project) } - let(:iid2) { other_issue.iid } - let(:iid3) { third_issue.iid } + let(:reference2) { other_issue.to_reference } + let(:reference3) { third_issue.to_reference } it 'fetches issues in single line message' do - message = "Closes ##{iid1} and fix ##{iid2}" + message = "Closes #{reference} and fix ##{reference2}" expect(subject.closed_by_message(message)). to eq([issue, other_issue]) end it 'fetches comma-separated issues references in single line message' do - message = "Closes ##{iid1}, closes ##{iid2}" + message = "Closes #{reference}, closes ##{reference2}" expect(subject.closed_by_message(message)). to eq([issue, other_issue]) end it 'fetches comma-separated issues numbers in single line message' do - message = "Closes ##{iid1}, ##{iid2} and ##{iid3}" + message = "Closes #{reference}, ##{reference2} and ##{reference3}" expect(subject.closed_by_message(message)). to eq([issue, other_issue, third_issue]) end it 'fetches issues in multi-line message' do - message = "Awesome commit (closes ##{iid1})\nAlso fixes ##{iid2}" + message = "Awesome commit (closes #{reference})\nAlso fixes ##{reference2}" expect(subject.closed_by_message(message)). to eq([issue, other_issue]) end it 'fetches issues in hybrid message' do - message = "Awesome commit (closes ##{iid1})\n"\ - "Also fixing issues ##{iid2}, ##{iid3} and #4" + message = "Awesome commit (closes #{reference})\n"\ + "Also fixing issues ##{reference2}, ##{reference3} and #4" expect(subject.closed_by_message(message)). to eq([issue, other_issue, third_issue]) diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index 9801dc1655..c14f4ac6bf 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -20,7 +20,7 @@ describe Gitlab::ReferenceExtractor do @i0 = create(:issue, project: project) @i1 = create(:issue, project: project) - subject.analyze("##{@i0.iid}, ##{@i1.iid}, and #999.") + subject.analyze("#{@i0.to_reference}, #{@i1.to_reference}, and #{Issue.reference_prefix}999.") expect(subject.issues).to eq([@i0, @i1]) end @@ -82,7 +82,7 @@ describe Gitlab::ReferenceExtractor do end it 'handles project issue references' do - subject.analyze("this refers issue #{other_project.path_with_namespace}##{issue.iid}") + subject.analyze("this refers issue #{issue.to_reference(project)}") extracted = subject.issues expect(extracted.size).to eq(1) expect(extracted).to eq([issue]) diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 57b1b9dfcf..0465aa3484 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -129,7 +129,7 @@ describe MergeRequest do it 'detects issues mentioned in the description' do issue2 = create(:issue, project: subject.project) - subject.description = "Closes ##{issue2.iid}" + subject.description = "Closes #{issue2.to_reference}" subject.project.stub(default_branch: subject.target_branch) expect(subject.closes_issues).to include(issue2) diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 4e4cb6d19e..ec173fa322 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -238,13 +238,13 @@ describe SystemNoteService do let(:mentioner) { project2.repository.commit } it 'references the mentioning commit' do - expect(subject.note).to eq "mentioned in commit #{project2.path_with_namespace}@#{mentioner.id}" + expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference(project)}" end end context 'from non-Commit' do it 'references the mentioning object' do - expect(subject.note).to eq "mentioned in issue #{project2.path_with_namespace}##{mentioner.iid}" + expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference(project)}" end end end @@ -254,13 +254,13 @@ describe SystemNoteService do let(:mentioner) { project.repository.commit } it 'references the mentioning commit' do - expect(subject.note).to eq "mentioned in commit #{mentioner.id}" + expect(subject.note).to eq "mentioned in commit #{mentioner.to_reference}" end end context 'from non-Commit' do it 'references the mentioning object' do - expect(subject.note).to eq "mentioned in issue ##{mentioner.iid}" + expect(subject.note).to eq "mentioned in issue #{mentioner.to_reference}" end end end @@ -270,7 +270,7 @@ describe SystemNoteService do describe '.cross_reference?' do it 'is truthy when text begins with expected text' do - expect(described_class.cross_reference?('mentioned in issue #1')).to be_truthy + expect(described_class.cross_reference?('mentioned in something')).to be_truthy end it 'is falsey when text does not begin with expected text' do From 2c1bf71793963f53c0921325ced2c7ad44a5fe95 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 26 May 2015 15:55:26 -0400 Subject: [PATCH 122/255] Fix ClosingIssueExtractor specs --- spec/lib/gitlab/closing_issue_extractor_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb index 63d474c0d1..5d7ff4f612 100644 --- a/spec/lib/gitlab/closing_issue_extractor_spec.rb +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -137,28 +137,28 @@ describe Gitlab::ClosingIssueExtractor do let(:reference3) { third_issue.to_reference } it 'fetches issues in single line message' do - message = "Closes #{reference} and fix ##{reference2}" + message = "Closes #{reference} and fix #{reference2}" expect(subject.closed_by_message(message)). to eq([issue, other_issue]) end it 'fetches comma-separated issues references in single line message' do - message = "Closes #{reference}, closes ##{reference2}" + message = "Closes #{reference}, closes #{reference2}" expect(subject.closed_by_message(message)). to eq([issue, other_issue]) end it 'fetches comma-separated issues numbers in single line message' do - message = "Closes #{reference}, ##{reference2} and ##{reference3}" + message = "Closes #{reference}, #{reference2} and #{reference3}" expect(subject.closed_by_message(message)). to eq([issue, other_issue, third_issue]) end it 'fetches issues in multi-line message' do - message = "Awesome commit (closes #{reference})\nAlso fixes ##{reference2}" + message = "Awesome commit (closes #{reference})\nAlso fixes #{reference2}" expect(subject.closed_by_message(message)). to eq([issue, other_issue]) @@ -166,7 +166,7 @@ describe Gitlab::ClosingIssueExtractor do it 'fetches issues in hybrid message' do message = "Awesome commit (closes #{reference})\n"\ - "Also fixing issues ##{reference2}, ##{reference3} and #4" + "Also fixing issues #{reference2}, #{reference3} and #4" expect(subject.closed_by_message(message)). to eq([issue, other_issue, third_issue]) From 3cb6a338466ca9b8e2a831cce306fc6d650231ed Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 26 May 2015 16:30:07 -0400 Subject: [PATCH 123/255] More SystemNoteService cleanup --- app/models/concerns/mentionable.rb | 4 +- app/models/note.rb | 4 +- app/services/system_note_service.rb | 60 +++++++++-------------------- 3 files changed, 22 insertions(+), 46 deletions(-) diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 9b29988947..6f9f54d08c 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -24,11 +24,11 @@ module Mentionable # # By default this will be the class name and the result of calling # `to_reference` on the object. - def gfm_reference + def gfm_reference(from_project = nil) # "MergeRequest" > "merge_request" > "Merge request" > "merge request" friendly_name = self.class.to_s.underscore.humanize.downcase - "#{friendly_name} #{to_reference}" + "#{friendly_name} #{to_reference(from_project)}" end # Construct a String that contains possible GFM references. diff --git a/app/models/note.rb b/app/models/note.rb index 6939a7e73a..d5f716b3de 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -326,8 +326,8 @@ class Note < ActiveRecord::Base end # Mentionable override. - def gfm_reference - noteable.gfm_reference + def gfm_reference(from_project = nil) + noteable.gfm_reference(from_project) end # Mentionable override. diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 0614f8689a..3d57c35bc1 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -10,7 +10,7 @@ class SystemNoteService # author - User performing the change # new_commits - Array of Commits added since last push # existing_commits - Array of Commits added in a previous push - # oldrev - TODO (rspeicher): I have no idea what this actually does + # oldrev - Optional String SHA of a previous Commit # # See new_commit_summary and existing_commit_summary. # @@ -138,11 +138,11 @@ class SystemNoteService # # Example Note text: # - # "Mentioned in #1" + # "mentioned in #1" # - # "Mentioned in !2" + # "mentioned in !2" # - # "Mentioned in 54f7727c" + # "mentioned in 54f7727c" # # See cross_reference_note_content. # @@ -150,7 +150,7 @@ class SystemNoteService def self.cross_reference(noteable, mentioner, author) return if cross_reference_disallowed?(noteable, mentioner) - gfm_reference = mentioner_gfm_ref(noteable, mentioner) + gfm_reference = mentioner.gfm_reference(noteable.project) note_options = { project: noteable.project, @@ -181,12 +181,21 @@ class SystemNoteService # # Returns Boolean def self.cross_reference_disallowed?(noteable, mentioner) - return false unless MergeRequest === mentioner - return false unless Commit === noteable + return false unless mentioner.is_a?(MergeRequest) + return false unless noteable.is_a?(Commit) mentioner.commits.include?(noteable) end + # Check if a cross reference to a noteable from a mentioner already exists + # + # This method is used to prevent multiple notes being created for a mention + # when a issue is updated, for example. + # + # noteable - Noteable object being referenced + # mentioner - Mentionable object + # + # Returns Boolean def self.cross_reference_exists?(noteable, mentioner) # Initial scope should be system notes of this noteable type notes = Note.system.where(noteable_type: noteable.class) @@ -198,7 +207,7 @@ class SystemNoteService notes = notes.where(noteable_id: noteable.id) end - gfm_reference = mentioner_gfm_ref(noteable, mentioner, true) + gfm_reference = mentioner.gfm_reference(noteable.project) notes = notes.where(note: cross_reference_note_content(gfm_reference)) notes.count > 0 @@ -210,39 +219,6 @@ class SystemNoteService Note.create(args.merge(system: true)) end - # Prepend the mentioner's namespaced project path to the GFM reference for - # cross-project references. For same-project references, return the - # unmodified GFM reference. - def self.mentioner_gfm_ref(noteable, mentioner, cross_reference = false) - # FIXME (rspeicher): This was breaking things. - # if mentioner.is_a?(Commit) && cross_reference - # return mentioner.gfm_reference.sub('commit ', 'commit %') - # end - - full_gfm_reference(mentioner.project, noteable.project, mentioner) - end - - # Return the +mentioner+ GFM reference. If the mentioner and noteable - # projects are not the same, add the mentioning project's path to the - # returned value. - def self.full_gfm_reference(mentioning_project, noteable_project, mentioner) - if mentioning_project == noteable_project - mentioner.gfm_reference - else - if mentioner.is_a?(Commit) - mentioner.gfm_reference.sub( - /(commit )/, - "\\1#{mentioning_project.path_with_namespace}@" - ) - else - mentioner.gfm_reference.sub( - /(issue |merge request )/, - "\\1#{mentioning_project.path_with_namespace}" - ) - end - end - end - def self.cross_reference_note_prefix 'mentioned in ' end @@ -267,7 +243,7 @@ class SystemNoteService # # noteable - MergeRequest object # existing_commits - Array of existing Commit objects - # oldrev - Optional String SHA of ... TODO (rspeicher): I have no idea what this actually does. + # oldrev - Optional String SHA of a previous Commit # # Examples: # From cf12da06534cc7a3a1d4cb661c4cb70184938c80 Mon Sep 17 00:00:00 2001 From: Patricio Cano Date: Wed, 27 May 2015 01:04:23 +0000 Subject: [PATCH 124/255] Added v7.4.4 and .5 to the CHANGELOG --- CHANGELOG | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 8749ca42e4..2b4844844c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -494,6 +494,12 @@ v 7.5.0 - Fix raw view for public snippets - Use secret token with GitLab internal API. - Add missing timestamps to 'members' table + +v 7.4.5 + - Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2) + +v 7.4.4 + - No changes v 7.4.3 - Fix raw snippets view @@ -1451,4 +1457,4 @@ v 0.8.0 - stability - security fixes - increased test coverage - - email notification + - email notification \ No newline at end of file From 1aa3921dd8b0084260fa381ed79580b4b54284b6 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 26 May 2015 21:49:04 -0400 Subject: [PATCH 125/255] Add a note when an Issue or Merge Request's title changes --- CHANGELOG | 1 + app/services/issuable_base_service.rb | 5 ++++ app/services/issues/update_service.rb | 4 +++ app/services/merge_requests/update_service.rb | 4 +++ app/services/system_note_service.rb | 19 ++++++++++++++ spec/services/issues/update_service_spec.rb | 25 ++++++++++++++++--- .../merge_requests/update_service_spec.rb | 25 ++++++++++++++++--- spec/services/system_note_service_spec.rb | 21 ++++++++++++++++ 8 files changed, 96 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index df59850282..35f4d1f0c6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -15,6 +15,7 @@ v 7.12.0 (unreleased) - Default extention for wiki pages is now .md instead of .markdown (Jeroen van Baarsen) - Add validation to wiki page creation (only [a-zA-Z0-9/_-] are allowed) (Jeroen van Baarsen) - Fix new/empty milestones showing 100% completion value (Jonah Bishop) + - Add a note when an Issue or Merge Request's title changes v 7.11.2 - no changes diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index 8960235b09..c5769a5ad2 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -15,4 +15,9 @@ class IssuableBaseService < BaseService SystemNoteService.change_label( issuable, issuable.project, current_user, added_labels, removed_labels) end + + def create_title_change_note(issuable, old_title) + SystemNoteService.change_title( + issuable, issuable.project, current_user, old_title) + end end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index 8f04a69287..6af942a5ca 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -37,6 +37,10 @@ module Issues notification_service.reassigned_issue(issue, current_user) end + if issue.previous_changes.include?('title') + create_title_change_note(issue, issue.previous_changes['title'].first) + end + issue.notice_added_references(issue.project, current_user) execute_hooks(issue, 'update') end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 23af2656c3..34fd59d692 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -50,6 +50,10 @@ module MergeRequests notification_service.reassigned_merge_request(merge_request, current_user) end + if merge_request.previous_changes.include?('title') + create_title_change_note(merge_request, merge_request.previous_changes['title'].first) + end + merge_request.notice_added_references(merge_request.project, current_user) execute_hooks(merge_request, 'update') end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 0614f8689a..1909ae0d6f 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -130,6 +130,25 @@ class SystemNoteService create_note(noteable: noteable, project: project, author: author, note: body) end + # Called when the title of a Noteable is changed + # + # noteable - Noteable object that responds to `title` + # project - Project owning noteable + # author - User performing the change + # old_title - Previous String title + # + # Example Note text: + # + # "Title changed from **Old** to **New**" + # + # Returns the created Note object + def self.change_title(noteable, project, author, old_title) + return unless noteable.respond_to?(:title) + + body = "Title changed from **#{old_title}** to **#{noteable.title}**" + create_note(noteable: noteable, project: project, author: author, note: body) + end + # Called when a Mentionable references a Noteable # # noteable - Noteable object being referenced diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 6fc69e9362..b240d247e7 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Issues::UpdateService do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:issue) { create(:issue) } + let(:issue) { create(:issue, title: 'Old title') } let(:label) { create(:label) } let(:project) { issue.project } @@ -12,7 +12,7 @@ describe Issues::UpdateService do project.team << [user2, :developer] end - describe :execute do + describe 'execute' do context "valid params" do before do opts = { @@ -40,15 +40,32 @@ describe Issues::UpdateService do expect(email.subject).to include(issue.title) end + def find_note(starting_with) + @issue.notes.find do |n| + n && n.note.start_with?(starting_with) + end + end + it 'should create system note about issue reassign' do - note = @issue.notes.last + note = find_note('Reassigned to') + + expect(note).not_to be_nil expect(note.note).to include "Reassigned to \@#{user2.username}" end it 'should create system note about issue label edit' do - note = @issue.notes[1] + note = find_note('Added ~') + + expect(note).not_to be_nil expect(note.note).to include "Added ~#{label.id} label" end + + it 'creates system note about title change' do + note = find_note('Title changed') + + expect(note).not_to be_nil + expect(note.note).to eq 'Title changed from **Old title** to **New title**' + end end end end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 916b01e1c4..bf9790c2be 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe MergeRequests::UpdateService do let(:user) { create(:user) } let(:user2) { create(:user) } - let(:merge_request) { create(:merge_request, :simple) } + let(:merge_request) { create(:merge_request, :simple, title: 'Old title') } let(:project) { merge_request.project } let(:label) { create(:label) } @@ -12,7 +12,7 @@ describe MergeRequests::UpdateService do project.team << [user2, :developer] end - describe :execute do + describe 'execute' do context 'valid params' do let(:opts) do { @@ -51,15 +51,32 @@ describe MergeRequests::UpdateService do expect(email.subject).to include(merge_request.title) end + def find_note(starting_with) + @merge_request.notes.find do |n| + n && n.note.start_with?(starting_with) + end + end + it 'should create system note about merge_request reassign' do - note = @merge_request.notes.last + note = find_note('Reassigned to') + + expect(note).not_to be_nil expect(note.note).to include "Reassigned to \@#{user2.username}" end it 'should create system note about merge_request label edit' do - note = @merge_request.notes[1] + note = find_note('Added ~') + + expect(note).not_to be_nil expect(note.note).to include "Added ~#{label.id} label" end + + it 'creates system note about title change' do + note = find_note('Title changed') + + expect(note).not_to be_nil + expect(note.note).to eq 'Title changed from **Old title** to **New title**' + end end end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 4e4cb6d19e..6d8c71f94f 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -207,6 +207,27 @@ describe SystemNoteService do end end + describe '.change_title' do + subject { described_class.change_title(noteable, project, author, 'Old title') } + + context 'when noteable responds to `title`' do + it_behaves_like 'a system note' + + it 'sets the note text' do + expect(subject.note). + to eq "Title changed from **Old title** to **#{noteable.title}**" + end + end + + context 'when noteable does not respond to `title' do + let(:noteable) { double('noteable') } + + it 'returns nil' do + expect(subject).to be_nil + end + end + end + describe '.cross_reference' do subject { described_class.cross_reference(noteable, mentioner, author) } From 0c9463174ba5b73156ed786648fb782fe79947e9 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Tue, 26 May 2015 21:51:31 -0700 Subject: [PATCH 126/255] Allow HipChat API version to be blank and default to v2 Closes #772 --- CHANGELOG | 1 + .../project_services/hipchat_service.rb | 2 +- .../project_services/hipchat_service_spec.rb | 31 ++++++++++++++++--- 3 files changed, 29 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index ed9ffefb67..e12ebbd4fd 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 7.12.0 (unreleased) + - Allow HipChat API version to be blank and default to v2 (Stan Hu) - Add web hook support for note events (Stan Hu) - Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu) - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 38cb64f8c4..6761f00183 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -63,7 +63,7 @@ class HipchatService < Service private def gate - options = { api_version: api_version || 'v2' } + options = { api_version: api_version.present? ? api_version : 'v2' } options[:server_url] = server unless server.blank? @gate ||= HipChat::Client.new(token, options) end diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb index bbaf54488b..e88615e1a2 100644 --- a/spec/models/project_services/hipchat_service_spec.rb +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -32,21 +32,44 @@ describe HipchatService do let(:project) { create(:project, name: 'project') } let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' } let(:project_name) { project.name_with_namespace.gsub(/\s/, '') } + let(:token) { 'verySecret' } + let(:server_url) { 'https://hipchat.example.com'} + let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } before(:each) do hipchat.stub( project_id: project.id, project: project, room: 123456, - server: 'https://hipchat.example.com', - token: 'verySecret' + server: server_url, + token: token ) WebMock.stub_request(:post, api_url) end - context 'push events' do - let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } + it 'should use v1 if version is provided' do + hipchat.stub(api_version: 'v1') + expect(HipChat::Client).to receive(:new). + with(token, + api_version: 'v1', + server_url: server_url). + and_return( + double(:hipchat_service).as_null_object) + hipchat.execute(push_sample_data) + end + it 'should use v2 as the version when nothing is provided' do + hipchat.stub(api_version: '') + expect(HipChat::Client).to receive(:new). + with(token, + api_version: 'v2', + server_url: server_url). + and_return( + double(:hipchat_service).as_null_object) + hipchat.execute(push_sample_data) + end + + context 'push events' do it "should call Hipchat API for push events" do hipchat.execute(push_sample_data) From 38637e7d08943c85101e10711f75d850527512b5 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 27 May 2015 03:33:54 -0400 Subject: [PATCH 127/255] Change one-character variable name [ci skip] --- spec/services/issues/update_service_spec.rb | 4 ++-- spec/services/merge_requests/update_service_spec.rb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index b240d247e7..a91be3b447 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -41,8 +41,8 @@ describe Issues::UpdateService do end def find_note(starting_with) - @issue.notes.find do |n| - n && n.note.start_with?(starting_with) + @issue.notes.find do |note| + note && note.note.start_with?(starting_with) end end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index bf9790c2be..0a0760056c 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -52,8 +52,8 @@ describe MergeRequests::UpdateService do end def find_note(starting_with) - @merge_request.notes.find do |n| - n && n.note.start_with?(starting_with) + @merge_request.notes.find do |note| + note && note.note.start_with?(starting_with) end end From 56ab471f9868bb8c8f204b11bb712572c20bb250 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 27 May 2015 13:17:14 +0200 Subject: [PATCH 128/255] Move user avatar and logout button to sidebar Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/generic/common.scss | 5 ++- app/assets/stylesheets/generic/header.scss | 31 +++++++++---------- app/assets/stylesheets/generic/sidebar.scss | 26 ++++++++++++++++ .../stylesheets/themes/gitlab-theme.scss | 6 ++++ app/views/layouts/_head_panel.html.haml | 6 ---- app/views/layouts/_page.html.haml | 9 ++++++ 6 files changed, 58 insertions(+), 25 deletions(-) diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index 1e569978cc..b69c5c4b57 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -342,9 +342,8 @@ table { } #nprogress .spinner { - top: auto !important; - bottom: 20px !important; - left: 20px !important; + top: 15px !important; + right: 10px !important; } .header-with-avatar { diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss index c4bafad690..fe32b024f4 100644 --- a/app/assets/stylesheets/generic/header.scss +++ b/app/assets/stylesheets/generic/header.scss @@ -13,7 +13,7 @@ header { .container { width: 100% !important; padding: 0; - + padding-right: 35px; background: #FFF; border-bottom: 1px solid #EEE; filter: none; @@ -62,13 +62,21 @@ header { } .nav > li > a { - color: #666; + color: #888; font-size: 14px; - line-height: 32px; - padding: 6px 10px; + line-height: 19px; + padding: 0; + background-color: #f5f5f5; + margin: 9px 0; + margin-left: 10px; + border-radius: 40px; + height: 26px; + width: 26px; + line-height: 26px; + text-align: center; &:hover, &:focus, &:active { - background: none; + background-color: #EEE; } } @@ -150,17 +158,6 @@ header { } } - .profile-pic { - padding: 0px !important; - width: 46px; - height: 46px; - margin-left: 5px; - img { - width: 46px; - height: 46px; - } - } - /** * * Search box @@ -184,6 +181,8 @@ header { padding: 4px 6px; padding-left: 25px; font-size: 13px; + background-color: #f5f5f5; + border-color: #f5f5f5; } } } diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss index 754c5b5302..a80b585080 100644 --- a/app/assets/stylesheets/generic/sidebar.scss +++ b/app/assets/stylesheets/generic/sidebar.scss @@ -127,6 +127,20 @@ left: 0px; width: 52px; } + + .sidebar-user { + .username { + display: none; + } + + .avatar { + margin-bottom: 10px; + } + + .logout-holder { + text-align: center; + } + } } } @@ -170,3 +184,15 @@ @include expanded-sidebar; } } + +.sidebar-user { + position: absolute; + bottom: 0; + width: 100%; + padding: 10px; + color: #fff; + + .avatar { + margin-top: 5px; + } +} diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss index 139b3cc1ac..9b8e3d8e29 100644 --- a/app/assets/stylesheets/themes/gitlab-theme.scss +++ b/app/assets/stylesheets/themes/gitlab-theme.scss @@ -29,6 +29,12 @@ .sidebar-wrapper { background: $color-darker; border-right: 1px solid $color-darker; + + .sidebar-user { + a { + color: $color-light; + } + } } .nav-sidebar li { diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index ef685a0434..581d6a3961 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -38,11 +38,5 @@ %li = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('user') - %li - = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('sign-out') - %li.hidden-xs - = link_to current_user, class: 'profile-pic', id: 'profile-pic', data: {toggle: 'tooltip', placement: 'bottom'} do - = image_tag avatar_icon(current_user.email, 60), alt: 'User activity' = render 'shared/outdated_browser' diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml index 5c55bdb546..c1283734d2 100644 --- a/app/views/layouts/_page.html.haml +++ b/app/views/layouts/_page.html.haml @@ -7,6 +7,15 @@ = render 'layouts/nav/dashboard' .collapse-nav = render partial: 'layouts/collapse_button' + - if current_user + .sidebar-user + = link_to current_user, class: 'profile-pic', id: 'profile-pic', data: {toggle: 'tooltip', placement: 'top'} do + = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s32' + .username + = current_user.username + .logout-holder + = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'top'} do + = icon('sign-out') .content-wrapper .container-fluid .content From 284664448f62df8c7fb4f8283e0bee16db6ffeab Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 27 May 2015 13:53:53 +0200 Subject: [PATCH 129/255] Add missing CHANGELOG item Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index a62296e065..6a4c8c3cd1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 7.12.0 (unreleased) - Prefix EmailsOnPush email subject with `[Git]`. - Group project contributions by both name and email. - Clarify navigation labels for Project Settings and Group Settings. + - Move user avatar and logout button to sidebar v 7.11.2 - no changes From 8313aa6da80ff79026b310181ad0a686f0a5b117 Mon Sep 17 00:00:00 2001 From: Stefan Schweter Date: Wed, 27 May 2015 14:06:40 +0200 Subject: [PATCH 130/255] Adds new section for changing time zone in GitLab configuration file. New section added for changing time zone in Omnibus installations. --- doc/workflow/timezone.md | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/doc/workflow/timezone.md b/doc/workflow/timezone.md index 8540ccfcab..7e08c0e51a 100644 --- a/doc/workflow/timezone.md +++ b/doc/workflow/timezone.md @@ -1,10 +1,22 @@ # Changing your time zone -GitLab defaults its time zone to UTC. It has a global timezone configuration parameter in /etc/gitlab/gitlab.rb +The global time zone configuration parameter can be changed in `config/gitlab.yml`: +``` + # time_zone: 'UTC' +``` + +Uncomment and customize if you want to change the default time zone of GitLab application. + +To see all available time zones, run `bundle exec rake time:zones:all`. + + +## Changing time zone in omnibus installations + +GitLab defaults its time zone to UTC. It has a global timezone configuration parameter in `/etc/gitlab/gitlab.rb`. To update, add the time zone that best applies to your location. Here are two examples: ``` -gitlab_rails['time_zone'] = 'America/New_York' +gitlab_rails['time_zone'] = 'America/New_York' ``` or ``` @@ -15,4 +27,4 @@ After you added this field, reconfigure and restart: ``` gitlab-ctl reconfigure gitlab-ctl restart -``` \ No newline at end of file +``` From ab88b7da19168c66404dfa9dfeb12110ccb8ceea Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Wed, 27 May 2015 05:07:44 -0700 Subject: [PATCH 131/255] Fix project snippets button appearing when it is disabled Closes #1705 --- app/models/ability.rb | 2 +- features/project/project.feature | 5 +++++ features/steps/project/project.rb | 4 ++++ features/steps/shared/project.rb | 5 +++++ 4 files changed, 15 insertions(+), 1 deletion(-) diff --git a/app/models/ability.rb b/app/models/ability.rb index e166b4197f..4e6c60dc8c 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -115,7 +115,7 @@ class Ability end unless project.snippets_enabled - rules -= named_abilities('snippet') + rules -= named_abilities('project_snippet') end unless project.wiki_enabled diff --git a/features/project/project.feature b/features/project/project.feature index ef11bceed1..56ae5c78d0 100644 --- a/features/project/project.feature +++ b/features/project/project.feature @@ -68,3 +68,8 @@ Feature: Project When I visit project "Shop" page Then I should not see "New Issue" button And I should not see "New Merge Request" button + + Scenario: I should not see Project snippets + Given I disable snippets in project + When I visit project "Shop" page + Then I should not see "Snippets" button diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 93fea693f8..fcc15aacc2 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -110,4 +110,8 @@ class Spinach::Features::Project < Spinach::FeatureSteps step 'I should not see "New Merge Request" button' do page.should_not have_link 'New Merge Request' end + + step 'I should not see "Snippets" button' do + page.should_not have_link 'Snippets' + end end diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index 24136fe421..3059c4ee04 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -14,6 +14,11 @@ module SharedProject @project.team << [@user, :master] end + step 'I disable snippets in project' do + @project.snippets_enabled = false + @project.save + end + step 'I disable issues and merge requests in project' do @project.issues_enabled = false @project.merge_requests_enabled = false From 45e4727f97034f719b4fb2a061fd626f545db968 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 25 May 2015 13:36:28 +0200 Subject: [PATCH 132/255] Set milestone on new issue when creating issue from index with milestone filter active. --- CHANGELOG | 1 + app/controllers/application_controller.rb | 8 +- app/finders/README.md | 2 +- app/finders/issuable_finder.rb | 104 ++++++++++++++++----- app/views/projects/issues/index.html.haml | 2 +- spec/finders/issues_finder_spec.rb | 16 ++-- spec/finders/merge_requests_finder_spec.rb | 4 +- 7 files changed, 100 insertions(+), 37 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 82967cbe0b..553bfb0282 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 7.12.0 (unreleased) - Fix Zen Mode not closing with ESC key (Stan Hu) - Allow HipChat API version to be blank and default to v2 (Stan Hu) - Add file attachment support in Milestone description (Stan Hu) + - Set milestone on new issue when creating issue from index with milestone filter active. - Add web hook support for note events (Stan Hu) - Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu) - Remove Rack Attack monkey patches and bump to version 4.3.0 (Stan Hu) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8ce881c741..e5da94b232 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -289,14 +289,14 @@ class ApplicationController < ActionController::Base def get_issues_collection set_filters_params - issues = IssuesFinder.new.execute(current_user, @filter_params) - issues + @issuable_finder = IssuesFinder.new(current_user, @filter_params) + @issuable_finder.execute end def get_merge_requests_collection set_filters_params - merge_requests = MergeRequestsFinder.new.execute(current_user, @filter_params) - merge_requests + @issuable_finder = MergeRequestsFinder.new(current_user, @filter_params) + @issuable_finder.execute end def github_import_enabled? diff --git a/app/finders/README.md b/app/finders/README.md index 1f46518d23..1a1c69dea3 100644 --- a/app/finders/README.md +++ b/app/finders/README.md @@ -16,7 +16,7 @@ issues = project.issues_for_user_filtered_by(user, params) Better use this: ```ruby -issues = IssuesFinder.new.execute(project, user, filter) +issues = IssuesFinder.new(project, user, filter).execute ``` It will help keep models thiner. diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index e658e14115..0bed2115dc 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -23,10 +23,12 @@ class IssuableFinder attr_accessor :current_user, :params - def execute(current_user, params) + def initialize(current_user, params) @current_user = current_user @params = params + end + def execute items = init_collection items = by_scope(items) items = by_state(items) @@ -40,6 +42,77 @@ class IssuableFinder items = sort(items) end + def group + return @group if defined?(@group) + + @group = + if params[:group_id].present? + Group.find(params[:group_id]) + else + nil + end + end + + def project + return @project if defined?(@project) + + @project = + if params[:project_id].present? + Project.find(params[:project_id]) + else + nil + end + end + + def search + params[:search].presence + end + + def milestones? + params[:milestone_title].present? + end + + def milestones + return @milestones if defined?(@milestones) + + @milestones = + if milestones? && params[:milestone_title] != NONE + Milestone.where(title: params[:milestone_title]) + else + nil + end + end + + def assignee? + params[:assignee_id].present? + end + + def assignee + return @assignee if defined?(@assignee) + + @assignee = + if assignee? && params[:assignee_id] != NONE + User.find(params[:assignee_id]) + else + nil + end + end + + def author? + params[:author_id].present? + end + + def author + return @author if defined?(@author) + + @author = + if author? && params[:author_id] != NONE + User.find(params[:author_id]) + else + nil + end + end + private def init_collection @@ -89,25 +162,19 @@ class IssuableFinder end def by_group(items) - if params[:group_id].present? - items = items.of_group(Group.find(params[:group_id])) - end + items = items.of_group(group) if group items end def by_project(items) - if params[:project_id].present? - items = items.of_projects(params[:project_id]) - end + items = items.of_projects(project.id) if project items end def by_search(items) - if params[:search].present? - items = items.search(params[:search]) - end + items = items.search(search) if search items end @@ -117,25 +184,24 @@ class IssuableFinder end def by_milestone(items) - if params[:milestone_title].present? - milestone_ids = (params[:milestone_title] == NONE ? nil : Milestone.where(title: params[:milestone_title]).pluck(:id)) - items = items.where(milestone_id: milestone_ids) + if milestones? + items = items.where(milestone_id: milestones.try(:pluck, :id)) end items end def by_assignee(items) - if params[:assignee_id].present? - items = items.where(assignee_id: (params[:assignee_id] == NONE ? nil : params[:assignee_id])) + if assignee? + items = items.where(assignee_id: assignee.try(:id)) end items end def by_author(items) - if params[:author_id].present? - items = items.where(author_id: (params[:author_id] == NONE ? nil : params[:author_id])) + if author? + items = items.where(author_id: author.try(:id)) end items @@ -155,10 +221,6 @@ class IssuableFinder items end - def project - Project.where(id: params[:project_id]).first if params[:project_id].present? - end - def current_user_related? params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' end diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index a378b37f4a..1d5597602d 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -14,7 +14,7 @@ = render 'shared/issuable_search_form', path: namespace_project_issues_path(@project.namespace, @project) - if can? current_user, :write_issue, @project - = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do + = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do %i.fa.fa-plus New Issue diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 69bac387d2..db20b23f87 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -26,37 +26,37 @@ describe IssuesFinder do context 'scope: all' do it 'should filter by all' do params = { scope: "all", state: 'opened' } - issues = IssuesFinder.new.execute(user, params) + issues = IssuesFinder.new(user, params).execute expect(issues.size).to eq(3) end it 'should filter by assignee id' do params = { scope: "all", assignee_id: user.id, state: 'opened' } - issues = IssuesFinder.new.execute(user, params) + issues = IssuesFinder.new(user, params).execute expect(issues.size).to eq(2) end it 'should filter by author id' do params = { scope: "all", author_id: user2.id, state: 'opened' } - issues = IssuesFinder.new.execute(user, params) + issues = IssuesFinder.new(user, params).execute expect(issues).to eq([issue3]) end it 'should filter by milestone id' do params = { scope: "all", milestone_title: milestone.title, state: 'opened' } - issues = IssuesFinder.new.execute(user, params) + issues = IssuesFinder.new(user, params).execute expect(issues).to eq([issue1]) end it 'should be empty for unauthorized user' do params = { scope: "all", state: 'opened' } - issues = IssuesFinder.new.execute(nil, params) + issues = IssuesFinder.new(nil, params).execute expect(issues.size).to be_zero end it 'should not include unauthorized issues' do params = { scope: "all", state: 'opened' } - issues = IssuesFinder.new.execute(user2, params) + issues = IssuesFinder.new(user2, params).execute expect(issues.size).to eq(2) expect(issues).not_to include(issue1) expect(issues).to include(issue2) @@ -67,13 +67,13 @@ describe IssuesFinder do context 'personal scope' do it 'should filter by assignee' do params = { scope: "assigned-to-me", state: 'opened' } - issues = IssuesFinder.new.execute(user, params) + issues = IssuesFinder.new(user, params).execute expect(issues.size).to eq(2) end it 'should filter by project' do params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id } - issues = IssuesFinder.new.execute(user, params) + issues = IssuesFinder.new(user, params).execute expect(issues.size).to eq(1) end end diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 8536377a7f..bc385fd0d6 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -20,13 +20,13 @@ describe MergeRequestsFinder do describe "#execute" do it 'should filter by scope' do params = { scope: 'authored', state: 'opened' } - merge_requests = MergeRequestsFinder.new.execute(user, params) + merge_requests = MergeRequestsFinder.new(user, params).execute expect(merge_requests.size).to eq(2) end it 'should filter by project' do params = { project_id: project1.id, scope: 'authored', state: 'opened' } - merge_requests = MergeRequestsFinder.new.execute(user, params) + merge_requests = MergeRequestsFinder.new(user, params).execute expect(merge_requests.size).to eq(1) end end From 511791014e032c8800c0b8b06130b73aad8505b5 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 25 May 2015 13:36:41 +0200 Subject: [PATCH 133/255] Fix milestone "Browse Issues" button. --- CHANGELOG | 1 + app/views/projects/milestones/_milestone.html.haml | 4 ++-- app/views/projects/milestones/show.html.haml | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 553bfb0282..87fb959a1e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -6,6 +6,7 @@ v 7.12.0 (unreleased) - Fix Zen Mode not closing with ESC key (Stan Hu) - Allow HipChat API version to be blank and default to v2 (Stan Hu) - Add file attachment support in Milestone description (Stan Hu) + - Fix milestone "Browse Issues" button. - Set milestone on new issue when creating issue from index with milestone filter active. - Add web hook support for note events (Stan Hu) - Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu) diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml index 62360158ff..14a0580f96 100644 --- a/app/views/projects/milestones/_milestone.html.haml +++ b/app/views/projects/milestones/_milestone.html.haml @@ -13,10 +13,10 @@ = milestone.expires_at .row .col-sm-6 - = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do + = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do = pluralize milestone.issues.count, 'Issue'   - = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do + = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_title: milestone.title) do = pluralize milestone.merge_requests.count, 'Merge Request'   %span.light #{milestone.percent_complete}% complete diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index ee2139e75f..417eaa1b09 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -67,7 +67,7 @@ %i.fa.fa-plus New Issue - if can?(current_user, :read_issue, @project) - = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link btn-grouped" + = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_title: @milestone.title), class: "btn edit-milestone-link btn-grouped" .tab-content .tab-pane.active#tab-issues From 7a11ef3e959735fffb189ad03218593f700ce25b Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 25 May 2015 14:17:17 +0200 Subject: [PATCH 134/255] Add Browse Issues button to Dashboard and Group milestones. --- app/views/dashboard/milestones/_milestone.html.haml | 4 ++-- app/views/dashboard/milestones/show.html.haml | 3 +++ app/views/groups/milestones/_milestone.html.haml | 4 ++-- app/views/groups/milestones/show.html.haml | 3 +++ 4 files changed, 10 insertions(+), 4 deletions(-) diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml index 21e730bb7f..d6f3e029a3 100644 --- a/app/views/dashboard/milestones/_milestone.html.haml +++ b/app/views/dashboard/milestones/_milestone.html.haml @@ -3,10 +3,10 @@ = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title) .row .col-sm-6 - = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do + = link_to issues_dashboard_path(milestone_title: milestone.title) do = pluralize milestone.issue_count, 'Issue'   - = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do + = link_to merge_requests_dashboard_path(milestone_title: milestone.title) do = pluralize milestone.merge_requests_count, 'Merge Request'   %span.light #{milestone.percent_complete}% complete diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml index 24f0bcb60d..0d204ced7e 100644 --- a/app/views/dashboard/milestones/show.html.haml +++ b/app/views/dashboard/milestones/show.html.haml @@ -56,6 +56,9 @@ Participants %span.badge= @dashboard_milestone.participants.count + .pull-right + = link_to 'Browse Issues', issues_dashboard_path(milestone_title: @dashboard_milestone.title), class: "btn edit-milestone-link btn-grouped" + .tab-content .tab-pane.active#tab-issues .row diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml index 30093d2d05..ba30e6e07c 100644 --- a/app/views/groups/milestones/_milestone.html.haml +++ b/app/views/groups/milestones/_milestone.html.haml @@ -9,10 +9,10 @@ = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title) .row .col-sm-6 - = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do + = link_to issues_group_path(@group, milestone_title: milestone.title) do = pluralize milestone.issue_count, 'Issue'   - = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do + = link_to merge_requests_group_path(@group, milestone_title: milestone.title) do = pluralize milestone.merge_requests_count, 'Merge Request'   %span.light #{milestone.percent_complete}% complete diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml index 6c41cd6b9e..8f2decb851 100644 --- a/app/views/groups/milestones/show.html.haml +++ b/app/views/groups/milestones/show.html.haml @@ -62,6 +62,9 @@ Participants %span.badge= @group_milestone.participants.count + .pull-right + = link_to 'Browse Issues', issues_group_path(@group, milestone_title: @group_milestone.title), class: "btn edit-milestone-link btn-grouped" + .tab-content .tab-pane.active#tab-issues .row From e6f282e0f127450aaaef635e6ea4f75916b0d8f1 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Mon, 25 May 2015 15:48:13 +0200 Subject: [PATCH 135/255] Fix spec. --- features/steps/groups.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/features/steps/groups.rb b/features/steps/groups.rb index 228b83e5fd..84348d1709 100644 --- a/features/steps/groups.rb +++ b/features/steps/groups.rb @@ -203,8 +203,8 @@ class Spinach::Features::Groups < Spinach::FeatureSteps step 'I should see group milestones index page with milestones' do page.should have_content('Version 7.2') page.should have_content('GL-113') - page.should have_link('2 Issues', href: group_milestone_path("owned", "version-7-2", title: "Version 7.2")) - page.should have_link('3 Merge Requests', href: group_milestone_path("owned", "gl-113", title: "GL-113")) + page.should have_link('2 Issues', href: issues_group_path("owned", milestone_title: "Version 7.2")) + page.should have_link('3 Merge Requests', href: merge_requests_group_path("owned", milestone_title: "GL-113")) end step 'I click on one group milestone' do From 10871732df078dc17e1c6c540a2ce18098ee21f0 Mon Sep 17 00:00:00 2001 From: Steve Norman Date: Wed, 27 May 2015 13:04:18 +0000 Subject: [PATCH 136/255] Use entity number for plus sign --- app/views/notify/repository_push_email.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index a374a66233..12f83aae04 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -35,7 +35,7 @@ = diff.new_path - elsif diff.new_file %span.new-file - + + + = diff.new_path - else = diff.new_path From 22de5443c5c37772e090268ed115b88d12427cc4 Mon Sep 17 00:00:00 2001 From: Alex Lossent Date: Wed, 27 May 2015 16:37:22 +0200 Subject: [PATCH 137/255] Add SAML support via Omniauth --- CHANGELOG | 1 + Gemfile | 1 + Gemfile.lock | 12 +++ .../omniauth_callbacks_controller.rb | 3 + config/gitlab.yml.example | 9 +++ doc/integration/omniauth.md | 1 + doc/integration/saml.md | 77 +++++++++++++++++++ 7 files changed, 104 insertions(+) create mode 100644 doc/integration/saml.md diff --git a/CHANGELOG b/CHANGELOG index a62296e065..bae8f77e25 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -22,6 +22,7 @@ v 7.12.0 (unreleased) - Prefix EmailsOnPush email subject with `[Git]`. - Group project contributions by both name and email. - Clarify navigation labels for Project Settings and Group Settings. + - Add SAML support as an omniauth provider v 7.11.2 - no changes diff --git a/Gemfile b/Gemfile index 8eb1f04000..1285846c83 100644 --- a/Gemfile +++ b/Gemfile @@ -31,6 +31,7 @@ gem 'omniauth-shibboleth' gem 'omniauth-kerberos', group: :kerberos gem 'omniauth-gitlab' gem 'omniauth-bitbucket' +gem 'omniauth-saml' gem 'doorkeeper', '2.1.3' gem "rack-oauth2", "~> 1.0.5" diff --git a/Gemfile.lock b/Gemfile.lock index 80e4a44c1d..b9d642731c 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -333,6 +333,8 @@ GEM rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) lumberjack (1.0.4) + macaddr (1.7.1) + systemu (~> 2.6.2) mail (2.6.3) mime-types (>= 1.16, < 3) method_source (0.8.2) @@ -389,6 +391,9 @@ GEM omniauth-oauth2 (1.1.1) oauth2 (~> 0.8.0) omniauth (~> 1.0) + omniauth-saml (1.3.1) + omniauth (~> 1.1) + ruby-saml (~> 0.8.1) omniauth-shibboleth (1.1.1) omniauth (>= 1.0.0) omniauth-twitter (1.0.1) @@ -523,6 +528,9 @@ GEM rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.4) ruby-progressbar (1.7.1) + ruby-saml (0.8.2) + nokogiri (>= 1.5.0) + uuid (~> 2.3) ruby2ruby (2.1.3) ruby_parser (~> 3.1) sexp_processor (~> 4.0) @@ -606,6 +614,7 @@ GEM stamp (0.5.0) state_machine (1.2.0) stringex (2.5.2) + systemu (2.6.5) task_list (1.0.2) html-pipeline temple (0.6.7) @@ -654,6 +663,8 @@ GEM raindrops (~> 0.7) unicorn-worker-killer (0.4.2) unicorn (~> 4) + uuid (2.3.7) + macaddr (~> 1.0) version_sorter (2.0.0) virtus (1.0.1) axiom-types (~> 0.0.5) @@ -757,6 +768,7 @@ DEPENDENCIES omniauth-gitlab omniauth-google-oauth2 omniauth-kerberos + omniauth-saml omniauth-shibboleth omniauth-twitter org-ruby (= 0.9.12) diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index dcd949a71d..a767815b31 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -1,4 +1,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController + + protect_from_forgery except: [:kerberos, :saml] + Gitlab.config.omniauth.providers.each do |provider| define_method provider['name'] do handle_omniauth diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index fbc7f515f3..5acfe54850 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -210,6 +210,15 @@ production: &base # args: { scope: 'api' } } # - { name: 'bitbucket', app_id: 'YOUR_APP_ID', # app_secret: 'YOUR_APP_SECRET'} + # - { name: 'saml', + # args: { + # assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback', + # idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8', + # idp_sso_target_url: 'https://login.example.com/idp', + # issuer: 'https://gitlab.example.com', + # name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' + # } } + diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index 24f7b4bb4b..8e2a602ec3 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -75,6 +75,7 @@ Now we can choose one or more of the Supported Providers below to continue confi - [Google](google.md) - [Shibboleth](shibboleth.md) - [Twitter](twitter.md) +- [SAML](saml.md) ## Enable OmniAuth for an Existing User diff --git a/doc/integration/saml.md b/doc/integration/saml.md new file mode 100644 index 0000000000..a8cc5c8f74 --- /dev/null +++ b/doc/integration/saml.md @@ -0,0 +1,77 @@ +# SAML OmniAuth Provider + +GitLab can be configured to act as a SAML 2.0 Service Provider (SP). This allows GitLab to consume assertions from a SAML 2.0 Identity Provider (IdP) such as Microsoft ADFS to authenticate users. + +First configure SAML 2.0 support in GitLab, then register the GitLab application in your SAML IdP: + +1. Make sure GitLab is configured with HTTPS. See [Using HTTPS](../install/installation.md#using-https) for instructions. + +1. On your GitLab server, open the configuration file. + + For omnibus package: + + ```sh + sudo editor /etc/gitlab/gitlab.rb + ``` + + For instalations from source: + + ```sh + cd /home/git/gitlab + + sudo -u git -H editor config/gitlab.yml + ``` + +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. + +1. Add the provider configuration: + + For omnibus package: + + ```ruby + gitlab_rails['omniauth_providers'] = [ + { + "name" => "saml", + args: { + assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback', + idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8', + idp_sso_target_url: 'https://login.example.com/idp', + issuer: 'https://gitlab.example.com', + name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' + } + } + ] + ``` + + For installations from source: + + ```yaml + - { name: 'saml', + args: { + assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback', + idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8', + idp_sso_target_url: 'https://login.example.com/idp', + issuer: 'https://gitlab.example.com', + name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' + } } + ``` + +1. Change the value for 'assertion_consumer_service_url' to match the HTTPS endpoint of GitLab (append 'users/auth/saml/callback' to the HTTPS URL of your GitLab installation to generate the correct value). + +1. Change the values of 'idp_cert_fingerprint', 'idp_sso_target_url', 'name_identifier_format' to match your IdP. Check [the omniauth-saml documentation](https://github.com/PracticallyGreen/omniauth-saml) for details on these options. + +1. Change the value of 'issuer' to a unique name, which will identify the application to the IdP. + +1. Restart GitLab for the changes to take effect. + +1. Register the GitLab SP in your SAML 2.0 IdP, using the application name specified in 'issuer'. + +To ease configuration, most IdP accept a metadata URL for the application to provide configuration information to the IdP. To build the metadata URL for GitLab, append 'users/auth/saml/metadata' to the HTTPS URL of your GitLab installation, for instance: + ``` + https://gitlab.example.com/users/auth/saml/metadata + ``` + +At a minimum the IdP *must* provide a claim containing the user's email address, using claim name 'email' or 'mail'. The email will be used to automatically generate the GitLab username. GitLab will also use claims with name 'name', 'first_name', 'last_name' (see [the omniauth-saml gem](https://github.com/PracticallyGreen/omniauth-saml/blob/master/lib/omniauth/strategies/saml.rb) for supported claims). + +On the sign in page there should now be a SAML button below the regular sign in form. Click the icon to begin the authentication process. If everything goes well the user will be returned to GitLab and will be signed in. + From 6cf45dde0c2d82d777a6c417cdddb90a1e94ebe1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 27 May 2015 17:56:36 +0200 Subject: [PATCH 138/255] Replace some icons in header Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/_head_panel.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index 581d6a3961..979755db65 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -30,13 +30,13 @@ - if current_user.is_admin? %li = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('cogs') + = icon('wrench') - if current_user.can_create_project? %li = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do = icon('plus') %li = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('user') + = icon('cog') = render 'shared/outdated_browser' From 7424d2fa5bd52b7f41ec4359bf80b1649b59706b Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 27 May 2015 15:39:08 -0400 Subject: [PATCH 139/255] Add ExternalLinkFilter to Markdown pipeline Forces a `rel="nofollow"` attribute on all external links. --- lib/gitlab/markdown.rb | 2 ++ lib/gitlab/markdown/external_link_filter.rb | 33 +++++++++++++++++++ spec/features/markdown_spec.rb | 14 +++++++- spec/fixtures/markdown.md.erb | 9 ++++- .../markdown/external_link_filter_spec.rb | 33 +++++++++++++++++++ 5 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 lib/gitlab/markdown/external_link_filter.rb create mode 100644 spec/lib/gitlab/markdown/external_link_filter_spec.rb diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index c0fb22e7f3..5db1566f55 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -11,6 +11,7 @@ module Gitlab autoload :CommitReferenceFilter, 'gitlab/markdown/commit_reference_filter' autoload :EmojiFilter, 'gitlab/markdown/emoji_filter' autoload :ExternalIssueReferenceFilter, 'gitlab/markdown/external_issue_reference_filter' + autoload :ExternalLinkFilter, 'gitlab/markdown/external_link_filter' autoload :IssueReferenceFilter, 'gitlab/markdown/issue_reference_filter' autoload :LabelReferenceFilter, 'gitlab/markdown/label_reference_filter' autoload :MergeRequestReferenceFilter, 'gitlab/markdown/merge_request_reference_filter' @@ -103,6 +104,7 @@ module Gitlab Gitlab::Markdown::EmojiFilter, Gitlab::Markdown::TableOfContentsFilter, Gitlab::Markdown::AutolinkFilter, + Gitlab::Markdown::ExternalLinkFilter, Gitlab::Markdown::UserReferenceFilter, Gitlab::Markdown::IssueReferenceFilter, diff --git a/lib/gitlab/markdown/external_link_filter.rb b/lib/gitlab/markdown/external_link_filter.rb new file mode 100644 index 0000000000..c539e0fb82 --- /dev/null +++ b/lib/gitlab/markdown/external_link_filter.rb @@ -0,0 +1,33 @@ +require 'html/pipeline/filter' + +module Gitlab + module Markdown + # HTML Filter to add a `rel="nofollow"` attribute to external links + # + class ExternalLinkFilter < HTML::Pipeline::Filter + def call + doc.search('a').each do |node| + next unless node.has_attribute?('href') + + link = node.attribute('href').value + + # Skip non-HTTP(S) links + next unless link.start_with?('http') + + # Skip internal links + next if link.start_with?(internal_url) + + node.set_attribute('rel', 'nofollow') + end + + doc + end + + private + + def internal_url + @internal_url ||= Gitlab.config.gitlab.url + end + end + end +end diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index d695417466..ee1b3bf749 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -149,7 +149,7 @@ describe 'GitLab Markdown' do it 'removes `rel` attribute from links' do body = get_section('sanitizationfilter') - expect(body).not_to have_selector('a[rel]') + expect(body).not_to have_selector('a[rel="bookmark"]') end it "removes `href` from `a` elements if it's fishy" do @@ -237,6 +237,18 @@ describe 'GitLab Markdown' do end end + describe 'ExternalLinkFilter' do + let(:links) { get_section('externallinkfilter').next_element } + + it 'adds nofollow to external link' do + expect(links.css('a').first.to_html).to match 'nofollow' + end + + it 'ignores internal link' do + expect(links.css('a').last.to_html).not_to match 'nofollow' + end + end + describe 'ReferenceFilter' do it 'handles references in headers' do header = @doc.at_css('#reference-filters-eg-1').parent diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb index 26fc4e38e5..02ab46c905 100644 --- a/spec/fixtures/markdown.md.erb +++ b/spec/fixtures/markdown.md.erb @@ -79,7 +79,7 @@ As permissive as it is, we've allowed even more stuff: span tag -This is a link with a defined rel attribute, which should be removed +This is a link with a defined rel attribute, which should be removed This is a link trying to be sneaky. It gets its link removed entirely. @@ -127,6 +127,13 @@ But it shouldn't autolink text inside certain tags: - http://about.gitlab.com/ - http://about.gitlab.com/ +### ExternalLinkFilter + +External links get a `rel="nofollow"` attribute: + +- [Google](https://google.com/) +- [GitLab Root](<%= Gitlab.config.gitlab.url %>) + ### Reference Filters (e.g., <%= issue.to_reference %>) References should be parseable even inside _<%= merge_request.to_reference %>_ emphasis. diff --git a/spec/lib/gitlab/markdown/external_link_filter_spec.rb b/spec/lib/gitlab/markdown/external_link_filter_spec.rb new file mode 100644 index 0000000000..c2ff4f80a4 --- /dev/null +++ b/spec/lib/gitlab/markdown/external_link_filter_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +module Gitlab::Markdown + describe ExternalLinkFilter do + def filter(html, options = {}) + described_class.call(html, options) + end + + it 'ignores elements without an href attribute' do + exp = act = %q(Ignore Me) + expect(filter(act).to_html).to eq exp + end + + it 'ignores non-HTTP(S) links' do + exp = act = %q(IRC) + expect(filter(act).to_html).to eq exp + end + + it 'skips internal links' do + internal = Gitlab.config.gitlab.url + exp = act = %Q(Login) + expect(filter(act).to_html).to eq exp + end + + it 'adds rel="nofollow" to external links' do + act = %q(Google) + doc = filter(act) + + expect(doc.at_css('a')).to have_attribute('rel') + expect(doc.at_css('a')['rel']).to eq 'nofollow' + end + end +end From 2fa3af047e93e14a4743e4a1b7c669a04306b0b6 Mon Sep 17 00:00:00 2001 From: Stefan Tatschner Date: Fri, 24 Apr 2015 10:09:30 +0200 Subject: [PATCH 140/255] Some language improvements --- .../merge_requests/show/_mr_accept.html.haml | 35 ++++++++++--------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml index 882b219f6e..906cc11dc6 100644 --- a/app/views/projects/merge_requests/show/_mr_accept.html.haml +++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml @@ -1,14 +1,17 @@ - unless @allowed_to_merge - if @project.archived? %p - %strong Archived projects cannot be committed to! + %strong Archived projects do not provide commit access. - else .automerge_widget.cannot_be_merged.hide - %strong This request can't be merged automatically. Even if it could be merged, you don't have permission to do so. + %strong This merge request contains merge conflicts that must be resolved. + Only those with write access to this repository can merge merge requests. .automerge_widget.work_in_progress.hide - %strong This request can't be accepted because it is marked a Work In Progress. Even if it could be accepted, you don't have permission to do so. + %strong This merge request is marked as Work In Progress. + Only those with write access to this repository can merge merge requests. .automerge_widget.can_be_merged.hide - %strong This request can be merged automatically, but you don't have permission to do so. + %strong This request can be merged automatically. + Only those with write access to this repository can merge merge requests. - if @show_merge_controls @@ -34,7 +37,7 @@ %br .light - If you still want to merge this request manually - use + If you want to merge this request manually, you can use the %strong = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" @@ -42,47 +45,45 @@ .automerge_widget.no_satellite.hide %p %span - %strong This repository does not have satellite. Ask an administrator to fix this issue + %strong This repository does not have a satellite. Please ask an administrator to fix this issue! .automerge_widget.cannot_be_merged.hide %h4 - This request can't be merged with GitLab. - You should do it manually with + This pull request contains merge conflicts that must be resolved. + You can try it manually on the %strong - = link_to "#modal_merge_info", class: "underlined-link how_to_merge_link", title: "How To Merge", "data-toggle" => "modal" do - command line + = link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal" %p %button.btn.disabled{:type => 'button'} %i.fa.fa-warning Accept Merge Request   - This usually happens when Git can not resolve conflicts between branches automatically. + This happens when Git is not able to automatically resolve conflicts between branches. .automerge_widget.work_in_progress.hide %h4 - This request can't be accepted because it is marked a Work In Progress. + This request cannot be merged because it is marked as Work In Progress. %p %button.btn.disabled{:type => 'button'} %i.fa.fa-warning Accept Merge Request   - - When the merge request is ready, remove the "WIP" prefix from the title to allow it to be accepted. + When the merge request is ready, remove the "WIP" prefix from the title to allow merging. .automerge_widget.unchecked %p %strong %i.fa.fa-spinner.fa-spin - Checking for ability to automatically merge… + Checking automatic merge… .automerge_widget.already_cannot_be_merged.hide %p - %strong This merge request can not be merged. Try to reload the page. + %strong This merge request cannot be merged. Try to reload the page. .merge-in-progress.hide %p %i.fa.fa-spinner.fa-spin   - Merge is in progress. Please wait. Page will be automatically reloaded.   + Merge is in progress. Please wait… Page will be reloaded automatically.   From 0cd73885e6279c2a9a477f59eb76095f4e542858 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 23 May 2015 23:34:03 -0400 Subject: [PATCH 141/255] Fix git blame syntax highlighting when different commits break up lines Closes #1521 --- CHANGELOG | 1 + Gemfile | 2 +- Gemfile.lock | 4 +-- app/helpers/blob_helper.rb | 10 ++++---- app/views/projects/blame/show.html.haml | 2 +- spec/helpers/blob_helper_spec.rb | 33 +++++++++++++++++++++++++ 6 files changed, 43 insertions(+), 9 deletions(-) create mode 100644 spec/helpers/blob_helper_spec.rb diff --git a/CHANGELOG b/CHANGELOG index 7d7b8e0aef..b18d50beab 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -12,6 +12,7 @@ v 7.12.0 (unreleased) - Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu) - Remove Rack Attack monkey patches and bump to version 4.3.0 (Stan Hu) - Fix clone URL losing selection after a single click in Safari and Chrome (Stan Hu) + - Fix git blame syntax highlighting when different commits break up lines (Stan Hu) - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) - Disabled expansion of top/bottom blobs for new file diffs - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) diff --git a/Gemfile b/Gemfile index 8eb1f04000..26981f3e0a 100644 --- a/Gemfile +++ b/Gemfile @@ -277,4 +277,4 @@ end gem "newrelic_rpm" gem 'octokit', '3.7.0' -gem "rugments" +gem "rugments", "~> 1.0.0.beta7" diff --git a/Gemfile.lock b/Gemfile.lock index 80e4a44c1d..7dbc3b4ffa 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -531,7 +531,7 @@ GEM rubyntlm (0.5.0) rubypants (0.2.0) rugged (0.22.2) - rugments (1.0.0.beta6) + rugments (1.0.0.beta7) safe_yaml (0.9.7) sanitize (2.1.0) nokogiri (>= 1.4.4) @@ -781,7 +781,7 @@ DEPENDENCIES rqrcode-rails3 rspec-rails (= 2.99) rubocop (= 0.28.0) - rugments + rugments (~> 1.0.0.beta7) sanitize (~> 2.0) sass-rails (~> 4.0.2) sdoc diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 9fe5f82f02..50df380170 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -1,6 +1,6 @@ module BlobHelper - def highlight(blob_name, blob_content, nowrap = false) - formatter = Rugments::Formatters::HTML.new( + def highlight(blob_name, blob_content, nowrap: false, continue: false) + @formatter ||= Rugments::Formatters::HTML.new( nowrap: nowrap, cssclass: 'code highlight', lineanchors: true, @@ -8,11 +8,11 @@ module BlobHelper ) begin - lexer = Rugments::Lexer.guess(filename: blob_name, source: blob_content) - result = formatter.format(lexer.lex(blob_content)).html_safe + @lexer ||= Rugments::Lexer.guess(filename: blob_name, source: blob_content).new + result = @formatter.format(@lexer.lex(blob_content, continue: continue)).html_safe rescue lexer = Rugments::Lexers::PlainText - result = formatter.format(lexer.lex(blob_content)).html_safe + result = @formatter.format(lexer.lex(blob_content)).html_safe end result diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index 462f5b7afb..8019c7f456 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -32,5 +32,5 @@ %code :erb <% lines.each do |line| %> - <%= highlight(@blob.name, line, true).html_safe %> + <%= highlight(@blob.name, line, nowrap: true, continue: true).html_safe %> <% end %> diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb new file mode 100644 index 0000000000..e49e4e6d5d --- /dev/null +++ b/spec/helpers/blob_helper_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe BlobHelper do + describe 'highlight' do + let(:blob_name) { 'test.lisp' } + let(:no_context_content) { ":type \"assem\"))" } + let(:blob_content) { "(make-pathname :defaults name\n#{no_context_content}" } + let(:split_content) { blob_content.split("\n") } + + it 'should return plaintext for unknown lexer context' do + result = highlight(blob_name, no_context_content, nowrap: true, continue: false) + expect(result).to eq(':type "assem"))') + end + + it 'should highlight single block' do + expected = %Q[(make-pathname :defaults name +:type "assem"))] + + expect(highlight(blob_name, blob_content, nowrap: true, continue: false)).to eq(expected) + end + + it 'should highlight continued blocks' do + # Both lines have LC1 as ID since formatter doesn't support continue at the moment + expected = [ + '(make-pathname :defaults name', + ':type "assem"))' + ] + + result = split_content.map{ |content| highlight(blob_name, content, nowrap: true, continue: true) } + expect(result).to eq(expected) + end + end +end From 5c52eaa9d266ec201cf6558eef9efa5c3d8f939e Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 27 May 2015 22:39:11 -0400 Subject: [PATCH 142/255] Persist current merge request tab selection via URL Closes internal https://dev.gitlab.org/gitlab/gitlabhq/issues/2350 --- .../javascripts/merge_request.js.coffee | 76 ++++++++++++++----- .../merge_requests/_new_submit.html.haml | 43 ++++++----- .../projects/merge_requests/_show.html.haml | 35 ++++----- features/steps/project/merge_requests.rb | 5 +- 4 files changed, 100 insertions(+), 59 deletions(-) diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 7c1e2b822d..3937c428e2 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -3,15 +3,30 @@ #= require task_list class @MergeRequest + # Initialize MergeRequest behavior + # + # Options: + # action - String, current controller action + # diffs_loaded - Boolean, have diffs been pre-rendered server-side? + # (default: true if `action` is 'diffs', otherwise false) + # commits_loaded - Boolean, have commits been pre-rendered server-side? + # (default: false) + # + # check_enable - Boolean, whether to check automerge status + # url_to_automerge_check - String, URL to use to check automerge status + # current_status - String, current automerge status + # ci_enable - Boolean, whether a CI service is enabled + # url_to_ci_check - String, URL to use to check CI status + # constructor: (@opts) -> @initContextWidget() this.$el = $('.merge-request') - @diffs_loaded = if @opts.action == 'diffs' then true else false - @commits_loaded = false - this.activateTab(@opts.action) + @diffs_loaded = @opts.diffs_loaded or @opts.action == 'diffs' + @commits_loaded = @opts.commits_loaded or false this.bindEvents() + this.activateTabFromHash() this.initMergeWidget() this.$('.show-all-commits').on 'click', => @@ -65,8 +80,21 @@ class @MergeRequest , 'json' bindEvents: -> - this.$('.merge-request-tabs').on 'click', 'li', (event) => - this.activateTab($(event.currentTarget).data('action')) + this.$('.merge-request-tabs a[data-toggle="tab"]').on 'shown.bs.tab', (e) => + $target = $(e.target) + + # Nothing else to be done if we're on the first tab + return if $target.data('action') == 'notes' + + # Persist current tab selection via URL + href = $target.attr('href') + if href.substr(0,1) == '#' + location.replace("#!#{href.substr(1)}") + + # Lazy-load diffs + if $target.data('action') == 'diffs' + this.loadDiff() unless @diffs_loaded + $('.diff-header').trigger("sticky_kit:recalc") this.$('.accept_merge_request').on 'click', -> $('.automerge_widget.can_be_merged').hide() @@ -84,21 +112,27 @@ class @MergeRequest this.$('.remove_source_branch_in_progress').hide() this.$('.remove_source_branch_widget.failed').show() - activateTab: (action) -> - this.$('.merge-request-tabs li').removeClass 'active' - this.$('.tab-content').hide() - switch action - when 'diffs' - this.$('.merge-request-tabs .diffs-tab').addClass 'active' - this.loadDiff() unless @diffs_loaded - this.$('.diffs').show() - $(".diff-header").trigger("sticky_kit:recalc") - when 'commits' - this.$('.merge-request-tabs .commits-tab').addClass 'active' - this.$('.commits').show() - else - this.$('.merge-request-tabs .notes-tab').addClass 'active' - this.$('.notes').show() + # Activates a tab section based on the `#!` URL hash + # + # If no hash value is present (i.e., on the initial page load), the first tab + # is selected by default. + # + # ... unless the current controller action is `diffs`, in which case that tab + # is selected instead. Fun, right? + # + # Note: We use a `#!` instead of a standard URL hash for two reasons: + # + # 1. Prevents the hash acting like an anchor and scrolling the page. + # 2. Prevents mutating browser history. + activateTabFromHash: -> + # Correct the hash if we came here directly via the `/diffs` path + if location.hash == '' and @opts.action == 'diffs' + location.replace('#!diffs') + + if location.hash == '' + this.$('.merge-request-tabs a[data-toggle="tab"]:first').tab('show') + else if location.hash.substr(0,2) == '#!' + this.$(".merge-request-tabs a[href='##{location.hash.substr(2)}']").tab("show") showState: (state) -> $('.automerge_widget').hide() @@ -127,7 +161,7 @@ class @MergeRequest loadDiff: (event) -> $.ajax type: 'GET' - url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + ".json" + url: this.$('.merge-request-tabs .diffs-tab a').data('source') + ".json" beforeSend: => this.$('.mr-loading-status .loading').show() complete: => diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 24a9563dd4..e83b764992 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -19,30 +19,31 @@ .mr-compare.merge-request %ul.nav.nav-tabs.merge-request-tabs - %li.commits-tab{data: {action: 'commits', toggle: 'tab'}} - = link_to url_for(params) do - %i.fa.fa-history + %li.commits-tab + = link_to '#commits', data: {action: 'commits', toggle: 'tab'} do + = icon('history') Commits %span.badge= @commits.size - %li.diffs-tab{data: {action: 'diffs', toggle: 'tab'}} - = link_to url_for(params) do - %i.fa.fa-list-alt + %li.diffs-tab + = link_to '#diffs', data: {action: 'diffs', toggle: 'tab'} do + = icon('list-alt') Changes %span.badge= @diffs.size - .commits.tab-content - = render "projects/commits/commits", project: @project - .diffs.tab-content - - if @diffs.present? - = render "projects/diffs/diffs", diffs: @diffs, project: @project - - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE - .alert.alert-danger - %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. - %p To preserve performance the line changes are not shown. - - else - .alert.alert-danger - %h4 This comparison includes a huge diff. - %p To preserve performance the line changes are not shown. + .tab-content + #commits.commits.tab-pane + = render "projects/commits/commits", project: @project + #diffs.diffs.tab-pane + - if @diffs.present? + = render "projects/diffs/diffs", diffs: @diffs, project: @project + - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE + .alert.alert-danger + %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. + %p To preserve performance the line changes are not shown. + - else + .alert.alert-danger + %h4 This comparison includes a huge diff. + %p To preserve performance the line changes are not shown. :javascript $('.assign-to-me-link').on('click', function(e){ @@ -55,6 +56,8 @@ :javascript var merge_request merge_request = new MergeRequest({ - action: 'commits' + action: 'diffs', + diffs_loaded: true, + commits_loaded: true }); diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index c2f5cdacae..0d894e360e 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -25,7 +25,7 @@ %span.pull-right .btn-group %a.btn.dropdown-toggle{ data: {toggle: :dropdown} } - %i.fa.fa-download + = icon('download') Download as %span.caret %ul.dropdown-menu @@ -37,29 +37,30 @@ - if @commits.present? %ul.nav.nav-tabs.merge-request-tabs - %li.notes-tab{data: {action: 'notes', toggle: 'tab'}} - = link_to merge_request_path(@merge_request) do - %i.fa.fa-comments + %li.notes-tab + = link_to '#notes', data: {action: 'notes', toggle: 'tab'} do + = icon('comments') Discussion %span.badge= @merge_request.mr_and_commit_notes.user.count - %li.commits-tab{data: {action: 'commits', toggle: 'tab'}} - = link_to merge_request_path(@merge_request), title: 'Commits' do - %i.fa.fa-history + %li.commits-tab + = link_to '#commits', data: {action: 'commits', toggle: 'tab'} do + = icon('history') Commits %span.badge= @commits.size - %li.diffs-tab{data: {action: 'diffs', toggle: 'tab'}} - = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) do - %i.fa.fa-list-alt + %li.diffs-tab + = link_to '#diffs', data: {source: diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), action: 'diffs', toggle: 'tab'} do + = icon('list-alt') Changes %span.badge= @merge_request.diffs.size - .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" } - = render "projects/merge_requests/discussion" - .commits.tab-content - = render "projects/merge_requests/show/commits" - .diffs.tab-content - - if current_page?(action: 'diffs') - = render "projects/merge_requests/show/diffs" + .tab-content + #notes.notes.tab-pane.voting_notes + = render "projects/merge_requests/discussion" + #commits.commits.tab-pane + = render "projects/merge_requests/show/commits" + #diffs.diffs.tab-pane + - if current_page?(action: 'diffs') + = render "projects/merge_requests/show/diffs" .mr-loading-status = spinner diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 92de94a75d..48bb316e20 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -113,7 +113,10 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps end step 'I click on the Changes tab via Javascript' do - find('.diffs-tab').click + within '.merge-request-tabs' do + click_link 'Changes' + end + sleep 2 end From 694743170c75263bcecc1cd81314a35741002d23 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 27 May 2015 22:56:35 -0400 Subject: [PATCH 143/255] Disable Unicorn::WorkerKiller in non-production environments --- config.ru | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/config.ru b/config.ru index e90863a5c2..a2525c8136 100644 --- a/config.ru +++ b/config.ru @@ -2,11 +2,14 @@ if defined?(Unicorn) require 'unicorn' - # Unicorn self-process killer - require 'unicorn/worker_killer' - # Max memory size (RSS) per worker - use Unicorn::WorkerKiller::Oom, (200 * (1 << 20)), (250 * (1 << 20)) + if ENV['RAILS_ENV'] == 'production' || ENV['RAILS_ENV'] == 'staging' + # Unicorn self-process killer + require 'unicorn/worker_killer' + + # Max memory size (RSS) per worker + use Unicorn::WorkerKiller::Oom, (200 * (1 << 20)), (250 * (1 << 20)) + end end require ::File.expand_path('../config/environment', __FILE__) From 3865a1d92585cb31864b5d0f1b325c3585b5c681 Mon Sep 17 00:00:00 2001 From: Jeroen van Baarsen Date: Thu, 21 May 2015 10:44:17 +0200 Subject: [PATCH 144/255] Allow special characters in users bio **What does this do?** It removes the very strict sanitation on the users bio field, so that people can have a bio like "I <3 GitLab" **Why is this needed?** Currently when you enter a bio with "I <3 GitLab", we only store "I ". This is unexpected behaviour, since we want users to have a normal profile, without having to worry what characters are allowed and which are not. **Related issues:** Fixes https://github.com/gitlabhq/gitlabhq/issues/5625 Signed-off-by: Jeroen van Baarsen --- CHANGELOG | 3 +++ app/models/user.rb | 2 +- features/steps/profile/profile.rb | 24 +++++++++++++----------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 46a052e1a1..a339038cbe 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -37,6 +37,9 @@ v 7.11.1 v 7.11.0 - Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger) - Get editing comments to work in Chrome 43 again. + - Allow special character in users bio. I.e.: I <3 GitLab + +v 7.11.0 (unreleased) - Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu) - Don't show duplicate deploy keys - Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger) diff --git a/app/models/user.rb b/app/models/user.rb index 50ca4bc5ac..65e726f7d5 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -483,7 +483,7 @@ class User < ActiveRecord::Base end def sanitize_attrs - %w(name username skype linkedin twitter bio).each do |attr| + %w(name username skype linkedin twitter).each do |attr| value = self.send(attr) self.send("#{attr}=", Sanitize.clean(value)) if value.present? end diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 791982d16c..1571ddbee3 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -7,21 +7,23 @@ class Spinach::Features::Profile < Spinach::FeatureSteps end step 'I change my profile info' do - fill_in "user_skype", with: "testskype" - fill_in "user_linkedin", with: "testlinkedin" - fill_in "user_twitter", with: "testtwitter" - fill_in "user_website_url", with: "testurl" - fill_in "user_location", with: "Ukraine" - click_button "Save changes" + fill_in 'user_skype', with: 'testskype' + fill_in 'user_linkedin', with: 'testlinkedin' + fill_in 'user_twitter', with: 'testtwitter' + fill_in 'user_website_url', with: 'testurl' + fill_in 'user_location', with: 'Ukraine' + fill_in 'user_bio', with: 'I <3 GitLab' + click_button 'Save changes' @user.reload end step 'I should see new profile info' do - @user.skype.should == 'testskype' - @user.linkedin.should == 'testlinkedin' - @user.twitter.should == 'testtwitter' - @user.website_url.should == 'testurl' - find("#user_location").value.should == "Ukraine" + expect(@user.skype).to eq 'testskype' + expect(@user.linkedin).to eq 'testlinkedin' + expect(@user.twitter).to eq 'testtwitter' + expect(@user.website_url).to eq 'testurl' + expect(@user.bio).to eq 'I <3 GitLab' + find('#user_location').value.should == 'Ukraine' end step 'I change my avatar' do From 29b75e14809925f3c0c93d19fe543464c06da765 Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Thu, 28 May 2015 10:59:26 +0200 Subject: [PATCH 145/255] update changelog for 7.11.4 --- CHANGELOG | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 46a052e1a1..1cdb669d5b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -24,17 +24,24 @@ v 7.12.0 (unreleased) - Consistently refer to MRs as either Accepted or Rejected. - Add Accepted and Rejected tabs to MR lists. - Prefix EmailsOnPush email subject with `[Git]`. - - Group project contributions by both name and email. + - Group project contributions by both name and email. - Clarify navigation labels for Project Settings and Group Settings. - Move user avatar and logout button to sidebar +v 7.11.4 + - Fix missing bullets when creating lists + - Set rel="nofollow" on external links + +v 7.11.3 + - no changes + v 7.11.2 - no changes v 7.11.1 - no changes -v 7.11.0 +v 7.11.0 - Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger) - Get editing comments to work in Chrome 43 again. - Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu) @@ -189,12 +196,12 @@ v 7.10.0 - Ability to skip some items from backup (database, respositories or uploads) - Archive repositories in background worker. - Import GitHub, Bitbucket or GitLab.com projects owned by authenticated user into current namespace. - - Project labels are now available over the API under the "tag_list" field (Cristian Medina) + - Project labels are now available over the API under the "tag_list" field (Cristian Medina) - Fixed link paths for HTTP and SSH on the admin project view (Jeremy Maziarz) - Fix and improve help rendering (Sullivan Sénéchal) - Fix final line in EmailsOnPush email diff being rendered as error. - Prevent duplicate Buildkite service creation. - - Fix git over ssh errors 'fatal: protocol error: bad line length character' + - Fix git over ssh errors 'fatal: protocol error: bad line length character' - Automatically setup GitLab CI project for forks if origin project has GitLab CI enabled - Bust group page project list cache when namespace name or path changes. - Explicitly set image alt-attribute to prevent graphical glitches if gravatars could not be loaded @@ -203,7 +210,7 @@ v 7.10.0 - Fix stuck Merge Request merging events from old installations (Ben Bodenmiller) - Fix merge request comments on files with multiple commits - Fix Resource Owner Password Authentication Flow - + v 7.9.4 - Security: Fix project import URL regex to prevent arbitary local repos from being imported - Fixed issue where only 25 commits would load in file listings @@ -507,10 +514,10 @@ v 7.5.0 - Fix raw view for public snippets - Use secret token with GitLab internal API. - Add missing timestamps to 'members' table - + v 7.4.5 - Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2) - + v 7.4.4 - No changes From 499154518a1555523ed5f203c9ce4bbe6317c9a5 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 May 2015 12:00:02 +0200 Subject: [PATCH 146/255] You can not remove user if he/she is an only owner of group To prevent loose of group data you need to transfer or remove group first before you can remove user Signed-off-by: Dmitriy Zaporozhets --- app/controllers/admin/users_controller.rb | 6 +---- app/controllers/registrations_controller.rb | 2 +- app/models/user.rb | 4 ++++ app/services/delete_user_service.rb | 10 ++++++++ app/views/admin/users/index.html.haml | 9 +++---- app/views/admin/users/show.html.haml | 24 +++++++++++-------- app/views/profiles/accounts/show.html.haml | 26 ++++++++++++--------- lib/api/users.rb | 2 +- spec/models/user_spec.rb | 18 +++++++++++++- 9 files changed, 68 insertions(+), 33 deletions(-) create mode 100644 app/services/delete_user_service.rb diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index d36e359934..06d6d61e90 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -86,11 +86,7 @@ class Admin::UsersController < Admin::ApplicationController end def destroy - # 1. Remove groups where user is the only owner - user.solo_owned_groups.map(&:destroy) - - # 2. Remove user with all authored content including personal projects - user.destroy + DeleteUserService.new.execute(user) respond_to do |format| format.html { redirect_to admin_users_path } diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 830751a989..6e57fded33 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -6,7 +6,7 @@ class RegistrationsController < Devise::RegistrationsController end def destroy - current_user.destroy + DeleteUserService.new.execute(user) respond_to do |format| format.html { redirect_to new_user_session_path, notice: "Account successfully removed." } diff --git a/app/models/user.rb b/app/models/user.rb index 50ca4bc5ac..c1bb51e86f 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -689,4 +689,8 @@ class User < ActiveRecord::Base true end + + def can_be_removed? + !solo_owned_groups.present? + end end diff --git a/app/services/delete_user_service.rb b/app/services/delete_user_service.rb new file mode 100644 index 0000000000..d259b4efca --- /dev/null +++ b/app/services/delete_user_service.rb @@ -0,0 +1,10 @@ +class DeleteUserService + def execute(user) + if user.solo_owned_groups.present? + user.errors[:base] << 'You must transfer ownership or delete groups before you can remove user' + user + else + user.destroy + end + end +end diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index fe64847023..45dee86b01 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -79,11 +79,12 @@ %i.fa.fa-envelope = mail_to user.email, user.email, class: 'light'   - = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn btn-sm" + = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn btn-xs" - unless user == current_user - if user.blocked? - = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-sm success" + = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-xs btn-success" - else - = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-sm btn-remove" - = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All tickets linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-sm btn-remove" + = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs btn-warning" + - if user.can_be_removed? + = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All tickets linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove" = paginate @users, theme: "gitlab" diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 7fc8520610..f7195ac332 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -140,18 +140,22 @@ .panel-heading Remove user .panel-body - %p Deleting a user has the following effects: - %ul - %li All user content like authored issues, snippets, comments will be removed - - rp = @user.personal_projects.count - - unless rp.zero? - %li #{pluralize rp, 'personal project'} will be removed and cannot be restored + - if @user.can_be_removed? + %p Deleting a user has the following effects: + %ul + %li All user content like authored issues, snippets, comments will be removed + - rp = @user.personal_projects.count + - unless rp.zero? + %li #{pluralize rp, 'personal project'} will be removed and cannot be restored + %br + = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" + - else - if @user.solo_owned_groups.present? - %li - Next groups with all content will be removed: + %p + This user is currently an owner in these groups: %strong #{@user.solo_owned_groups.map(&:name).join(', ')} - %br - = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" + %p + You must transfer ownership or delete these groups before you can delete this user. #profile.tab-pane .row diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 06bad7dd84..4d1d50dcba 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -91,15 +91,19 @@ %legend Remove account %div - %p Deleting an account has the following effects: - %ul - %li All user content like authored issues, snippets, comments will be removed - - rp = current_user.personal_projects.count - - unless rp.zero? - %li #{pluralize rp, 'personal project'} will be removed and cannot be restored - - if current_user.solo_owned_groups.present? - %li - The following groups will be abandoned. You should transfer or remove them: - %strong #{current_user.solo_owned_groups.map(&:name).join(', ')} - = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove" + - if @user.can_be_removed? + %p Deleting an account has the following effects: + %ul + %li All user content like authored issues, snippets, comments will be removed + - rp = current_user.personal_projects.count + - unless rp.zero? + %li #{pluralize rp, 'personal project'} will be removed and cannot be restored + = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove" + - else + - if @user.solo_owned_groups.present? + %p + Your account is currently an owner in these groups: + %strong #{@user.solo_owned_groups.map(&:name).join(', ')} + %p + You must transfer ownership or delete these groups before you can delete yur account. diff --git a/lib/api/users.rb b/lib/api/users.rb index 032a5d76e4..7d4c68c741 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -194,7 +194,7 @@ module API user = User.find_by(id: params[:id]) if user - user.destroy + DeleteUserService.new.execute(user) else not_found!('User') end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e1205c18a8..49c7b7d99c 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -572,7 +572,6 @@ describe User do end describe "#contributed_projects_ids" do - subject { create(:user) } let!(:project1) { create(:project) } let!(:project2) { create(:project, forked_from_project: project3) } @@ -598,4 +597,21 @@ describe User do expect(subject.contributed_projects_ids).not_to include(project2.id) end end + + describe :can_be_removed? do + subject { create(:user) } + + context 'no owned groups' do + it { expect(subject.can_be_removed?).to be_truthy } + end + + context 'has owned groups' do + before do + group = create(:group) + group.add_owner(subject) + end + + it { expect(subject.can_be_removed?).to be_falsey } + end + end end From 9cc23910a6817b663defb0943cd055836e98366f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 May 2015 12:03:53 +0200 Subject: [PATCH 147/255] Add CHANGELOG item Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 46a052e1a1..56263b36f6 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -27,6 +27,7 @@ v 7.12.0 (unreleased) - Group project contributions by both name and email. - Clarify navigation labels for Project Settings and Group Settings. - Move user avatar and logout button to sidebar + - You can not remove user if he/she is an only owner of group v 7.11.2 - no changes From 2db026793df81f97515a13e1b4355436f6cc76c3 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 May 2015 12:53:04 +0200 Subject: [PATCH 148/255] Fix current user removal Signed-off-by: Dmitriy Zaporozhets --- app/controllers/registrations_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 6e57fded33..6ccc7934f2 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -6,7 +6,7 @@ class RegistrationsController < Devise::RegistrationsController end def destroy - DeleteUserService.new.execute(user) + DeleteUserService.new.execute(current_user) respond_to do |format| format.html { redirect_to new_user_session_path, notice: "Account successfully removed." } From dcc9dc94d56fc909fa7264e2c0e0541f0029272f Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 May 2015 14:44:38 +0200 Subject: [PATCH 149/255] Re-organize profile settings titles and headers Signed-off-by: Dmitriy Zaporozhets --- app/views/layouts/profile.html.haml | 4 ++-- app/views/profiles/applications.html.haml | 2 +- app/views/profiles/design.html.haml | 2 +- app/views/profiles/emails/index.html.haml | 2 +- app/views/profiles/keys/index.html.haml | 2 +- app/views/profiles/notifications/show.html.haml | 2 +- app/views/profiles/passwords/edit.html.haml | 3 ++- app/views/profiles/show.html.haml | 4 ++-- 8 files changed, 11 insertions(+), 10 deletions(-) diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index 9799b4cc4d..3193206fe1 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -1,5 +1,5 @@ -- page_title "Profile" -- header_title "Profile", profile_path +- page_title "Settings" +- header_title "Settings", profile_path - sidebar "profile" = render template: "layouts/application" diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml index c4f6f59624..06857c302a 100644 --- a/app/views/profiles/applications.html.haml +++ b/app/views/profiles/applications.html.haml @@ -1,6 +1,6 @@ - page_title "Applications" %h3.page-title - Application Settings + = page_title %p.light OAuth2 protocol settings below. diff --git a/app/views/profiles/design.html.haml b/app/views/profiles/design.html.haml index af284f6040..6a3b4e88db 100644 --- a/app/views/profiles/design.html.haml +++ b/app/views/profiles/design.html.haml @@ -1,6 +1,6 @@ - page_title "Design" %h3.page-title - Design Settings + = page_title %p.light Appearance settings will be saved to your profile and made available across all devices. %hr diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 2c0d0e10a4..88e4f69b14 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -1,6 +1,6 @@ - page_title "Emails" %h3.page-title - Email Settings + = page_title %p.light Your %b Primary Email diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index e3af0d4e18..06655f7ba3 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -1,6 +1,6 @@ - page_title "SSH Keys" %h3.page-title - SSH Keys Settings + = page_title .pull-right = link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new" %p.light diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index a74d97dac3..9480a19f5b 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -1,6 +1,6 @@ - page_title "Notifications" %h3.page-title - Notifications Settings + = page_title %p.light These are your global notification settings. %hr diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 21dabbdfe2..399ae98adf 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -1,5 +1,6 @@ - page_title "Password" -%h3.page-title Password Settings +%h3.page-title + = page_title %p.light - if @user.password_automatically_set? Set your password. diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 29c3090511..62fac46df2 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,6 +1,6 @@ -- page_title "Settings" +- page_title "Profile" %h3.page-title - Profile Settings + = page_title %p.light This information will appear on your profile. - if current_user.ldap_user? From 47989d6037cf8dee0897a22ecb53921c680e9bce Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 May 2015 14:54:05 +0200 Subject: [PATCH 150/255] Consistent header look for setting pages Signed-off-by: Dmitriy Zaporozhets --- app/views/profiles/accounts/show.html.haml | 5 ++++ app/views/profiles/applications.html.haml | 1 + app/views/profiles/emails/index.html.haml | 32 ++++++++++++---------- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 06bad7dd84..5939c951fe 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -1,4 +1,9 @@ - page_title "Account" +%h3.page-title + = page_title +%p.light + Change your username and basic account settings. +%hr - if current_user.ldap_user? .alert.alert-info Some options are unavailable for LDAP accounts diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml index 06857c302a..353b2496a7 100644 --- a/app/views/profiles/applications.html.haml +++ b/app/views/profiles/applications.html.haml @@ -3,6 +3,7 @@ = page_title %p.light OAuth2 protocol settings below. +%hr %fieldset.oauth-applications %legend Your applications diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index 88e4f69b14..66812872c4 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -2,22 +2,26 @@ %h3.page-title = page_title %p.light - Your - %b Primary Email - will be used for avatar detection and web based operations, such as edits and merges. -%p.light - Your - %b Notification Email - will be used for account notifications. -%p.light - Your - %b Public Email - will be displayed on your public profile. -%p.light - All email addresses will be used to identify your commits. - + Control emails linked to your account %hr + +%ul + %li + Your + %b Primary Email + will be used for avatar detection and web based operations, such as edits and merges. + %li + Your + %b Notification Email + will be used for account notifications. + %li + Your + %b Public Email + will be displayed on your public profile. + %li + All email addresses will be used to identify your commits. + .panel.panel-default .panel-heading Emails (#{@emails.count + 1}) From 35d0d774a1b2aa47a68b7c77ba163886327cb4cd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 May 2015 15:26:37 +0200 Subject: [PATCH 151/255] Prefer panels over fieldset when different forms Signed-off-by: Dmitriy Zaporozhets --- app/views/profiles/accounts/show.html.haml | 84 ++++++++++---------- app/views/profiles/applications.html.haml | 13 ++-- app/views/profiles/design.html.haml | 90 +++++++++++----------- 3 files changed, 98 insertions(+), 89 deletions(-) diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index 5939c951fe..55bd909639 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -9,10 +9,10 @@ Some options are unavailable for LDAP accounts .account-page - %fieldset.update-token - %legend + .panel.panel-default.update-token + .panel-heading Reset Private token - %div + .panel-body = form_for @user, url: reset_private_token_profile_path, method: :put do |f| .data %p @@ -32,12 +32,11 @@ = f.submit 'Generate', class: "btn success btn-build-token" - unless current_user.ldap_user? - %fieldset - - if current_user.otp_required_for_login - %legend.text-success - = icon('check') + - if current_user.otp_required_for_login + .panel.panel-success + .panel-heading Two-factor Authentication enabled - %div + .panel-body .pull-right = link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm', data: { confirm: 'Are you sure?' } @@ -48,9 +47,11 @@ = link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' } invalidating all previous codes. - - else - %legend Two-factor Authentication - %div + - else + .panel.panel-default + .panel-heading + Two-factor Authentication + .panel-body %p Increase your account's security by enabling two-factor authentication (2FA). %p @@ -60,42 +61,45 @@ = link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success' - if show_profile_social_tab? - %fieldset - %legend Connected Accounts - .oauth-buttons.append-bottom-10 - %p Click on icon to activate signin with one of the following services - - enabled_social_providers.each do |provider| - .btn-group - = link_to oauth_image_tag(provider), omniauth_authorize_path(User, provider), - method: :post, class: "btn btn-lg #{'active' if oauth_active?(provider)}" - - if oauth_active?(provider) - = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'btn btn-lg' do - = icon('close') + .panel.panel-default + .panel-heading + Connected Accounts + .panel-body + .oauth-buttons.append-bottom-10 + %p Click on icon to activate signin with one of the following services + - enabled_social_providers.each do |provider| + .btn-group + = link_to oauth_image_tag(provider), omniauth_authorize_path(User, provider), + method: :post, class: "btn btn-lg #{'active' if oauth_active?(provider)}" + - if oauth_active?(provider) + = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'btn btn-lg' do + = icon('close') - if show_profile_username_tab? - %fieldset.update-username - %legend + .panel.panel-warning.update-username + .panel-heading Change Username - = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f| - %p - Changing your username will change path to all personal projects! - %div - = f.text_field :username, required: true, class: 'form-control' -   - .loading-gif.hide + .panel-body + = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f| %p - = icon('spinner spin') - Saving new username - %p.light - = user_url(@user) - %div - = f.submit 'Save username', class: "btn btn-warning" + Changing your username will change path to all personal projects! + %div + = f.text_field :username, required: true, class: 'form-control' +   + .loading-gif.hide + %p + = icon('spinner spin') + Saving new username + %p.light + = user_url(@user) + %div + = f.submit 'Save username', class: "btn btn-warning" - if show_profile_remove_tab? - %fieldset.remove-account - %legend + .panel.panel-danger.remove-account + .panel-heading Remove account - %div + .panel-body %p Deleting an account has the following effects: %ul %li All user content like authored issues, snippets, comments will be removed diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml index 353b2496a7..c145a9b7f6 100644 --- a/app/views/profiles/applications.html.haml +++ b/app/views/profiles/applications.html.haml @@ -5,9 +5,11 @@ OAuth2 protocol settings below. %hr -%fieldset.oauth-applications - %legend Your applications - %p= link_to 'New Application', new_oauth_application_path, class: 'btn btn-success' +.oauth-applications + %h3 + Your applications + .pull-right + = link_to 'New Application', new_oauth_application_path, class: 'btn btn-success' - if @applications.any? %table.table.table-striped %thead @@ -28,8 +30,9 @@ %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm' %td= render 'doorkeeper/applications/delete_form', application: application -%fieldset.oauth-authorized-applications.prepend-top-20 - %legend Authorized applications +.oauth-authorized-applications.prepend-top-20 + %h3 + Authorized applications - if @authorized_tokens.any? %table.table.table-striped diff --git a/app/views/profiles/design.html.haml b/app/views/profiles/design.html.haml index 6a3b4e88db..f450ec1c01 100644 --- a/app/views/profiles/design.html.haml +++ b/app/views/profiles/design.html.haml @@ -6,49 +6,51 @@ %hr = form_for @user, url: profile_path, remote: true, method: :put do |f| - %fieldset.application-theme - %legend + .panel.panel-default.application-theme + .panel-heading Application theme - .themes_opts - = label_tag do - .prev.default - = f.radio_button :theme_id, 1 - Graphite - - = label_tag do - .prev.classic - = f.radio_button :theme_id, 2 - Charcoal - - = label_tag do - .prev.modern - = f.radio_button :theme_id, 3 - Green - - = label_tag do - .prev.gray - = f.radio_button :theme_id, 4 - Gray - - = label_tag do - .prev.violet - = f.radio_button :theme_id, 5 - Violet - - = label_tag do - .prev.blue - = f.radio_button :theme_id, 6 - Blue - %br - .clearfix - - %fieldset.code-preview-theme - %legend - Code preview theme - .code_highlight_opts - - color_schemes.each do |color_scheme_id, color_scheme| + .panel-body + .themes_opts = label_tag do - .prev - = image_tag "#{color_scheme}-scheme-preview.png" - = f.radio_button :color_scheme_id, color_scheme_id - = color_scheme.gsub(/[-_]+/, ' ').humanize + .prev.default + = f.radio_button :theme_id, 1 + Graphite + + = label_tag do + .prev.classic + = f.radio_button :theme_id, 2 + Charcoal + + = label_tag do + .prev.modern + = f.radio_button :theme_id, 3 + Green + + = label_tag do + .prev.gray + = f.radio_button :theme_id, 4 + Gray + + = label_tag do + .prev.violet + = f.radio_button :theme_id, 5 + Violet + + = label_tag do + .prev.blue + = f.radio_button :theme_id, 6 + Blue + %br + .clearfix + + .panel.panel-default.code-preview-theme + .panel-heading + Code preview theme + .panel-body + .code_highlight_opts + - color_schemes.each do |color_scheme_id, color_scheme| + = label_tag do + .prev + = image_tag "#{color_scheme}-scheme-preview.png" + = f.radio_button :color_scheme_id, color_scheme_id + = color_scheme.gsub(/[-_]+/, ' ').humanize From 9e50f28ee9b5062c2b1822da95e76c86b44efafa Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 May 2015 15:32:28 +0200 Subject: [PATCH 152/255] Fix profile tests after header rename Signed-off-by: Dmitriy Zaporozhets --- features/steps/profile/notifications.rb | 2 +- features/steps/profile/profile.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/steps/profile/notifications.rb b/features/steps/profile/notifications.rb index 13e93618eb..b6e03b549a 100644 --- a/features/steps/profile/notifications.rb +++ b/features/steps/profile/notifications.rb @@ -7,6 +7,6 @@ class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps end step 'I should see global notifications settings' do - page.should have_content "Notifications Settings" + page.should have_content "Notifications" end end diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 791982d16c..b8f79f70ca 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -3,7 +3,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps include SharedPaths step 'I should see my profile info' do - page.should have_content "Profile Settings" + page.should have_content "This information will appear on your profile" end step 'I change my profile info' do From a01737ac78dc605d67f306c8fa498831b8b0c020 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 May 2015 17:30:05 +0200 Subject: [PATCH 153/255] Use panels instead of well for widgets in project sidebar Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/projects.scss | 14 +- app/views/projects/_aside.html.haml | 144 ++++++++++----------- 2 files changed, 78 insertions(+), 80 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 83771480cb..ee8c746d2d 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -209,13 +209,9 @@ ul.nav.nav-projects-tabs { line-height: 1.5; } - .well { - padding: 14px; - - h4 { - font-weight: normal; - margin: 0; - color: #555; + .panel { + .panel-heading, .panel-footer { + background-color: #fcfcfc; } .actions { @@ -224,10 +220,12 @@ ul.nav.nav-projects-tabs { .nav-pills a { padding: 10px; + font-weight: bold; + color: $gl-link-color; } .nav { - margin: 10px 0; + margin-bottom: 10px; } } diff --git a/app/views/projects/_aside.html.haml b/app/views/projects/_aside.html.haml index e90c7b26dd..000a40b466 100644 --- a/app/views/projects/_aside.html.haml +++ b/app/views/projects/_aside.html.haml @@ -1,94 +1,94 @@ .clearfix - unless @project.empty_repo? - .well - %h4.visibility-level-label + .panel.panel-default + .panel-heading = visibility_level_icon(@project.visibility_level) = "#{visibility_level_label(@project.visibility_level).capitalize} project" - - if @repository.changelog || @repository.license || @repository.contribution_guide - %ul.nav.nav-pills - - if @repository.changelog - %li.hidden-xs - = link_to changelog_url(@project) do - Changelog - - if @repository.license - %li - = link_to license_url(@project) do - License - - if @repository.contribution_guide - %li - = link_to contribution_guide_url(@project) do - Contribution guide + .panel-body + - if @repository.changelog || @repository.license || @repository.contribution_guide + %ul.nav.nav-pills + - if @repository.changelog + %li.hidden-xs + = link_to changelog_url(@project) do + Changelog + - if @repository.license + %li + = link_to license_url(@project) do + License + - if @repository.contribution_guide + %li + = link_to contribution_guide_url(@project) do + Contribution guide - .actions - - if can? current_user, :write_issue, @project - = link_to url_for_new_issue(@project, only_path: true), title: "New Issue", class: 'btn btn-sm append-right-10' do - = icon("exclamation-circle fw") - New Issue + .actions + - if can? current_user, :write_issue, @project + = link_to url_for_new_issue(@project, only_path: true), title: "New Issue", class: 'btn btn-sm append-right-10' do + = icon("exclamation-circle fw") + New Issue - - if can? current_user, :write_merge_request, @project - = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-sm", title: "New Merge Request" do - = icon("plus fw") - New Merge Request + - if can? current_user, :write_merge_request, @project + = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-sm", title: "New Merge Request" do + = icon("plus fw") + New Merge Request - - if forked_from_project = @project.forked_from_project - .well - %h4 - = icon("code-fork fw") - Forked from - .pull-right - = link_to forked_from_project.namespace.try(:name), project_path(forked_from_project) - - - if version = @repository.version - .well - %h4 - = icon("clock-o fw") - Version - .pull-right - = link_to version_url(@project) do - = @repository.blob_by_oid(version.id).data - - - @project.ci_services.each do |ci_service| - - if ci_service.active? && ci_service.respond_to?(:builds_path) - .well - %h4 - = icon("check fw") - = ci_service.title + - if forked_from_project = @project.forked_from_project + .panel-footer + = icon("code-fork fw") + Forked from .pull-right - - if ci_service.respond_to?(:status_img_path) - = link_to ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' do - = image_tag ci_service.status_img_path, alt: "build status", class: 'ci-status-image' - - else - = link_to 'view builds', ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' + = link_to forked_from_project.namespace.try(:name), project_path(forked_from_project) + + + - @project.ci_services.each do |ci_service| + - if ci_service.active? && ci_service.respond_to?(:builds_path) + .panel-footer + = icon("check fw") + = ci_service.title + .pull-right + - if ci_service.respond_to?(:status_img_path) + = link_to ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' do + = image_tag ci_service.status_img_path, alt: "build status", class: 'ci-status-image' + - else + = link_to 'view builds', ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' + - unless @project.empty_repo? - .well - %h4 + .panel.panel-default + .panel-heading = icon("archive fw") Repository + .panel-body + %ul.nav.nav-pills + %li + = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do + = pluralize(number_with_delimiter(@repository.commit_count), 'commit') + %li + = link_to namespace_project_branches_path(@project.namespace, @project) do + = pluralize(number_with_delimiter(@repository.branch_names.count), 'branch') + %li + = link_to namespace_project_tags_path(@project.namespace, @project) do + = pluralize(number_with_delimiter(@repository.tag_names.count), 'tag') - %ul.nav.nav-pills - %li - = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do - = pluralize(number_with_delimiter(@repository.commit_count), 'commit') - %li - = link_to namespace_project_branches_path(@project.namespace, @project) do - = pluralize(number_with_delimiter(@repository.branch_names.count), 'branch') - %li - = link_to namespace_project_tags_path(@project.namespace, @project) do - = pluralize(number_with_delimiter(@repository.tag_names.count), 'tag') + .actions + = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-sm append-right-10' do + %i.fa.fa-exchange + Compare code - .actions - = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-sm append-right-10' do - %i.fa.fa-exchange - Compare code - - - if can?(current_user, :download_code, @project) - = render 'projects/repositories/download_archive', split_button: true, btn_class: 'btn-group-sm' + - if can?(current_user, :download_code, @project) + = render 'projects/repositories/download_archive', split_button: true, btn_class: 'btn-group-sm' + - if version = @repository.version + .panel-footer + = icon("clock-o fw") + Version + .pull-right + = link_to version_url(@project) do + = @repository.blob_by_oid(version.id).data = render "shared/clone_panel" - if @project.archived? + %br .alert.alert-warning %h4 = icon("exclamation-triangle fw") From 08a12f24b00a0d3c3b42ff80baaebba1f26a33a6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 28 May 2015 20:36:47 +0200 Subject: [PATCH 154/255] Make user settings account page nicer Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/profile.scss | 5 +++-- app/views/profiles/accounts/show.html.haml | 23 +++++++++++----------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index 5b528b38d3..5a5fbc468a 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -84,8 +84,9 @@ } .btn { - line-height: 36px; - height: 56px; + line-height: 40px; + height: 42px; + padding: 0px 12px; img { width: 32px; diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index c30a3f5d79..a26d4e0c75 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -26,20 +26,23 @@ - if current_user.private_token = text_field_tag "token", current_user.private_token, class: "form-control" %div - = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token" + = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default btn-build-token" - else %span You don`t have one yet. Click generate to fix it. - = f.submit 'Generate', class: "btn success btn-build-token" + = f.submit 'Generate', class: "btn btn-default btn-build-token" - unless current_user.ldap_user? - - if current_user.otp_required_for_login - .panel.panel-success - .panel-heading - Two-factor Authentication enabled - .panel-body + .panel.panel-default + .panel-heading + Two-factor Authentication + .panel-body + - if current_user.otp_required_for_login .pull-right = link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm', data: { confirm: 'Are you sure?' } + %p.text-success + %strong + Two-factor Authentication is enabled %p If you lose your recovery codes you can %strong @@ -47,11 +50,7 @@ = link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' } invalidating all previous codes. - - else - .panel.panel-default - .panel-heading - Two-factor Authentication - .panel-body + - else %p Increase your account's security by enabling two-factor authentication (2FA). %p From 67992b9be6fc19ef4cc06de48995d1ee9617049a Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 25 May 2015 16:51:37 -0400 Subject: [PATCH 155/255] Make namespace API available to all users Closes https://github.com/gitlabhq/gitlabhq/issues/9328 --- CHANGELOG | 1 + app/models/user.rb | 6 ++++ doc/api/README.md | 1 + doc/api/namespaces.md | 44 ++++++++++++++++++++++++++++ lib/api/namespaces.rb | 11 +++---- spec/models/user_spec.rb | 2 ++ spec/requests/api/namespaces_spec.rb | 29 +++++++++++++++++- 7 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 doc/api/namespaces.md diff --git a/CHANGELOG b/CHANGELOG index 35724ae602..25455b6de8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,6 +8,7 @@ v 7.12.0 (unreleased) - Add file attachment support in Milestone description (Stan Hu) - Fix milestone "Browse Issues" button. - Set milestone on new issue when creating issue from index with milestone filter active. + - Make namespace API available to all users (Stan Hu) - Add web hook support for note events (Stan Hu) - Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu) - Remove Rack Attack monkey patches and bump to version 4.3.0 (Stan Hu) diff --git a/app/models/user.rb b/app/models/user.rb index 50ca4bc5ac..8058a0dab8 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -655,6 +655,12 @@ class User < ActiveRecord::Base end end + def namespaces + namespace_ids = groups.pluck(:id) + namespace_ids.push(namespace.id) + Namespace.where(id: namespace_ids) + end + def oauth_authorized_tokens Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil) end diff --git a/doc/api/README.md b/doc/api/README.md index f6757b0a6a..ca58c18454 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -19,6 +19,7 @@ - [Deploy Keys](deploy_keys.md) - [System Hooks](system_hooks.md) - [Groups](groups.md) +- [Namespaces](namespaces.md) ## Clients diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md new file mode 100644 index 0000000000..7b3238441f --- /dev/null +++ b/doc/api/namespaces.md @@ -0,0 +1,44 @@ +# Namespaces + +## List namespaces + +Get a list of namespaces. (As user: my namespaces, as admin: all namespaces) + +``` +GET /namespaces +``` + +```json +[ + { + "id": 1, + "path": "user1", + "kind": "user" + }, + { + "id": 2, + "path": "group1", + "kind": "group" + } +] +``` + +You can search for namespaces by name or path, see below. + +## Search for namespace + +Get all namespaces that match your string in their name or path. + +``` +GET /namespaces?search=foobar +``` + +```json +[ + { + "id": 1, + "path": "user1", + "kind": "user" + } +] +``` diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb index b90ed6af5f..50d3729449 100644 --- a/lib/api/namespaces.rb +++ b/lib/api/namespaces.rb @@ -1,10 +1,7 @@ module API # namespaces API class Namespaces < Grape::API - before do - authenticate! - authenticated_as_admin! - end + before { authenticate! } resource :namespaces do # Get a namespaces list @@ -12,7 +9,11 @@ module API # Example Request: # GET /namespaces get do - @namespaces = Namespace.all + @namespaces = if current_user.admin + Namespace.all + else + current_user.namespaces + end @namespaces = @namespaces.search(params[:search]) if params[:search].present? @namespaces = paginate @namespaces diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e1205c18a8..93caa05c07 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -248,6 +248,7 @@ describe User do it { expect(@user.several_namespaces?).to be_truthy } it { expect(@user.authorized_groups).to eq([@group]) } it { expect(@user.owned_groups).to eq([@group]) } + it { expect(@user.namespaces).to match_array([@user.namespace, @group]) } end describe 'group multiple owners' do @@ -270,6 +271,7 @@ describe User do end it { expect(@user.several_namespaces?).to be_falsey } + it { expect(@user.namespaces).to eq([@user.namespace]) } end describe 'blocking user' do diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index 6ddaaa0a6d..21787fdd89 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -3,6 +3,7 @@ require 'spec_helper' describe API::API, api: true do include ApiHelpers let(:admin) { create(:admin) } + let(:user) { create(:user) } let!(:group1) { create(:group) } let!(:group2) { create(:group) } @@ -14,7 +15,7 @@ describe API::API, api: true do end end - context "when authenticated as admin" do + context "when authenticated as admin" do it "admin: should return an array of all namespaces" do get api("/namespaces", admin) expect(response.status).to eq(200) @@ -22,6 +23,32 @@ describe API::API, api: true do expect(json_response.length).to eq(Namespace.count) end + + it "admin: should return an array of matched namespaces" do + get api("/namespaces?search=#{group1.name}", admin) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + + expect(json_response.length).to eq(1) + end + end + + context "when authenticated as a regular user" do + it "user: should return an array of namespaces" do + get api("/namespaces", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + + expect(json_response.length).to eq(1) + end + + it "admin: should return an array of matched namespaces" do + get api("/namespaces?search=#{user.username}", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + + expect(json_response.length).to eq(1) + end end end end From b8977cb432e1c153bf90e378c1a8a325c87441b6 Mon Sep 17 00:00:00 2001 From: Mike Butsko Date: Thu, 28 May 2015 16:09:07 -0400 Subject: [PATCH 156/255] Remove extra brace --- app/views/layouts/notify.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index 00c7cedce4..ee1b57278b 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -27,7 +27,7 @@ } .file-stats .deleted-file { color: #B00; - }} + } %body %div.content = yield From 05aa71ccd965d3c366d50231a6b1b29f05aba373 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 May 2015 16:50:47 -0400 Subject: [PATCH 157/255] Remove jasmine-rails; add teaspoon --- Gemfile | 10 ++++++---- Gemfile.lock | 21 +++++++++++---------- config/initializers/6_rack_profiler.rb | 2 +- config/routes.rb | 1 - spec/javascripts/support/jasmine.yml | 15 --------------- spec/javascripts/support/jasmine_helper.rb | 15 --------------- 6 files changed, 18 insertions(+), 46 deletions(-) delete mode 100644 spec/javascripts/support/jasmine.yml delete mode 100644 spec/javascripts/support/jasmine_helper.rb diff --git a/Gemfile b/Gemfile index 26981f3e0a..82a74616bb 100644 --- a/Gemfile +++ b/Gemfile @@ -253,11 +253,13 @@ group :development, :test do # PhantomJS driver for Capybara gem 'poltergeist', '~> 1.5.1' - gem 'jasmine-rails' + gem 'teaspoon', '~> 1.0.0' + gem 'teaspoon-jasmine' - gem "spring", '~> 1.3.1' - gem "spring-commands-rspec", '1.0.4' - gem "spring-commands-spinach", '1.0.0' + gem 'spring', '~> 1.3.1' + gem 'spring-commands-rspec', '~> 1.0.0' + gem 'spring-commands-spinach', '~> 1.0.0' + gem 'spring-commands-teaspoon', '~> 0.0.2' gem "byebug" end diff --git a/Gemfile.lock b/Gemfile.lock index 7dbc3b4ffa..d14940bbf0 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -300,12 +300,6 @@ GEM i18n (0.7.0) ice_cube (0.11.1) ice_nine (0.10.0) - jasmine-core (2.2.0) - jasmine-rails (0.10.8) - jasmine-core (>= 1.3, < 3.0) - phantomjs (>= 1.9) - railties (>= 3.2.0) - sprockets-rails jquery-atwho-rails (1.0.1) jquery-rails (3.1.0) railties (>= 3.0, < 5.0) @@ -400,7 +394,6 @@ GEM parser (2.2.0.2) ast (>= 1.1, < 3.0) pg (0.15.1) - phantomjs (1.9.8.0) poltergeist (1.5.1) capybara (~> 2.1) cliver (~> 0.3.1) @@ -594,6 +587,8 @@ GEM spring (>= 0.9.1) spring-commands-spinach (1.0.0) spring (>= 0.9.1) + spring-commands-teaspoon (0.0.2) + spring (>= 0.9.1) sprockets (2.11.0) hike (~> 1.2) multi_json (~> 1.0) @@ -608,6 +603,10 @@ GEM stringex (2.5.2) task_list (1.0.2) html-pipeline + teaspoon (1.0.2) + railties (>= 3.2.5, < 5) + teaspoon-jasmine (2.2.0) + teaspoon (>= 1.0.0) temple (0.6.7) term-ansicolor (1.2.2) tins (~> 0.8) @@ -737,7 +736,6 @@ DEPENDENCIES hipchat (~> 1.5.0) html-pipeline (~> 1.11.0) httparty - jasmine-rails jquery-atwho-rails (~> 1.0.0) jquery-rails jquery-scrollto-rails @@ -798,11 +796,14 @@ DEPENDENCIES slim spinach-rails spring (~> 1.3.1) - spring-commands-rspec (= 1.0.4) - spring-commands-spinach (= 1.0.0) + spring-commands-rspec (~> 1.0.0) + spring-commands-spinach (~> 1.0.0) + spring-commands-teaspoon (~> 0.0.2) stamp state_machine task_list (= 1.0.2) + teaspoon (~> 1.0.0) + teaspoon-jasmine test_after_commit thin tinder (~> 1.9.2) diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb index 38a5fa98dc..59934e210f 100644 --- a/config/initializers/6_rack_profiler.rb +++ b/config/initializers/6_rack_profiler.rb @@ -5,5 +5,5 @@ if Rails.env.development? Rack::MiniProfilerRails.initialize!(Rails.application) Rack::MiniProfiler.config.position = 'right' Rack::MiniProfiler.config.start_hidden = true - Rack::MiniProfiler.config.skip_paths << '/specs' + Rack::MiniProfiler.config.skip_paths << %w(/specs /teaspoon) end diff --git a/config/routes.rb b/config/routes.rb index bf2cb6421c..567691a643 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,7 +2,6 @@ require 'sidekiq/web' require 'api/api' Gitlab::Application.routes.draw do - mount JasmineRails::Engine => '/specs' if defined?(JasmineRails) use_doorkeeper do controllers applications: 'oauth/applications', authorized_applications: 'oauth/authorized_applications', diff --git a/spec/javascripts/support/jasmine.yml b/spec/javascripts/support/jasmine.yml deleted file mode 100644 index 168c961864..0000000000 --- a/spec/javascripts/support/jasmine.yml +++ /dev/null @@ -1,15 +0,0 @@ -# path to parent directory of spec_files -# relative path from Rails.root -# -# Alternatively accept an array of directory to include external spec files -# spec_dir: -# - spec/javascripts -# - ../engine/spec/javascripts -# -# defaults to spec/javascripts -spec_dir: spec/javascripts - -# list of file expressions to include as specs into spec runner -# relative path from spec_dir -spec_files: - - "**/*[Ss]pec.{js.coffee,js,coffee}" diff --git a/spec/javascripts/support/jasmine_helper.rb b/spec/javascripts/support/jasmine_helper.rb deleted file mode 100644 index 4d73aec5a3..0000000000 --- a/spec/javascripts/support/jasmine_helper.rb +++ /dev/null @@ -1,15 +0,0 @@ -#Use this file to set/override Jasmine configuration options -#You can remove it if you don't need it. -#This file is loaded *after* jasmine.yml is interpreted. -# -#Example: using a different boot file. -#Jasmine.configure do |config| -# config.boot_dir = '/absolute/path/to/boot_dir' -# config.boot_files = lambda { ['/absolute/path/to/boot_dir/file.js'] } -#end -# -#Example: prevent PhantomJS auto install, uses PhantomJS already on your path. -#Jasmine.configure do |config| -# config.prevent_phantom_js_auto_install = true -#end -# From d850a57ff49e8781f2c72c5573c245bdf214db00 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 May 2015 16:53:11 -0400 Subject: [PATCH 158/255] teaspoon install --- spec/javascripts/spec_helper.coffee | 46 +++++++ spec/teaspoon_env.rb | 178 ++++++++++++++++++++++++++++ 2 files changed, 224 insertions(+) create mode 100644 spec/javascripts/spec_helper.coffee create mode 100644 spec/teaspoon_env.rb diff --git a/spec/javascripts/spec_helper.coffee b/spec/javascripts/spec_helper.coffee new file mode 100644 index 0000000000..892a539d96 --- /dev/null +++ b/spec/javascripts/spec_helper.coffee @@ -0,0 +1,46 @@ +# Teaspoon includes some support files, but you can use anything from your own +# support path too. + +# require support/jasmine-jquery-1.7.0 +# require support/jasmine-jquery-2.0.0 +# require support/jasmine-jquery-2.1.0 +# require support/sinon +# require support/your-support-file + +# PhantomJS (Teaspoons default driver) doesn't have support for +# Function.prototype.bind, which has caused confusion. Use this polyfill to +# avoid the confusion. + +#= require support/bind-poly + +# You can require your own javascript files here. By default this will include +# everything in application, however you may get better load performance if you +# require the specific files that are being used in the spec that tests them. + +#= require jquery +#= require bootstrap +#= require underscore + +# Deferring execution + +# If you're using CommonJS, RequireJS or some other asynchronous library you can +# defer execution. Call Teaspoon.execute() after everything has been loaded. +# Simple example of a timeout: + +# Teaspoon.defer = true +# setTimeout(Teaspoon.execute, 1000) + +# Matching files + +# By default Teaspoon will look for files that match +# _spec.{js,js.coffee,.coffee}. Add a filename_spec.js file in your spec path +# and it'll be included in the default suite automatically. If you want to +# customize suites, check out the configuration in teaspoon_env.rb + +# Manifest + +# If you'd rather require your spec files manually (to control order for +# instance) you can disable the suite matcher in the configuration and use this +# file as a manifest. + +# For more information: http://github.com/modeset/teaspoon diff --git a/spec/teaspoon_env.rb b/spec/teaspoon_env.rb new file mode 100644 index 0000000000..58f45ff861 --- /dev/null +++ b/spec/teaspoon_env.rb @@ -0,0 +1,178 @@ +Teaspoon.configure do |config| + # Determines where the Teaspoon routes will be mounted. Changing this to "/jasmine" would allow you to browse to + # `http://localhost:3000/jasmine` to run your tests. + config.mount_at = "/teaspoon" + + # Specifies the root where Teaspoon will look for files. If you're testing an engine using a dummy application it can + # be useful to set this to your engines root (e.g. `Teaspoon::Engine.root`). + # Note: Defaults to `Rails.root` if nil. + config.root = nil + + # Paths that will be appended to the Rails assets paths + # Note: Relative to `config.root`. + config.asset_paths = ["spec/javascripts", "spec/javascripts/stylesheets"] + + # Fixtures are rendered through a controller, which allows using HAML, RABL/JBuilder, etc. Files in these paths will + # be rendered as fixtures. + config.fixture_paths = ["spec/javascripts/fixtures"] + + # SUITES + # + # You can modify the default suite configuration and create new suites here. Suites are isolated from one another. + # + # When defining a suite you can provide a name and a block. If the name is left blank, :default is assumed. You can + # omit various directives and the ones defined in the default suite will be used. + # + # To run a specific suite + # - in the browser: http://localhost/teaspoon/[suite_name] + # - with the rake task: rake teaspoon suite=[suite_name] + # - with the cli: teaspoon --suite=[suite_name] + config.suite do |suite| + # Specify the framework you would like to use. This allows you to select versions, and will do some basic setup for + # you -- which you can override with the directives below. This should be specified first, as it can override other + # directives. + # Note: If no version is specified, the latest is assumed. + # + # Versions: 1.3.1, 2.0.3, 2.1.3, 2.2.0 + suite.use_framework :jasmine, "2.2.0" + + # Specify a file matcher as a regular expression and all matching files will be loaded when the suite is run. These + # files need to be within an asset path. You can add asset paths using the `config.asset_paths`. + suite.matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.coffee,coffee}" + + # Load additional JS files, but requiring them in your spec helper is the preferred way to do this. + #suite.javascripts = [] + + # You can include your own stylesheets if you want to change how Teaspoon looks. + # Note: Spec related CSS can and should be loaded using fixtures. + #suite.stylesheets = ["teaspoon"] + + # This suites spec helper, which can require additional support files. This file is loaded before any of your test + # files are loaded. + suite.helper = "spec_helper" + + # Partial to be rendered in the head tag of the runner. You can use the provided ones or define your own by creating + # a `_boot.html.erb` in your fixtures path, and adjust the config to `"/boot"` for instance. + # + # Available: boot, boot_require_js + suite.boot_partial = "boot" + + # Partial to be rendered in the body tag of the runner. You can define your own to create a custom body structure. + suite.body_partial = "body" + + # Hooks allow you to use `Teaspoon.hook("fixtures")` before, after, or during your spec run. This will make a + # synchronous Ajax request to the server that will call all of the blocks you've defined for that hook name. + #suite.hook :fixtures, &proc{} + + # Determine whether specs loaded into the test harness should be embedded as individual script tags or concatenated + # into a single file. Similar to Rails' asset `debug: true` and `config.assets.debug = true` options. By default, + # Teaspoon expands all assets to provide more valuable stack traces that reference individual source files. + #suite.expand_assets = true + end + + # Example suite. Since we're just filtering to files already within the root test/javascripts, these files will also + # be run in the default suite -- but can be focused into a more specific suite. + #config.suite :targeted do |suite| + # suite.matcher = "spec/javascripts/targeted/*_spec.{js,js.coffee,coffee}" + #end + + # CONSOLE RUNNER SPECIFIC + # + # These configuration directives are applicable only when running via the rake task or command line interface. These + # directives can be overridden using the command line interface arguments or with ENV variables when using the rake + # task. + # + # Command Line Interface: + # teaspoon --driver=phantomjs --server-port=31337 --fail-fast=true --format=junit --suite=my_suite /spec/file_spec.js + # + # Rake: + # teaspoon DRIVER=phantomjs SERVER_PORT=31337 FAIL_FAST=true FORMATTERS=junit suite=my_suite + + # Specify which headless driver to use. Supports PhantomJS and Selenium Webdriver. + # + # Available: :phantomjs, :selenium, :capybara_webkit + # PhantomJS: https://github.com/modeset/teaspoon/wiki/Using-PhantomJS + # Selenium Webdriver: https://github.com/modeset/teaspoon/wiki/Using-Selenium-WebDriver + # Capybara Webkit: https://github.com/modeset/teaspoon/wiki/Using-Capybara-Webkit + #config.driver = :phantomjs + + # Specify additional options for the driver. + # + # PhantomJS: https://github.com/modeset/teaspoon/wiki/Using-PhantomJS + # Selenium Webdriver: https://github.com/modeset/teaspoon/wiki/Using-Selenium-WebDriver + # Capybara Webkit: https://github.com/modeset/teaspoon/wiki/Using-Capybara-Webkit + #config.driver_options = nil + + # Specify the timeout for the driver. Specs are expected to complete within this time frame or the run will be + # considered a failure. This is to avoid issues that can arise where tests stall. + #config.driver_timeout = 180 + + # Specify a server to use with Rack (e.g. thin, mongrel). If nil is provided Rack::Server is used. + #config.server = nil + + # Specify a port to run on a specific port, otherwise Teaspoon will use a random available port. + #config.server_port = nil + + # Timeout for starting the server in seconds. If your server is slow to start you may have to bump this, or you may + # want to lower this if you know it shouldn't take long to start. + #config.server_timeout = 20 + + # Force Teaspoon to fail immediately after a failing suite. Can be useful to make Teaspoon fail early if you have + # several suites, but in environments like CI this may not be desirable. + #config.fail_fast = true + + # Specify the formatters to use when outputting the results. + # Note: Output files can be specified by using `"junit>/path/to/output.xml"`. + # + # Available: :dot, :clean, :documentation, :json, :junit, :pride, :rspec_html, :snowday, :swayze_or_oprah, :tap, :tap_y, :teamcity + #config.formatters = [:dot] + + # Specify if you want color output from the formatters. + #config.color = true + + # Teaspoon pipes all console[log/debug/error] to $stdout. This is useful to catch places where you've forgotten to + # remove them, but in verbose applications this may not be desirable. + #config.suppress_log = false + + # COVERAGE REPORTS / THRESHOLD ASSERTIONS + # + # Coverage reports requires Istanbul (https://github.com/gotwarlost/istanbul) to add instrumentation to your code and + # display coverage statistics. + # + # Coverage configurations are similar to suites. You can define several, and use different ones under different + # conditions. + # + # To run with a specific coverage configuration + # - with the rake task: rake teaspoon USE_COVERAGE=[coverage_name] + # - with the cli: teaspoon --coverage=[coverage_name] + + # Specify that you always want a coverage configuration to be used. Otherwise, specify that you want coverage + # on the CLI. + # Set this to "true" or the name of your coverage config. + #config.use_coverage = nil + + # You can have multiple coverage configs by passing a name to config.coverage. + # e.g. config.coverage :ci do |coverage| + # The default coverage config name is :default. + config.coverage do |coverage| + # Which coverage reports Istanbul should generate. Correlates directly to what Istanbul supports. + # + # Available: text-summary, text, html, lcov, lcovonly, cobertura, teamcity + #coverage.reports = ["text-summary", "html"] + + # The path that the coverage should be written to - when there's an artifact to write to disk. + # Note: Relative to `config.root`. + #coverage.output_path = "coverage" + + # Assets to be ignored when generating coverage reports. Accepts an array of filenames or regular expressions. The + # default excludes assets from vendor, gems and support libraries. + #coverage.ignore = [%r{/lib/ruby/gems/}, %r{/vendor/assets/}, %r{/support/}, %r{/(.+)_helper.}] + + # Various thresholds requirements can be defined, and those thresholds will be checked at the end of a run. If any + # aren't met the run will fail with a message. Thresholds can be defined as a percentage (0-100), or nil. + #coverage.statements = nil + #coverage.functions = nil + #coverage.branches = nil + #coverage.lines = nil + end +end From 73440b0364dbb49a64a49644e2b5487d203d88cc Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 May 2015 16:53:35 -0400 Subject: [PATCH 159/255] Javascripts don't need to require jquery or bootstrap explicitly --- app/assets/javascripts/issue.js.coffee | 1 - app/assets/javascripts/merge_request.js.coffee | 3 +-- app/assets/javascripts/notes.js.coffee | 2 -- app/assets/javascripts/shortcuts_issuable.coffee | 2 -- app/assets/javascripts/stat_graph_contributors.js.coffee | 1 - 5 files changed, 1 insertion(+), 8 deletions(-) diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 86ad3d03ba..74d6b80be5 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -1,4 +1,3 @@ -#= require jquery #= require jquery.waitforimages #= require task_list diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 3937c428e2..e95274fc5e 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -1,5 +1,4 @@ -#= require jquery -#= require bootstrap +#= require jquery.waitforimages #= require task_list class @MergeRequest diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index f186fec2a0..b9bd5c730b 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -1,6 +1,4 @@ -#= require jquery #= require autosave -#= require bootstrap #= require dropzone #= require dropzone_input #= require gfm_auto_complete diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee index 6b534f2921..bb53219468 100644 --- a/app/assets/javascripts/shortcuts_issuable.coffee +++ b/app/assets/javascripts/shortcuts_issuable.coffee @@ -1,6 +1,4 @@ -#= require jquery #= require mousetrap - #= require shortcuts_navigation class @ShortcutsIssuable extends ShortcutsNavigation diff --git a/app/assets/javascripts/stat_graph_contributors.js.coffee b/app/assets/javascripts/stat_graph_contributors.js.coffee index ed12bdcef2..3be14cb43d 100644 --- a/app/assets/javascripts/stat_graph_contributors.js.coffee +++ b/app/assets/javascripts/stat_graph_contributors.js.coffee @@ -1,5 +1,4 @@ #= require d3 -#= require jquery #= require stat_graph_contributors_util class @ContributorsStatGraph From c9788bd9d8f6e7b7ae445b61b06877411ee01c9e Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 May 2015 16:54:34 -0400 Subject: [PATCH 160/255] Remove jasmine-fixture, use teaspoon fixtures --- spec/javascripts/fixtures/issuable.html.haml | 2 + .../javascripts/fixtures/issue_note.html.haml | 12 + .../fixtures/issues_show.html.haml | 13 + .../fixtures/merge_requests_show.html.haml | 13 + spec/javascripts/issue_spec.js.coffee | 27 +- spec/javascripts/merge_request_spec.js.coffee | 29 +- spec/javascripts/notes_spec.js.coffee | 19 +- .../shortcuts_issuable_spec.js.coffee | 7 +- vendor/assets/javascripts/jasmine-fixture.js | 433 ------------------ 9 files changed, 64 insertions(+), 491 deletions(-) create mode 100644 spec/javascripts/fixtures/issuable.html.haml create mode 100644 spec/javascripts/fixtures/issue_note.html.haml create mode 100644 spec/javascripts/fixtures/issues_show.html.haml create mode 100644 spec/javascripts/fixtures/merge_requests_show.html.haml delete mode 100755 vendor/assets/javascripts/jasmine-fixture.js diff --git a/spec/javascripts/fixtures/issuable.html.haml b/spec/javascripts/fixtures/issuable.html.haml new file mode 100644 index 0000000000..42ab4aa68b --- /dev/null +++ b/spec/javascripts/fixtures/issuable.html.haml @@ -0,0 +1,2 @@ +%form.js-main-target-form + %textarea#note_note diff --git a/spec/javascripts/fixtures/issue_note.html.haml b/spec/javascripts/fixtures/issue_note.html.haml new file mode 100644 index 0000000000..0aecc7334f --- /dev/null +++ b/spec/javascripts/fixtures/issue_note.html.haml @@ -0,0 +1,12 @@ +%ul + %li.note + .js-task-list-container + .note-text + %ul.task-list + %li.task-list-item + %input.task-list-item-checkbox{type: 'checkbox'} + Task List Item + .note-edit-form + %form + %textarea.js-task-list-field + \- [ ] Task List Item diff --git a/spec/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml new file mode 100644 index 0000000000..db5abe0cae --- /dev/null +++ b/spec/javascripts/fixtures/issues_show.html.haml @@ -0,0 +1,13 @@ +%a.btn-close + +.issue-details + .description.js-task-list-container + .wiki + %ul.task-list + %li.task-list-item + %input.task-list-item-checkbox{type: 'checkbox'} + Task List Item + %textarea.js-task-list-field + \- [ ] Task List Item + +%form.js-issue-update{action: '/foo'} diff --git a/spec/javascripts/fixtures/merge_requests_show.html.haml b/spec/javascripts/fixtures/merge_requests_show.html.haml new file mode 100644 index 0000000000..c4329b8f94 --- /dev/null +++ b/spec/javascripts/fixtures/merge_requests_show.html.haml @@ -0,0 +1,13 @@ +%a.btn-close + +.merge-request-details + .description.js-task-list-container + .wiki + %ul.task-list + %li.task-list-item + %input.task-list-item-checkbox{type: 'checkbox'} + Task List Item + %textarea.js-task-list-field + \- [ ] Task List Item + +%form.js-merge-request-update{action: '/foo'} diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee index 13b25862f5..abe0754b65 100644 --- a/spec/javascripts/issue_spec.js.coffee +++ b/spec/javascripts/issue_spec.js.coffee @@ -1,32 +1,17 @@ -#= require jquery -#= require jasmine-fixture #= require issue describe 'Issue', -> describe 'task lists', -> - selectors = { - container: '.issue-details .description.js-task-list-container' - item: '.wiki ul.task-list li.task-list-item input.task-list-item-checkbox[type=checkbox] {Task List Item}' - textarea: '.wiki textarea.js-task-list-field{- [ ] Task List Item}' - form: 'form.js-issue-update[action="/foo"]' - close: 'a.btn-close' - } + fixture.preload('issues_show.html') beforeEach -> - $container = affix(selectors.container) - - # # These two elements are siblings inside the container - $container.find('.js-task-list-container').append(affix(selectors.item)) - $container.find('.js-task-list-container').append(affix(selectors.textarea)) - - # Task lists don't get initialized unless this button exists. Not ideal. - $container.append(affix(selectors.close)) - - # This form is used to get the `update` URL. Not ideal. - $container.append(affix(selectors.form)) - + fixture.load('issues_show.html') @issue = new Issue() + it 'modifies the Markdown field', -> + $('input[type=checkbox]').attr('checked', true).trigger('change') + expect($('.js-task-list-field').val()).toBe('- [x] Task List Item') + it 'submits an ajax request on tasklist:changed', -> spyOn($, 'ajax').and.callFake (req) -> expect(req.type).toBe('PATCH') diff --git a/spec/javascripts/merge_request_spec.js.coffee b/spec/javascripts/merge_request_spec.js.coffee index 3ebc4a4eed..8b8f77c56c 100644 --- a/spec/javascripts/merge_request_spec.js.coffee +++ b/spec/javascripts/merge_request_spec.js.coffee @@ -1,32 +1,19 @@ -#= require jquery -#= require jasmine-fixture #= require merge_request +window.disableButtonIfEmptyField = -> null + describe 'MergeRequest', -> describe 'task lists', -> - selectors = { - container: '.merge-request-details .description.js-task-list-container' - item: '.wiki ul.task-list li.task-list-item input.task-list-item-checkbox[type=checkbox] {Task List Item}' - textarea: '.wiki textarea.js-task-list-field{- [ ] Task List Item}' - form: 'form.js-merge-request-update[action="/foo"]' - close: 'a.btn-close' - } + fixture.preload('merge_requests_show.html') beforeEach -> - $container = affix(selectors.container) - - # # These two elements are siblings inside the container - $container.find('.js-task-list-container').append(affix(selectors.item)) - $container.find('.js-task-list-container').append(affix(selectors.textarea)) - - # Task lists don't get initialized unless this button exists. Not ideal. - $container.append(affix(selectors.close)) - - # This form is used to get the `update` URL. Not ideal. - $container.append(affix(selectors.form)) - + fixture.load('merge_requests_show.html') @merge = new MergeRequest({}) + it 'modifies the Markdown field', -> + $('input[type=checkbox]').attr('checked', true).trigger('change') + expect($('.js-task-list-field').val()).toBe('- [x] Task List Item') + it 'submits an ajax request on tasklist:changed', -> spyOn($, 'ajax').and.callFake (req) -> expect(req.type).toBe('PATCH') diff --git a/spec/javascripts/notes_spec.js.coffee b/spec/javascripts/notes_spec.js.coffee index de2e8e7f6c..050b6e362c 100644 --- a/spec/javascripts/notes_spec.js.coffee +++ b/spec/javascripts/notes_spec.js.coffee @@ -1,5 +1,3 @@ -#= require jquery -#= require jasmine-fixture #= require notes window.gon = {} @@ -7,21 +5,18 @@ window.disableButtonIfEmptyField = -> null describe 'Notes', -> describe 'task lists', -> - selectors = { - container: 'li.note .js-task-list-container' - item: '.note-text ul.task-list li.task-list-item input.task-list-item-checkbox[type=checkbox] {Task List Item}' - textarea: '.note-edit-form form textarea.js-task-list-field{- [ ] Task List Item}' - } + fixture.preload('issue_note.html') beforeEach -> - $container = affix(selectors.container) - - # These two elements are siblings inside the container - $container.find('.js-task-list-container').append(affix(selectors.item)) - $container.find('.js-task-list-container').append(affix(selectors.textarea)) + fixture.load('issue_note.html') + $('form').on 'submit', (e) -> e.preventDefault() @notes = new Notes() + it 'modifies the Markdown field', -> + $('input[type=checkbox]').attr('checked', true).trigger('change') + expect($('.js-task-list-field').val()).toBe('- [x] Task List Item') + it 'submits the form on tasklist:changed', -> submitted = false $('form').on 'submit', (e) -> submitted = true; e.preventDefault() diff --git a/spec/javascripts/shortcuts_issuable_spec.js.coffee b/spec/javascripts/shortcuts_issuable_spec.js.coffee index 57dcc2161d..a01ad7140d 100644 --- a/spec/javascripts/shortcuts_issuable_spec.js.coffee +++ b/spec/javascripts/shortcuts_issuable_spec.js.coffee @@ -1,10 +1,10 @@ -#= require jquery -#= require jasmine-fixture - #= require shortcuts_issuable describe 'ShortcutsIssuable', -> + fixture.preload('issuable.html') + beforeEach -> + fixture.load('issuable.html') @shortcut = new ShortcutsIssuable() describe '#replyWithSelectedText', -> @@ -14,7 +14,6 @@ describe 'ShortcutsIssuable', -> beforeEach -> @selector = 'form.js-main-target-form textarea#note_note' - affix(@selector) describe 'with empty selection', -> it 'does nothing', -> diff --git a/vendor/assets/javascripts/jasmine-fixture.js b/vendor/assets/javascripts/jasmine-fixture.js deleted file mode 100755 index 9980aec6dd..0000000000 --- a/vendor/assets/javascripts/jasmine-fixture.js +++ /dev/null @@ -1,433 +0,0 @@ -/* jasmine-fixture - 1.3.1 - * Makes injecting HTML snippets into the DOM easy & clean! - * https://github.com/searls/jasmine-fixture - */ -(function() { - var createHTMLBlock, - __slice = [].slice; - - (function($) { - var ewwSideEffects, jasmineFixture, originalAffix, originalJasmineDotFixture, originalJasmineFixture, root, _, _ref; - root = (1, eval)('this'); - originalJasmineFixture = root.jasmineFixture; - originalJasmineDotFixture = (_ref = root.jasmine) != null ? _ref.fixture : void 0; - originalAffix = root.affix; - _ = function(list) { - return { - inject: function(iterator, memo) { - var item, _i, _len, _results; - _results = []; - for (_i = 0, _len = list.length; _i < _len; _i++) { - item = list[_i]; - _results.push(memo = iterator(memo, item)); - } - return _results; - } - }; - }; - root.jasmineFixture = function($) { - var $whatsTheRootOf, affix, create, jasmineFixture, noConflict; - affix = function(selectorOptions) { - return create.call(this, selectorOptions, true); - }; - create = function(selectorOptions, attach) { - var $top; - $top = null; - _(selectorOptions.split(/[ ](?![^\{]*\})(?=[^\]]*?(?:\[|$))/)).inject(function($parent, elementSelector) { - var $el; - if (elementSelector === ">") { - return $parent; - } - $el = createHTMLBlock($, elementSelector); - if (attach || $top) { - $el.appendTo($parent); - } - $top || ($top = $el); - return $el; - }, $whatsTheRootOf(this)); - return $top; - }; - noConflict = function() { - var currentJasmineFixture, _ref1; - currentJasmineFixture = jasmine.fixture; - root.jasmineFixture = originalJasmineFixture; - if ((_ref1 = root.jasmine) != null) { - _ref1.fixture = originalJasmineDotFixture; - } - root.affix = originalAffix; - return currentJasmineFixture; - }; - $whatsTheRootOf = function(that) { - if (that.jquery != null) { - return that; - } else if ($('#jasmine_content').length > 0) { - return $('#jasmine_content'); - } else { - return $('
').appendTo('body'); - } - }; - jasmineFixture = { - affix: affix, - create: create, - noConflict: noConflict - }; - ewwSideEffects(jasmineFixture); - return jasmineFixture; - }; - ewwSideEffects = function(jasmineFixture) { - var _ref1; - if ((_ref1 = root.jasmine) != null) { - _ref1.fixture = jasmineFixture; - } - $.fn.affix = root.affix = jasmineFixture.affix; - return afterEach(function() { - return $('#jasmine_content').remove(); - }); - }; - if ($) { - return jasmineFixture = root.jasmineFixture($); - } else { - return root.affix = function() { - var nowJQueryExists; - nowJQueryExists = window.jQuery || window.$; - if (nowJQueryExists != null) { - jasmineFixture = root.jasmineFixture(nowJQueryExists); - return affix.call.apply(affix, [this].concat(__slice.call(arguments))); - } else { - throw new Error("jasmine-fixture requires jQuery to be defined at window.jQuery or window.$"); - } - }; - } - })(window.jQuery || window.$); - - createHTMLBlock = (function() { - var bindData, bindEvents, parseAttributes, parseClasses, parseContents, parseEnclosure, parseReferences, parseVariableScope, regAttr, regAttrDfn, regAttrs, regCBrace, regClass, regClasses, regData, regDatas, regEvent, regEvents, regExclamation, regId, regReference, regTag, regTagNotContent, regZenTagDfn; - createHTMLBlock = function($, ZenObject, data, functions, indexes) { - var ZenCode, arr, block, blockAttrs, blockClasses, blockHTML, blockId, blockTag, blocks, el, el2, els, forScope, indexName, inner, len, obj, origZenCode, paren, result, ret, zc, zo; - if ($.isPlainObject(ZenObject)) { - ZenCode = ZenObject.main; - } else { - ZenCode = ZenObject; - ZenObject = { - main: ZenCode - }; - } - origZenCode = ZenCode; - if (indexes === undefined) { - indexes = {}; - } - if (ZenCode.charAt(0) === "!" || $.isArray(data)) { - if ($.isArray(data)) { - forScope = ZenCode; - } else { - obj = parseEnclosure(ZenCode, "!"); - obj = obj.substring(obj.indexOf(":") + 1, obj.length - 1); - forScope = parseVariableScope(ZenCode); - } - while (forScope.charAt(0) === "@") { - forScope = parseVariableScope("!for:!" + parseReferences(forScope, ZenObject)); - } - zo = ZenObject; - zo.main = forScope; - el = $(); - if (ZenCode.substring(0, 5) === "!for:" || $.isArray(data)) { - if (!$.isArray(data) && obj.indexOf(":") > 0) { - indexName = obj.substring(0, obj.indexOf(":")); - obj = obj.substr(obj.indexOf(":") + 1); - } - arr = ($.isArray(data) ? data : data[obj]); - zc = zo.main; - if ($.isArray(arr) || $.isPlainObject(arr)) { - $.map(arr, function(value, index) { - var next; - zo.main = zc; - if (indexName !== undefined) { - indexes[indexName] = index; - } - if (!$.isPlainObject(value)) { - value = { - value: value - }; - } - next = createHTMLBlock($, zo, value, functions, indexes); - if (el.length !== 0) { - return $.each(next, function(index, value) { - return el.push(value); - }); - } - }); - } - if (!$.isArray(data)) { - ZenCode = ZenCode.substr(obj.length + 6 + forScope.length); - } else { - ZenCode = ""; - } - } else if (ZenCode.substring(0, 4) === "!if:") { - result = parseContents("!" + obj + "!", data, indexes); - if (result !== "undefined" || result !== "false" || result !== "") { - el = createHTMLBlock($, zo, data, functions, indexes); - } - ZenCode = ZenCode.substr(obj.length + 5 + forScope.length); - } - ZenObject.main = ZenCode; - } else if (ZenCode.charAt(0) === "(") { - paren = parseEnclosure(ZenCode, "(", ")"); - inner = paren.substring(1, paren.length - 1); - ZenCode = ZenCode.substr(paren.length); - zo = ZenObject; - zo.main = inner; - el = createHTMLBlock($, zo, data, functions, indexes); - } else { - blocks = ZenCode.match(regZenTagDfn); - block = blocks[0]; - if (block.length === 0) { - return ""; - } - if (block.indexOf("@") >= 0) { - ZenCode = parseReferences(ZenCode, ZenObject); - zo = ZenObject; - zo.main = ZenCode; - return createHTMLBlock($, zo, data, functions, indexes); - } - block = parseContents(block, data, indexes); - blockClasses = parseClasses($, block); - if (regId.test(block)) { - blockId = regId.exec(block)[1]; - } - blockAttrs = parseAttributes(block, data); - blockTag = (block.charAt(0) === "{" ? "span" : "div"); - if (ZenCode.charAt(0) !== "#" && ZenCode.charAt(0) !== "." && ZenCode.charAt(0) !== "{") { - blockTag = regTag.exec(block)[1]; - } - if (block.search(regCBrace) !== -1) { - blockHTML = block.match(regCBrace)[1]; - } - blockAttrs = $.extend(blockAttrs, { - id: blockId, - "class": blockClasses, - html: blockHTML - }); - el = $("<" + blockTag + ">", blockAttrs); - el.attr(blockAttrs); - el = bindEvents(block, el, functions); - el = bindData(block, el, data); - ZenCode = ZenCode.substr(blocks[0].length); - ZenObject.main = ZenCode; - } - if (ZenCode.length > 0) { - if (ZenCode.charAt(0) === ">") { - if (ZenCode.charAt(1) === "(") { - zc = parseEnclosure(ZenCode.substr(1), "(", ")"); - ZenCode = ZenCode.substr(zc.length + 1); - } else if (ZenCode.charAt(1) === "!") { - obj = parseEnclosure(ZenCode.substr(1), "!"); - forScope = parseVariableScope(ZenCode.substr(1)); - zc = obj + forScope; - ZenCode = ZenCode.substr(zc.length + 1); - } else { - len = Math.max(ZenCode.indexOf("+"), ZenCode.length); - zc = ZenCode.substring(1, len); - ZenCode = ZenCode.substr(len); - } - zo = ZenObject; - zo.main = zc; - els = $(createHTMLBlock($, zo, data, functions, indexes)); - els.appendTo(el); - } - if (ZenCode.charAt(0) === "+") { - zo = ZenObject; - zo.main = ZenCode.substr(1); - el2 = createHTMLBlock($, zo, data, functions, indexes); - $.each(el2, function(index, value) { - return el.push(value); - }); - } - } - ret = el; - return ret; - }; - bindData = function(ZenCode, el, data) { - var datas, i, split; - if (ZenCode.search(regDatas) === 0) { - return el; - } - datas = ZenCode.match(regDatas); - if (datas === null) { - return el; - } - i = 0; - while (i < datas.length) { - split = regData.exec(datas[i]); - if (split[3] === undefined) { - $(el).data(split[1], data[split[1]]); - } else { - $(el).data(split[1], data[split[3]]); - } - i++; - } - return el; - }; - bindEvents = function(ZenCode, el, functions) { - var bindings, fn, i, split; - if (ZenCode.search(regEvents) === 0) { - return el; - } - bindings = ZenCode.match(regEvents); - if (bindings === null) { - return el; - } - i = 0; - while (i < bindings.length) { - split = regEvent.exec(bindings[i]); - if (split[2] === undefined) { - fn = functions[split[1]]; - } else { - fn = functions[split[2]]; - } - $(el).bind(split[1], fn); - i++; - } - return el; - }; - parseAttributes = function(ZenBlock, data) { - var attrStrs, attrs, i, parts; - if (ZenBlock.search(regAttrDfn) === -1) { - return undefined; - } - attrStrs = ZenBlock.match(regAttrDfn); - attrs = {}; - i = 0; - while (i < attrStrs.length) { - parts = regAttr.exec(attrStrs[i]); - attrs[parts[1]] = ""; - if (parts[3] !== undefined) { - attrs[parts[1]] = parseContents(parts[3], data); - } - i++; - } - return attrs; - }; - parseClasses = function($, ZenBlock) { - var classes, clsString, i; - ZenBlock = ZenBlock.match(regTagNotContent)[0]; - if (ZenBlock.search(regClasses) === -1) { - return undefined; - } - classes = ZenBlock.match(regClasses); - clsString = ""; - i = 0; - while (i < classes.length) { - clsString += " " + regClass.exec(classes[i])[1]; - i++; - } - return $.trim(clsString); - }; - parseContents = function(ZenBlock, data, indexes) { - var html; - if (indexes === undefined) { - indexes = {}; - } - html = ZenBlock; - if (data === undefined) { - return html; - } - while (regExclamation.test(html)) { - html = html.replace(regExclamation, function(str, str2) { - var begChar, fn, val; - begChar = ""; - if (str.indexOf("!for:") > 0 || str.indexOf("!if:") > 0) { - return str; - } - if (str.charAt(0) !== "!") { - begChar = str.charAt(0); - str = str.substring(2, str.length - 1); - } - fn = new Function("data", "indexes", "var r=undefined;" + "with(data){try{r=" + str + ";}catch(e){}}" + "with(indexes){try{if(r===undefined)r=" + str + ";}catch(e){}}" + "return r;"); - val = unescape(fn(data, indexes)); - return begChar + val; - }); - } - html = html.replace(/\\./g, function(str) { - return str.charAt(1); - }); - return unescape(html); - }; - parseEnclosure = function(ZenCode, open, close, count) { - var index, ret; - if (close === undefined) { - close = open; - } - index = 1; - if (count === undefined) { - count = (ZenCode.charAt(0) === open ? 1 : 0); - } - if (count === 0) { - return; - } - while (count > 0 && index < ZenCode.length) { - if (ZenCode.charAt(index) === close && ZenCode.charAt(index - 1) !== "\\") { - count--; - } else { - if (ZenCode.charAt(index) === open && ZenCode.charAt(index - 1) !== "\\") { - count++; - } - } - index++; - } - ret = ZenCode.substring(0, index); - return ret; - }; - parseReferences = function(ZenCode, ZenObject) { - ZenCode = ZenCode.replace(regReference, function(str) { - var fn; - str = str.substr(1); - fn = new Function("objs", "var r=\"\";" + "with(objs){try{" + "r=" + str + ";" + "}catch(e){}}" + "return r;"); - return fn(ZenObject, parseReferences); - }); - return ZenCode; - }; - parseVariableScope = function(ZenCode) { - var forCode, rest, tag; - if (ZenCode.substring(0, 5) !== "!for:" && ZenCode.substring(0, 4) !== "!if:") { - return undefined; - } - forCode = parseEnclosure(ZenCode, "!"); - ZenCode = ZenCode.substr(forCode.length); - if (ZenCode.charAt(0) === "(") { - return parseEnclosure(ZenCode, "(", ")"); - } - tag = ZenCode.match(regZenTagDfn)[0]; - ZenCode = ZenCode.substr(tag.length); - if (ZenCode.length === 0 || ZenCode.charAt(0) === "+") { - return tag; - } else if (ZenCode.charAt(0) === ">") { - rest = ""; - rest = parseEnclosure(ZenCode.substr(1), "(", ")", 1); - return tag + ">" + rest; - } - return undefined; - }; - regZenTagDfn = /([#\.\@]?[\w-]+|\[([\w-!?=:"']+(="([^"]|\\")+")? {0,})+\]|\~[\w$]+=[\w$]+|&[\w$]+(=[\w$]+)?|[#\.\@]?!([^!]|\\!)+!){0,}(\{([^\}]|\\\})+\})?/i; - regTag = /(\w+)/i; - regId = /(?:^|\b)#([\w-!]+)/i; - regTagNotContent = /((([#\.]?[\w-]+)?(\[([\w!]+(="([^"]|\\")+")? {0,})+\])?)+)/i; - /* - See lookahead syntax (?!) at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp - */ - - regClasses = /(\.[\w-]+)(?!["\w])/g; - regClass = /\.([\w-]+)/i; - regReference = /(@[\w$_][\w$_\d]+)/i; - regAttrDfn = /(\[([\w-!]+(="?([^"]|\\")+"?)? {0,})+\])/ig; - regAttrs = /([\w-!]+(="([^"]|\\")+")?)/g; - regAttr = /([\w-!]+)(="?((([\w]+(\[.*?\])+)|[^"\]]|\\")+)"?)?/i; - regCBrace = /\{(([^\}]|\\\})+)\}/i; - regExclamation = /(?:([^\\]|^))!([^!]|\\!)+!/g; - regEvents = /\~[\w$]+(=[\w$]+)?/g; - regEvent = /\~([\w$]+)=([\w$]+)/i; - regDatas = /&[\w$]+(=[\w$]+)?/g; - regData = /&([\w$]+)(=([\w$]+))?/i; - return createHTMLBlock; - })(); - -}).call(this); From d2256c18f4c4a5f896460be69b05df75303b107f Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 May 2015 17:02:14 -0400 Subject: [PATCH 161/255] Stub ajax in JS specs --- spec/javascripts/issue_spec.js.coffee | 3 ++- spec/javascripts/merge_request_spec.js.coffee | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee index abe0754b65..268e4c68c3 100644 --- a/spec/javascripts/issue_spec.js.coffee +++ b/spec/javascripts/issue_spec.js.coffee @@ -9,11 +9,12 @@ describe 'Issue', -> @issue = new Issue() it 'modifies the Markdown field', -> + spyOn(jQuery, 'ajax').and.stub() $('input[type=checkbox]').attr('checked', true).trigger('change') expect($('.js-task-list-field').val()).toBe('- [x] Task List Item') it 'submits an ajax request on tasklist:changed', -> - spyOn($, 'ajax').and.callFake (req) -> + spyOn(jQuery, 'ajax').and.callFake (req) -> expect(req.type).toBe('PATCH') expect(req.url).toBe('/foo') expect(req.data.issue.description).not.toBe(null) diff --git a/spec/javascripts/merge_request_spec.js.coffee b/spec/javascripts/merge_request_spec.js.coffee index 8b8f77c56c..a4735af034 100644 --- a/spec/javascripts/merge_request_spec.js.coffee +++ b/spec/javascripts/merge_request_spec.js.coffee @@ -11,11 +11,13 @@ describe 'MergeRequest', -> @merge = new MergeRequest({}) it 'modifies the Markdown field', -> + spyOn(jQuery, 'ajax').and.stub() + $('input[type=checkbox]').attr('checked', true).trigger('change') expect($('.js-task-list-field').val()).toBe('- [x] Task List Item') it 'submits an ajax request on tasklist:changed', -> - spyOn($, 'ajax').and.callFake (req) -> + spyOn(jQuery, 'ajax').and.callFake (req) -> expect(req.type).toBe('PATCH') expect(req.url).toBe('/foo') expect(req.data.merge_request.description).not.toBe(null) From 330c25385bb8f54149c6ea7547af9fae6d6c5dd6 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 6 May 2015 17:04:09 -0400 Subject: [PATCH 162/255] Update jasmine:ci task to use teaspoon --- lib/tasks/jasmine.rake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/jasmine.rake b/lib/tasks/jasmine.rake index 9e2cceffa1..ac307a9e92 100644 --- a/lib/tasks/jasmine.rake +++ b/lib/tasks/jasmine.rake @@ -7,6 +7,6 @@ task jasmine: ['jasmine:ci'] namespace :jasmine do task :ci do - Rake::Task['spec:javascript'].invoke + Rake::Task['teaspoon'].invoke end end From bd12ca5eb3c8b6ca9de28cbbb074ff1e29c661f2 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 23 May 2015 00:37:01 -0400 Subject: [PATCH 163/255] Disable Rack::MiniProfiler for /teaspoon path --- config/initializers/6_rack_profiler.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb index 59934e210f..5312fd8e89 100644 --- a/config/initializers/6_rack_profiler.rb +++ b/config/initializers/6_rack_profiler.rb @@ -3,7 +3,8 @@ if Rails.env.development? # initialization is skipped so trigger it Rack::MiniProfilerRails.initialize!(Rails.application) + Rack::MiniProfiler.config.position = 'right' Rack::MiniProfiler.config.start_hidden = true - Rack::MiniProfiler.config.skip_paths << %w(/specs /teaspoon) + Rack::MiniProfiler.config.skip_paths << '/teaspoon' end From cef8ab460402b46002c0a25e90418f6920bb3c18 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 29 May 2015 00:05:14 -0400 Subject: [PATCH 164/255] Bump turbolinks version --- Gemfile | 2 +- Gemfile.lock | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Gemfile b/Gemfile index 26981f3e0a..c51941b1e3 100644 --- a/Gemfile +++ b/Gemfile @@ -186,7 +186,7 @@ gem 'charlock_holmes' gem "sass-rails", '~> 4.0.2' gem "coffee-rails" gem "uglifier" -gem 'turbolinks' +gem 'turbolinks', '~> 2.5.0' gem 'jquery-turbolinks' gem 'select2-rails' diff --git a/Gemfile.lock b/Gemfile.lock index 7dbc3b4ffa..f479ca7ed5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -101,13 +101,13 @@ GEM coderay (1.1.0) coercible (1.0.0) descendants_tracker (~> 0.0.1) - coffee-rails (4.0.1) + coffee-rails (4.1.0) coffee-script (>= 2.2.0) railties (>= 4.0.0, < 5.0) - coffee-script (2.2.0) + coffee-script (2.4.1) coffee-script-source execjs - coffee-script-source (1.6.3) + coffee-script-source (1.9.1.1) colored (1.2) colorize (0.5.8) columnize (0.9.0) @@ -418,7 +418,7 @@ GEM quiet_assets (1.0.2) railties (>= 3.1, < 5.0) racc (1.4.10) - rack (1.5.2) + rack (1.5.3) rack-accept (0.4.5) rack (>= 0.4) rack-attack (4.3.0) @@ -633,7 +633,7 @@ GEM multi_json (~> 1.7) twitter-stream (~> 0.1) tins (0.13.1) - turbolinks (2.0.0) + turbolinks (2.5.3) coffee-rails twitter-stream (0.1.16) eventmachine (>= 0.12.8) @@ -806,7 +806,7 @@ DEPENDENCIES test_after_commit thin tinder (~> 1.9.2) - turbolinks + turbolinks (~> 2.5.0) uglifier underscore-rails (~> 1.4.4) unf From f46b3670680ba9d07a2745299764990c944c48a8 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 29 May 2015 00:09:28 -0400 Subject: [PATCH 165/255] Add MergeRequests#commits action and route /:namespace_id/:project_id/merge_requests/:id/commits(.:format) --- .../projects/merge_requests_controller.rb | 17 +++++++--- config/routes.rb | 1 + spec/routing/project_routing_spec.rb | 32 ++++++++++++------- 3 files changed, 33 insertions(+), 17 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index c7467e9b2f..71d3051ab8 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -2,10 +2,13 @@ require 'gitlab/satellite/satellite' class Projects::MergeRequestsController < Projects::ApplicationController before_action :module_enabled - before_action :merge_request, only: [:edit, :update, :show, :diffs, :automerge, :automerge_check, :ci_status, :toggle_subscription] - before_action :closes_issues, only: [:edit, :update, :show, :diffs] - before_action :validates_merge_request, only: [:show, :diffs] - before_action :define_show_vars, only: [:show, :diffs] + before_action :merge_request, only: [ + :edit, :update, :show, :diffs, :commits, :automerge, :automerge_check, + :ci_status, :toggle_subscription + ] + before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits] + before_action :validates_merge_request, only: [:show, :diffs, :commits] + before_action :define_show_vars, only: [:show, :diffs, :commits] # Allow read any merge_request before_action :authorize_read_merge_request! @@ -27,7 +30,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_requests = @merge_requests.full_search(terms) end end - + @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE) respond_to do |format| @@ -67,6 +70,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController end end + def commits + render 'show' + end + def new params[:merge_request] ||= ActionController::Parameters.new(source_project: @project) @merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute diff --git a/config/routes.rb b/config/routes.rb index bf2cb6421c..3f8f920963 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -450,6 +450,7 @@ Gitlab::Application.routes.draw do resources :merge_requests, constraints: { id: /\d+/ }, except: [:destroy] do member do get :diffs + get :commits post :automerge get :automerge_check get :ci_status diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 042352311d..3a0d9b88d7 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -208,23 +208,31 @@ describe Projects::RefsController, 'routing' do end end -# diffs_project_merge_request GET /:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs -# automerge_project_merge_request POST /:project_id/merge_requests/:id/automerge(.:format) projects/merge_requests#automerge -# automerge_check_project_merge_request GET /:project_id/merge_requests/:id/automerge_check(.:format) projects/merge_requests#automerge_check -# branch_from_project_merge_requests GET /:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from -# branch_to_project_merge_requests GET /:project_id/merge_requests/branch_to(.:format) projects/merge_requests#branch_to -# project_merge_requests GET /:project_id/merge_requests(.:format) projects/merge_requests#index -# POST /:project_id/merge_requests(.:format) projects/merge_requests#create -# new_project_merge_request GET /:project_id/merge_requests/new(.:format) projects/merge_requests#new -# edit_project_merge_request GET /:project_id/merge_requests/:id/edit(.:format) projects/merge_requests#edit -# project_merge_request GET /:project_id/merge_requests/:id(.:format) projects/merge_requests#show -# PUT /:project_id/merge_requests/:id(.:format) projects/merge_requests#update -# DELETE /:project_id/merge_requests/:id(.:format) projects/merge_requests#destroy +# diffs_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs +# commits_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/commits(.:format) projects/merge_requests#commits +# automerge_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/automerge(.:format) projects/merge_requests#automerge +# automerge_check_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/automerge_check(.:format) projects/merge_requests#automerge_check +# ci_status_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/ci_status(.:format) projects/merge_requests#ci_status +# toggle_subscription_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/toggle_subscription(.:format) projects/merge_requests#toggle_subscription +# branch_from_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from +# branch_to_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_to(.:format) projects/merge_requests#branch_to +# update_branches_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/update_branches(.:format) projects/merge_requests#update_branches +# namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#index +# POST /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#create +# new_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/new(.:format) projects/merge_requests#new +# edit_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/edit(.:format) projects/merge_requests#edit +# namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#show +# PATCH /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update +# PUT /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update describe Projects::MergeRequestsController, 'routing' do it 'to #diffs' do expect(get('/gitlab/gitlabhq/merge_requests/1/diffs')).to route_to('projects/merge_requests#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') end + it 'to #commits' do + expect(get('/gitlab/gitlabhq/merge_requests/1/commits')).to route_to('projects/merge_requests#commits', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') + end + it 'to #automerge' do expect(post('/gitlab/gitlabhq/merge_requests/1/automerge')).to route_to( 'projects/merge_requests#automerge', From 3f156ed4823f2e9ecfba3468ea5f58ede5719852 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 29 May 2015 00:27:56 -0400 Subject: [PATCH 166/255] Improve MergeRequest tab-persisting behavior Now uses the path instead of the hash. See discussion in #728. --- .../javascripts/merge_request.js.coffee | 83 ++++++++++++------- .../merge_requests/_new_submit.html.haml | 6 +- .../projects/merge_requests/_show.html.haml | 6 +- 3 files changed, 58 insertions(+), 37 deletions(-) diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 3937c428e2..a5a2caf126 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -26,7 +26,7 @@ class @MergeRequest @commits_loaded = @opts.commits_loaded or false this.bindEvents() - this.activateTabFromHash() + this.activateTabFromPath() this.initMergeWidget() this.$('.show-all-commits').on 'click', => @@ -82,19 +82,16 @@ class @MergeRequest bindEvents: -> this.$('.merge-request-tabs a[data-toggle="tab"]').on 'shown.bs.tab', (e) => $target = $(e.target) - - # Nothing else to be done if we're on the first tab - return if $target.data('action') == 'notes' - - # Persist current tab selection via URL - href = $target.attr('href') - if href.substr(0,1) == '#' - location.replace("#!#{href.substr(1)}") + tab_action = $target.data('action') # Lazy-load diffs - if $target.data('action') == 'diffs' + if tab_action == 'diffs' this.loadDiff() unless @diffs_loaded - $('.diff-header').trigger("sticky_kit:recalc") + $('.diff-header').trigger('sticky_kit:recalc') + + # Skip tab-persisting behavior on MergeRequests#new + unless @opts.action == 'new' + @setCurrentAction(tab_action) this.$('.accept_merge_request').on 'click', -> $('.automerge_widget.can_be_merged').hide() @@ -112,27 +109,51 @@ class @MergeRequest this.$('.remove_source_branch_in_progress').hide() this.$('.remove_source_branch_widget.failed').show() - # Activates a tab section based on the `#!` URL hash + # Activate a tab based on the current URL path # - # If no hash value is present (i.e., on the initial page load), the first tab - # is selected by default. - # - # ... unless the current controller action is `diffs`, in which case that tab - # is selected instead. Fun, right? - # - # Note: We use a `#!` instead of a standard URL hash for two reasons: - # - # 1. Prevents the hash acting like an anchor and scrolling the page. - # 2. Prevents mutating browser history. - activateTabFromHash: -> - # Correct the hash if we came here directly via the `/diffs` path - if location.hash == '' and @opts.action == 'diffs' - location.replace('#!diffs') - - if location.hash == '' + # If the current action is 'show' or 'new' (i.e., initial page load), + # activates the first tab, otherwise activates the tab corresponding to the + # current action (diffs, commits). + activateTabFromPath: -> + if @opts.action == 'show' || @opts.action == 'new' this.$('.merge-request-tabs a[data-toggle="tab"]:first').tab('show') - else if location.hash.substr(0,2) == '#!' - this.$(".merge-request-tabs a[href='##{location.hash.substr(2)}']").tab("show") + else + this.$(".merge-request-tabs a[data-action='#{@opts.action}']").tab('show') + + # Replaces the current Merge Request-specific action in the URL with a new one + # + # If the action is "notes", the URL is reset to the standard + # `MergeRequests#show` route. + # + # Examples: + # + # location.pathname # => "/namespace/project/merge_requests/1" + # setCurrentAction('diffs') + # location.pathname # => "/namespace/project/merge_requests/1/diffs" + # + # location.pathname # => "/namespace/project/merge_requests/1/diffs" + # setCurrentAction('notes') + # location.pathname # => "/namespace/project/merge_requests/1" + # + # location.pathname # => "/namespace/project/merge_requests/1/diffs" + # setCurrentAction('commits') + # location.pathname # => "/namespace/project/merge_requests/1/commits" + setCurrentAction: (action) -> + # Normalize action, just to be safe + action = 'notes' if action == 'show' + + # Remove a trailing '/commits' or '/diffs' + new_state = location.pathname.replace(/\/(commits|diffs)\/?$/, '') + + # Append the new action if we're on a tab other than 'notes' + unless action == 'notes' + new_state += "/#{action}" + + # Replace the current history state with the new one without breaking + # Turbolinks' history. + # + # See https://github.com/rails/turbolinks/issues/363 + history.replaceState {turbolinks: true, url: new_state}, '', new_state showState: (state) -> $('.automerge_widget').hide() @@ -161,7 +182,7 @@ class @MergeRequest loadDiff: (event) -> $.ajax type: 'GET' - url: this.$('.merge-request-tabs .diffs-tab a').data('source') + ".json" + url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + ".json" beforeSend: => this.$('.mr-loading-status .loading').show() complete: => diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index e83b764992..9a2edbf0a8 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -20,12 +20,12 @@ .mr-compare.merge-request %ul.nav.nav-tabs.merge-request-tabs %li.commits-tab - = link_to '#commits', data: {action: 'commits', toggle: 'tab'} do + = link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do = icon('history') Commits %span.badge= @commits.size %li.diffs-tab - = link_to '#diffs', data: {action: 'diffs', toggle: 'tab'} do + = link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do = icon('list-alt') Changes %span.badge= @diffs.size @@ -56,7 +56,7 @@ :javascript var merge_request merge_request = new MergeRequest({ - action: 'diffs', + action: 'new', diffs_loaded: true, commits_loaded: true }); diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 0d894e360e..bf056462b7 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -38,17 +38,17 @@ - if @commits.present? %ul.nav.nav-tabs.merge-request-tabs %li.notes-tab - = link_to '#notes', data: {action: 'notes', toggle: 'tab'} do + = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do = icon('comments') Discussion %span.badge= @merge_request.mr_and_commit_notes.user.count %li.commits-tab - = link_to '#commits', data: {action: 'commits', toggle: 'tab'} do + = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#commits', action: 'commits', toggle: 'tab'} do = icon('history') Commits %span.badge= @commits.size %li.diffs-tab - = link_to '#diffs', data: {source: diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), action: 'diffs', toggle: 'tab'} do + = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do = icon('list-alt') Changes %span.badge= @merge_request.diffs.size From 5733bdb7c7ccd59f63a3500882d22ebdcf822fe1 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 29 May 2015 01:56:30 -0400 Subject: [PATCH 167/255] Fix link_to_gfm with only a reference having the incorrect link Closes #1721 --- app/helpers/gitlab_markdown_helper.rb | 19 ++++++++++++++++--- spec/helpers/gitlab_markdown_helper_spec.rb | 6 ++++++ 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index d89f7b4a28..3c207619ad 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -1,3 +1,5 @@ +require 'nokogiri' + module GitlabMarkdownHelper include Gitlab::Markdown @@ -21,11 +23,22 @@ module GitlabMarkdownHelper gfm_body = gfm(escaped_body, {}, html_options) - gfm_body.gsub!(%r{.*?}m) do |match| - "#{match}#{link_to("", url, html_options)[0..-5]}" # "".length +1 + fragment = Nokogiri::XML::DocumentFragment.parse(gfm_body) + if fragment.children.size == 1 && fragment.children[0].name == 'a' + # Fragment has only one node, and it's a link generated by `gfm`. + # Replace it with our requested link. + text = fragment.children[0].text + fragment.children[0].replace(link_to(text, url, html_options)) + else + # Traverse the fragment's first generation of children looking for pure + # text, wrapping anything found in the requested link + fragment.children.each do |node| + next unless node.text? + node.replace(link_to(node.text, url, html_options)) + end end - link_to(gfm_body.html_safe, url, html_options) + fragment.to_html.html_safe end def markdown(text, options={}) diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index d0b200a9ff..bbb434638c 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -94,6 +94,12 @@ describe GitlabMarkdownHelper do expect(link_to_gfm(actual, commit_path)). to match('<h1>test</h1>') end + + it 'ignores reference links when they are the entire body' do + text = issues[0].to_reference + act = link_to_gfm(text, '/foo') + expect(act).to eq %Q(#{issues[0].to_reference}) + end end describe '#render_wiki_content' do From e11e042d89bbbdc16ec8e73178715bee94016667 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 29 May 2015 11:01:40 +0200 Subject: [PATCH 168/255] Fix diff header with submodule change Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/diffs/_file.html.haml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml index d4b019780f..99ee23a1dd 100644 --- a/app/views/projects/diffs/_file.html.haml +++ b/app/views/projects/diffs/_file.html.haml @@ -10,8 +10,9 @@ - if @commit.parent_ids.present? = view_file_btn(@commit.parent_id, diff_file, project) - elsif diff_file.diff.submodule? - - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path) - = submodule_link(submodule_item, @commit.id, project.repository) + %span + - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path) + = submodule_link(submodule_item, @commit.id, project.repository) - else %span - if diff_file.renamed_file From 7f529ef0f7541c1063388f6fad337a0ad628cc99 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 29 May 2015 05:15:09 -0400 Subject: [PATCH 169/255] Include location.search and location.hash in URL for replaceState --- app/assets/javascripts/merge_request.js.coffee | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index a5a2caf126..b82f4c8b80 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -149,6 +149,9 @@ class @MergeRequest unless action == 'notes' new_state += "/#{action}" + # Ensure parameters and hash come along for the ride + new_state += location.search + location.hash + # Replace the current history state with the new one without breaking # Turbolinks' history. # From d5b1c58e26ec7245c372bd147fed6f497337e63f Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 29 May 2015 11:36:44 +0200 Subject: [PATCH 170/255] Shorten merge request WIP text. --- CHANGELOG | 1 + app/views/projects/_issuable_form.html.haml | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d208812af6..5d26b148ca 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 7.12.0 (unreleased) + - Shorten merge request WIP text. - Refactor permission checks with issues and merge requests project settings (Stan Hu) - Fix Markdown preview not working in Edit Milestone page (Stan Hu) - Fix Zen Mode not closing with ESC key (Stan Hu) diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml index 2292aaaa21..c85da8eff9 100644 --- a/app/views/projects/_issuable_form.html.haml +++ b/app/views/projects/_issuable_form.html.haml @@ -15,11 +15,11 @@ - if issuable.is_a?(MergeRequest) %p.help-block - if issuable.work_in_progress? - This merge request is marked a Work In Progress. - When it's ready, remove the WIP prefix from the title to allow it to be accepted. + Remove the WIP prefix from the title to allow this + Work In Progress merge request to be accepted when it's ready. - else - To prevent this merge request from being accepted before it's ready, - mark it a Work In Progress by starting the title with [WIP] or WIP:. + Start the title with [WIP] or WIP: to prevent a + Work In Progress merge request from being accepted before it's ready. .form-group.issuable-description = f.label :description, 'Description', class: 'control-label' .col-sm-10 From 5e4384ec9bc5e015c6a5427e337d8f5412e91d1e Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 28 May 2015 18:00:37 -0700 Subject: [PATCH 171/255] Support editing target branch of merge request Closes https://github.com/gitlabhq/gitlabhq/issues/7105 See: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/130 --- CHANGELOG | 1 + app/models/merge_request.rb | 1 - app/services/issuable_base_service.rb | 6 ++++++ app/services/merge_requests/update_service.rb | 13 ++++++++++++- app/services/system_note_service.rb | 19 +++++++++++++++++++ app/views/projects/_issuable_form.html.haml | 18 ++++++++++++++++++ features/project/merge_requests.feature | 8 ++++++++ features/steps/dashboard/dashboard.rb | 4 ++-- features/steps/project/merge_requests.rb | 14 ++++++++++++++ .../merge_requests/update_service_spec.rb | 11 ++++++++++- spec/services/system_note_service_spec.rb | 14 ++++++++++++++ 11 files changed, 104 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 35724ae602..faa803c5d8 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 7.12.0 (unreleased) + - Support editing target branch of merge request (Stan Hu) - Refactor permission checks with issues and merge requests project settings (Stan Hu) - Fix Markdown preview not working in Edit Milestone page (Stan Hu) - Fix Zen Mode not closing with ESC key (Stan Hu) diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index 5690c375b9..f1f9f23b12 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -197,7 +197,6 @@ class MergeRequest < ActiveRecord::Base def update_merge_request_diff if source_branch_changed? || target_branch_changed? reload_code - mark_as_unchecked end end diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb index c5769a5ad2..1d99223cfe 100644 --- a/app/services/issuable_base_service.rb +++ b/app/services/issuable_base_service.rb @@ -20,4 +20,10 @@ class IssuableBaseService < BaseService SystemNoteService.change_title( issuable, issuable.project, current_user, old_title) end + + def create_branch_change_note(issuable, branch_type, old_branch, new_branch) + SystemNoteService.change_branch( + issuable, issuable.project, current_user, branch_type, + old_branch, new_branch) + end end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 34fd59d692..34c190bf62 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -5,7 +5,7 @@ require_relative 'close_service' module MergeRequests class UpdateService < MergeRequests::BaseService def execute(merge_request) - # We dont allow change of source/target projects + # We don't allow change of source/target projects # after merge request was created params.except!(:source_project_id) params.except!(:target_project_id) @@ -41,6 +41,12 @@ module MergeRequests ) end + if merge_request.previous_changes.include?('target_branch') + create_branch_change_note(merge_request, 'target', + merge_request.previous_changes['target_branch'].first, + merge_request.target_branch) + end + if merge_request.previous_changes.include?('milestone_id') create_milestone_note(merge_request) end @@ -54,6 +60,11 @@ module MergeRequests create_title_change_note(merge_request, merge_request.previous_changes['title'].first) end + if merge_request.previous_changes.include?('target_branch') || + merge_request.previous_changes.include?('source_branch') + merge_request.mark_as_unchecked + end + merge_request.notice_added_references(merge_request.project, current_user) execute_hooks(merge_request, 'update') end diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 1527ae0486..b6801a9233 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -149,6 +149,25 @@ class SystemNoteService create_note(noteable: noteable, project: project, author: author, note: body) end + # Called when a branch in Noteable is changed + # + # noteable - Noteable object + # project - Project owning noteable + # author - User performing the change + # branch_type - 'source' or 'target' + # old_branch - old branch name + # new_branch - new branch nmae + # + # Example Note text: + # + # "Target branch changed from `Old` to `New`" + # + # Returns the created Note object + def self.change_branch(noteable, project, author, branch_type, old_branch, new_branch) + body = "#{branch_type} branch changed from `#{old_branch}` to `#{new_branch}`".capitalize + create_note(noteable: noteable, project: project, author: author, note: body) + end + # Called when a Mentionable references a Noteable # # noteable - Noteable object being referenced diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml index 2292aaaa21..b1e337c397 100644 --- a/app/views/projects/_issuable_form.html.haml +++ b/app/views/projects/_issuable_form.html.haml @@ -79,6 +79,24 @@ - if can? current_user, :admin_label, issuable.project = link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank +- if issuable.is_a?(MergeRequest) + %hr + - unless @merge_request.persisted? + .form-group + = f.label :source_branch, class: 'control-label' do + %i.fa.fa-code-fork + Source Branch + .col-sm-10 + = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true }) + %p.help-block + = link_to 'Change source branch', mr_change_branches_path(@merge_request) + .form-group + = f.label :target_branch, class: 'control-label' do + %i.fa.fa-code-fork + Target Branch + .col-sm-10 + = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, { class: 'target_branch select2 span2' }) + .form-actions - if !issuable.project.empty_repo? && (guide_url = contribution_guide_url(issuable.project)) && !issuable.persisted? %p diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 7a83190160..eb091c291e 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -207,3 +207,11 @@ Feature: Project Merge Requests Then I should see that I am subscribed When I click button "Unsubscribe" Then I should see that I am unsubscribed + + @javascript + Scenario: I can change the target branch + Given I visit merge request page "Bug NS-04" + And I click link "Edit" for the merge request + When I click the "Target branch" dropdown + And I select a new target branch + Then I should see new target branch changes diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index 8508b2a809..bb1f2f444f 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -23,8 +23,8 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps step 'I see prefilled new Merge Request page' do current_path.should == new_namespace_project_merge_request_path(@project.namespace, @project) find("#merge_request_target_project_id").value.should == @project.id.to_s - find("#merge_request_source_branch").value.should == "fix" - find("#merge_request_target_branch").value.should == "master" + find("input#merge_request_source_branch").value.should == "fix" + find("input#merge_request_target_branch").value.should == "master" end step 'user with name "John Doe" joined project "Shop"' do diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 48bb316e20..4ca7cf5e5f 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -305,6 +305,20 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps fill_in 'issue_search', with: "Fe" end + step 'I click the "Target branch" dropdown' do + first('.target_branch').click + end + + step 'I select a new target branch' do + select "feature", from: "merge_request_target_branch" + click_button 'Save' + end + + step 'I should see new target branch changes' do + page.should have_content 'From fix into feature' + page.should have_content 'Target branch changed from master to feature' + end + def merge_request @merge_request ||= MergeRequest.find_by!(title: "Bug NS-05") end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index 0a0760056c..c75173c145 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -20,7 +20,8 @@ describe MergeRequests::UpdateService do description: 'Also please fix', assignee_id: user2.id, state_event: 'close', - label_ids: [label.id] + label_ids: [label.id], + target_branch: 'target' } end @@ -39,6 +40,7 @@ describe MergeRequests::UpdateService do it { expect(@merge_request).to be_closed } it { expect(@merge_request.labels.count).to eq(1) } it { expect(@merge_request.labels.first.title).to eq('Bug') } + it { expect(@merge_request.target_branch).to eq('target') } it 'should execute hooks with update action' do expect(service).to have_received(:execute_hooks). @@ -77,6 +79,13 @@ describe MergeRequests::UpdateService do expect(note).not_to be_nil expect(note.note).to eq 'Title changed from **Old title** to **New title**' end + + it 'creates system note about branch change' do + note = find_note('Target') + + expect(note).not_to be_nil + expect(note.note).to eq 'Target branch changed from `master` to `target`' + end end end end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index 0dcc94e8bd..700286b585 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -228,6 +228,20 @@ describe SystemNoteService do end end + describe '.change_branch' do + subject { described_class.change_branch(noteable, project, author, 'target', old_branch, new_branch) } + let(:old_branch) { 'old_branch'} + let(:new_branch) { 'new_branch'} + + it_behaves_like 'a system note' + + context 'when target branch name changed' do + it 'sets the note text' do + expect(subject.note).to eq "Target branch changed from `#{old_branch}` to `#{new_branch}`" + end + end + end + describe '.cross_reference' do subject { described_class.cross_reference(noteable, mentioner, author) } From 96d6fdc27cc3721ec76b6542a32ae236d5e78956 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Fri, 29 May 2015 13:29:16 +0200 Subject: [PATCH 172/255] Add option to disallow users from registering any application to use GitLab as an OAuth provider --- CHANGELOG | 1 + .../admin/application_settings_controller.rb | 1 + .../oauth/applications_controller.rb | 8 +++ app/helpers/application_settings_helper.rb | 4 ++ app/models/application_setting.rb | 1 + .../application_settings/_form.html.haml | 9 ++- app/views/profiles/applications.html.haml | 60 ++++++++++--------- ...th_applications_to_application_settings.rb | 5 ++ db/schema.rb | 3 +- 9 files changed, 63 insertions(+), 29 deletions(-) create mode 100644 db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb diff --git a/CHANGELOG b/CHANGELOG index 452fe553b0..f0d03fa00f 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 7.12.0 (unreleased) + - Add option to disallow users from registering any application to use GitLab as an OAuth provider - Refactor permission checks with issues and merge requests project settings (Stan Hu) - Fix Markdown preview not working in Edit Milestone page (Stan Hu) - Fix Zen Mode not closing with ESC key (Stan Hu) diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 4c35622fff..5aaae94e6b 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -43,6 +43,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :default_snippet_visibility, :restricted_signup_domains_raw, :version_check_enabled, + :user_oauth_applications, restricted_visibility_levels: [], ) end diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb index 507b8290a2..fc31118124 100644 --- a/app/controllers/oauth/applications_controller.rb +++ b/app/controllers/oauth/applications_controller.rb @@ -1,6 +1,8 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController + include Gitlab::CurrentSettings include PageLayoutHelper + before_action :verify_user_oauth_applications_enabled before_action :authenticate_user! layout 'profile' @@ -32,6 +34,12 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController private + def verify_user_oauth_applications_enabled + return if current_application_settings.user_oauth_applications? + + redirect_to applications_profile_url + end + def set_application @application = current_user.oauth_applications.find(params[:id]) end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb index 241d6075c9..63c3ff5674 100644 --- a/app/helpers/application_settings_helper.rb +++ b/app/helpers/application_settings_helper.rb @@ -19,6 +19,10 @@ module ApplicationSettingsHelper current_application_settings.sign_in_text end + def user_oauth_applications? + current_application_settings.user_oauth_applications + end + # Return a group of checkboxes that use Bootstrap's button plugin for a # toggle button effect. def restricted_level_checkboxes(help_block_id) diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index d5123249c5..c465158f76 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -18,6 +18,7 @@ # default_project_visibility :integer # default_snippet_visibility :integer # restricted_signup_domains :text +# user_oauth_applications :bool default(TRUE) # class ApplicationSetting < ActiveRecord::Base diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index 4ceae81480..dd8978647c 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -30,7 +30,7 @@ .checkbox = f.label :twitter_sharing_enabled do = f.check_box :twitter_sharing_enabled, :'aria-describedby' => 'twitter_help_block' - %strong Twitter enabled + Twitter enabled %span.help-block#twitter_help_block Show users a button to share their newly created public or internal projects on twitter .form-group .col-sm-offset-2.col-sm-10 @@ -83,6 +83,13 @@ .col-sm-10 = f.text_area :restricted_signup_domains_raw, placeholder: 'domain.com', class: 'form-control' .help-block Only users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com + .form_group + = f.label :user_oauth_applications, 'User OAuth applications', class: 'control-label col-sm-2' + .col-sm-10 + .checkbox + = f.label :user_oauth_applications do + = f.check_box :user_oauth_applications + Allow users to register any application to use GitLab as an OAuth provider .form-actions = f.submit 'Save', class: 'btn btn-primary' diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml index c145a9b7f6..2c4f0804f0 100644 --- a/app/views/profiles/applications.html.haml +++ b/app/views/profiles/applications.html.haml @@ -2,37 +2,43 @@ %h3.page-title = page_title %p.light - OAuth2 protocol settings below. + - if user_oauth_applications? + Manage applications that can use GitLab as an OAuth provider, + and applications that you've authorized to use your account. + - else + Manage applications that you've authorized to use your account. %hr -.oauth-applications - %h3 - Your applications - .pull-right - = link_to 'New Application', new_oauth_application_path, class: 'btn btn-success' - - if @applications.any? - %table.table.table-striped - %thead - %tr - %th Name - %th Callback URL - %th Clients - %th - %th - %tbody - - @applications.each do |application| - %tr{:id => "application_#{application.id}"} - %td= link_to application.name, oauth_application_path(application) - %td - - application.redirect_uri.split.each do |uri| - %div= uri - %td= application.access_tokens.count - %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm' - %td= render 'doorkeeper/applications/delete_form', application: application +- if user_oauth_applications? + .oauth-applications + %h3 + Your applications + .pull-right + = link_to 'New Application', new_oauth_application_path, class: 'btn btn-success' + - if @applications.any? + %table.table.table-striped + %thead + %tr + %th Name + %th Callback URL + %th Clients + %th + %th + %tbody + - @applications.each do |application| + %tr{:id => "application_#{application.id}"} + %td= link_to application.name, oauth_application_path(application) + %td + - application.redirect_uri.split.each do |uri| + %div= uri + %td= application.access_tokens.count + %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm' + %td= render 'doorkeeper/applications/delete_form', application: application .oauth-authorized-applications.prepend-top-20 - %h3 - Authorized applications + - if user_oauth_applications? + %h3 + Authorized applications - if @authorized_tokens.any? %table.table.table-striped diff --git a/db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb b/db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb new file mode 100644 index 0000000000..6a78294f0b --- /dev/null +++ b/db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb @@ -0,0 +1,5 @@ +class AddUserOauthApplicationsToApplicationSettings < ActiveRecord::Migration + def change + add_column :application_settings, :user_oauth_applications, :bool, default: true + end +end diff --git a/db/schema.rb b/db/schema.rb index 1ab9125640..dfd93d056e 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: 20150516060434) do +ActiveRecord::Schema.define(version: 20150529111607) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -33,6 +33,7 @@ ActiveRecord::Schema.define(version: 20150516060434) do t.integer "default_project_visibility" t.integer "default_snippet_visibility" t.text "restricted_signup_domains" + t.boolean "user_oauth_applications", default: true end create_table "broadcast_messages", force: true do |t| From cef746dc94347d65af6d953eb03223d16e3c4019 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 29 May 2015 14:11:37 +0200 Subject: [PATCH 173/255] User should be able to leave group. If not - show him proper message Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + app/controllers/groups/group_members_controller.rb | 6 +++++- app/views/dashboard/groups/index.html.haml | 7 +++---- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index d208812af6..dba9e4a05a 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -29,6 +29,7 @@ v 7.12.0 (unreleased) - Clarify navigation labels for Project Settings and Group Settings. - Move user avatar and logout button to sidebar - You can not remove user if he/she is an only owner of group + - User should be able to leave group. If not - show him proper message v 7.11.4 - Fix missing bullets when creating lists diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb index a11c554a2a..040255f08e 100644 --- a/app/controllers/groups/group_members_controller.rb +++ b/app/controllers/groups/group_members_controller.rb @@ -66,7 +66,11 @@ class Groups::GroupMembersController < Groups::ApplicationController @group_member.destroy redirect_to(dashboard_groups_path, notice: "You left #{group.name} group.") else - return render_403 + if @group.last_owner?(current_user) + redirect_to(dashboard_groups_path, alert: "You can not leave #{group.name} group because you're the last owner. Transfer or delete the group.") + else + return render_403 + end end end diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml index 5ecd53cff8..cfb386e131 100644 --- a/app/views/dashboard/groups/index.html.haml +++ b/app/views/dashboard/groups/index.html.haml @@ -23,10 +23,9 @@ %i.fa.fa-cogs Settings - - if can?(current_user, :destroy_group_member, group_member) - = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Remove user from group' do - %i.fa.fa-sign-out - Leave + = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do + %i.fa.fa-sign-out + Leave = image_tag group_icon(group), class: "avatar s40 avatar-tile" = link_to group, class: 'group-name' do From 85de253ee10aa7821a212270a1940c6205533d38 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 29 May 2015 15:07:04 +0200 Subject: [PATCH 174/255] Fix tests for group leave feature Signed-off-by: Dmitriy Zaporozhets --- features/dashboard/group.feature | 3 ++- features/steps/dashboard/group.rb | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/features/dashboard/group.feature b/features/dashboard/group.feature index cf4b8d7283..e3c01db2eb 100644 --- a/features/dashboard/group.feature +++ b/features/dashboard/group.feature @@ -24,7 +24,8 @@ Feature: Dashboard Group When I visit dashboard groups page Then I should see group "Owned" in group list Then I should see group "Guest" in group list - Then I should not see the "Leave" button for group "Owned" + When I click on the "Leave" button for group "Owned" + Then I should see the "Can not leave message" @javascript Scenario: Guest should be able to leave from group diff --git a/features/steps/dashboard/group.rb b/features/steps/dashboard/group.rb index 8384df2fb5..aeea49320f 100644 --- a/features/steps/dashboard/group.rb +++ b/features/steps/dashboard/group.rb @@ -60,4 +60,8 @@ class Spinach::Features::DashboardGroup < Spinach::FeatureSteps page.should have_content "Samurai" page.should have_content "Tokugawa Shogunate" end + + step 'I should see the "Can not leave message"' do + page.should have_content "You can not leave Owned group because you're the last owner" + end end From d4a58c685f8db01e15a850e6f83642aa5cc83a94 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 29 May 2015 15:14:24 +0200 Subject: [PATCH 175/255] Style header search field on focus Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/generic/header.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss index fe32b024f4..3b0ee264bc 100644 --- a/app/assets/stylesheets/generic/header.scss +++ b/app/assets/stylesheets/generic/header.scss @@ -183,6 +183,13 @@ header { font-size: 13px; background-color: #f5f5f5; border-color: #f5f5f5; + + &:focus { + @include box-shadow(none); + outline: none; + border-color: #DDD; + background-color: #FFF; + } } } } From 2afa5fcb52b6cec89872fe0794cba651f3ef3c86 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 29 May 2015 16:23:36 +0200 Subject: [PATCH 176/255] Add ability to leave project Signed-off-by: Dmitriy Zaporozhets --- .../projects/project_members_controller.rb | 6 +++++- app/helpers/projects_helper.rb | 12 ++++++++++++ app/views/projects/_aside.html.haml | 12 ++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb index d7fbc97906..b110de1101 100644 --- a/app/controllers/projects/project_members_controller.rb +++ b/app/controllers/projects/project_members_controller.rb @@ -73,10 +73,14 @@ class Projects::ProjectMembersController < Projects::ApplicationController end def leave + if @project.namespace == current_user.namespace + return redirect_to(:back, alert: 'You can not leave your own project. Transfer or delete the project.') + end + @project.project_members.find_by(user_id: current_user).destroy respond_to do |format| - format.html { redirect_to :back } + format.html { redirect_to dashboard_path } format.js { render nothing: true } end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index f8df39d236..94ce664663 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -294,4 +294,16 @@ module ProjectsHelper nil end end + + def user_max_access_in_project(user, project) + level = project.team.max_member_access(user) + + if level + Gitlab::Access.options_with_owner.key(level) + end + end + + def leave_project_message(project) + "Are you sure you want to leave \"#{project.name}\" project?" + end end diff --git a/app/views/projects/_aside.html.haml b/app/views/projects/_aside.html.haml index 000a40b466..9c2ff8f840 100644 --- a/app/views/projects/_aside.html.haml +++ b/app/views/projects/_aside.html.haml @@ -94,3 +94,15 @@ = icon("exclamation-triangle fw") Archived project! %p Repository is read-only + + - if current_user + - access = user_max_access_in_project(current_user, @project) + - if access + .light-well.light.prepend-top-20 + %small + You have #{access} access to this project. + - if @project.project_member_by_id(current_user) + %br + = link_to leave_namespace_project_project_members_path(@project.namespace, @project), + data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project' do + Leave this project From 4f0f182244de8e807bbadec6818481e5f7157e26 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 29 May 2015 16:26:08 +0200 Subject: [PATCH 177/255] Add changelog item about leave project feature Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 43788f5c4d..a1a7f44384 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -30,6 +30,7 @@ v 7.12.0 (unreleased) - Clarify navigation labels for Project Settings and Group Settings. - Move user avatar and logout button to sidebar - You can not remove user if he/she is an only owner of group + - User has ability to leave project v 7.11.4 - Fix missing bullets when creating lists From 7815f9ddaceaa379d7ccce1ab565ecb87bcaf845 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 29 May 2015 16:34:26 +0200 Subject: [PATCH 178/255] Make leave buttons more explicit Signed-off-by: Dmitriy Zaporozhets --- app/views/groups/group_members/_group_member.html.haml | 3 ++- app/views/projects/project_members/_project_member.html.haml | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml index 56b1948a47..ec39a755f0 100644 --- a/app/views/groups/group_members/_group_member.html.haml +++ b/app/views/groups/group_members/_group_member.html.haml @@ -40,7 +40,8 @@   - if current_user == user = link_to leave_group_group_members_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from group' do - %i.fa.fa-minus.fa-inverse + = icon("sign-out") + Leave - else = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do %i.fa.fa-minus.fa-inverse diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml index 635e4d7094..860a997cff 100644 --- a/app/views/projects/project_members/_project_member.html.haml +++ b/app/views/projects/project_members/_project_member.html.haml @@ -38,8 +38,9 @@   - if current_user == user - = link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: "Leave project?"}, method: :delete, class: "btn-xs btn btn-remove", title: 'Leave project' do - %i.fa.fa-minus.fa-inverse + = link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: leave_project_message(@project) }, method: :delete, class: "btn-xs btn btn-remove", title: 'Leave project' do + = icon("sign-out") + Leave - else = link_to namespace_project_project_member_path(@project.namespace, @project, member), data: { confirm: remove_from_project_team_message(@project, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from team' do %i.fa.fa-minus.fa-inverse From 467d7f6720c6b85d2b1559bb7263dde0f448402e Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 29 May 2015 18:12:31 +0200 Subject: [PATCH 179/255] Improve UI of project sidebar Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/projects.scss | 9 +++++++-- app/views/layouts/_head_panel.html.haml | 12 ++++++------ app/views/projects/_aside.html.haml | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index ee8c746d2d..12489ccc2d 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -210,8 +210,13 @@ ul.nav.nav-projects-tabs { } .panel { + @include border-radius(3px); + .panel-heading, .panel-footer { - background-color: #fcfcfc; + font-weight: normal; + background-color: transparent; + color: #666; + border-color: #EEE; } .actions { @@ -225,7 +230,7 @@ ul.nav.nav-projects-tabs { } .nav { - margin-bottom: 10px; + margin-bottom: 15px; } } diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index 979755db65..ddf1ffc761 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -20,23 +20,23 @@ = icon('search') %li = link_to help_path, title: 'Help', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('question-circle') + = icon('question-circle fw') %li = link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('globe') + = icon('globe fw') %li = link_to user_snippets_path(current_user), title: 'Your snippets', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('clipboard') + = icon('clipboard fw') - if current_user.is_admin? %li = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('wrench') + = icon('wrench fw') - if current_user.can_create_project? %li = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('plus') + = icon('plus fw') %li = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('cog') + = icon('cog fw') = render 'shared/outdated_browser' diff --git a/app/views/projects/_aside.html.haml b/app/views/projects/_aside.html.haml index 9c2ff8f840..c9c17110d2 100644 --- a/app/views/projects/_aside.html.haml +++ b/app/views/projects/_aside.html.haml @@ -56,7 +56,7 @@ - unless @project.empty_repo? .panel.panel-default .panel-heading - = icon("archive fw") + = icon("folder-o fw") Repository .panel-body %ul.nav.nav-pills From 5491f6fbdeeff35589ef5b6f0aa3264a77e9aa36 Mon Sep 17 00:00:00 2001 From: Alex Lossent Date: Wed, 27 May 2015 17:40:21 +0200 Subject: [PATCH 180/255] Add an option to automatically sign-in with an Omniauth provider without showing the GitLab sign-in page This is useful when integrating with existing SSO environments and we want to use a single Omniauth provider for all user authentication. --- CHANGELOG | 1 + app/controllers/sessions_controller.rb | 16 ++++++++++++++++ config/gitlab.yml.example | 4 ++++ config/initializers/1_settings.rb | 2 ++ config/initializers/7_omniauth.rb | 2 ++ 5 files changed, 25 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 66d23dcfd4..a6c761c3e2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,6 +34,7 @@ v 7.12.0 (unreleased) - You can not remove user if he/she is an only owner of group - User should be able to leave group. If not - show him proper message - User has ability to leave project + - Add an option to automatically sign-in with an Omniauth provider v 7.11.4 - Fix missing bullets when creating lists diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index b89b4c2735..4d976fe663 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -2,6 +2,7 @@ class SessionsController < Devise::SessionsController include AuthenticatesWithTwoFactor prepend_before_action :authenticate_with_two_factor, only: [:create] + before_action :auto_sign_in_with_provider, only: [:new] def new redirect_path = @@ -75,6 +76,21 @@ class SessionsController < Devise::SessionsController end end + def auto_sign_in_with_provider + provider = Gitlab.config.omniauth.auto_sign_in_with_provider + return unless provider.present? + + # Auto sign in with an Omniauth provider only if the standard "you need to sign-in" alert is + # registered or no alert at all. In case of another alert (such as a blocked user), it is safer + # to do nothing to prevent redirection loops with certain Omniauth providers. + return unless flash[:alert].blank? || flash[:alert] == I18n.t('devise.failure.unauthenticated') + + # Prevent alert from popping up on the first page shown after authentication. + flash[:alert] = nil + + redirect_to omniauth_authorize_path(:user, provider.to_sym) + end + def valid_otp_attempt?(user) user.valid_otp?(user_params[:otp_attempt]) || user.invalidate_otp_backup_code!(user_params[:otp_attempt]) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 5acfe54850..c7f22b9388 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -182,6 +182,10 @@ production: &base # Allow login via Twitter, Google, etc. using OmniAuth providers enabled: false + # Uncomment this to automatically sign in with a specific omniauth provider's without + # showing GitLab's sign-in page (default: show the GitLab sign-in page) + # auto_sign_in_with_provider: saml + # CAUTION! # This allows users to login without having a user account first (default: false). # User accounts will be created automatically when authentication was successful. diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 2351ef7b0c..c234bd69e9 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -87,6 +87,8 @@ end Settings['omniauth'] ||= Settingslogic.new({}) Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil? +Settings.omniauth['auto_sign_in_with_provider'] = false if Settings.omniauth['auto_sign_in_with_provider'].nil? + Settings.omniauth['providers'] ||= [] Settings['issues_tracker'] ||= {} diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb index 103aa06ca3..6f1f267bf9 100644 --- a/config/initializers/7_omniauth.rb +++ b/config/initializers/7_omniauth.rb @@ -12,6 +12,8 @@ if Gitlab::LDAP::Config.enabled? end OmniAuth.config.allowed_request_methods = [:post] +#In case of auto sign-in, the GET method is used (users don't get to click on a button) +OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present? OmniAuth.config.before_request_phase do |env| OmniAuth::RequestForgeryProtection.new(env).call end From 60225a067dd69e047088dc73f1227fce071311e3 Mon Sep 17 00:00:00 2001 From: Alex Lossent Date: Fri, 29 May 2015 17:42:27 +0200 Subject: [PATCH 181/255] Allow to configure a URL to show after sign out --- CHANGELOG | 1 + app/controllers/admin/application_settings_controller.rb | 1 + app/controllers/application_controller.rb | 2 +- app/models/application_setting.rb | 5 +++++ app/views/admin/application_settings/_form.html.haml | 5 +++++ ...50354_add_after_sign_out_path_for_application_settings.rb | 5 +++++ db/schema.rb | 3 ++- 7 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb diff --git a/CHANGELOG b/CHANGELOG index 66d23dcfd4..318d359de9 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,6 +34,7 @@ v 7.12.0 (unreleased) - You can not remove user if he/she is an only owner of group - User should be able to leave group. If not - show him proper message - User has ability to leave project + - Allow to configure a URL to show after sign out v 7.11.4 - Fix missing bullets when creating lists diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index 5aaae94e6b..a01e2a907d 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -38,6 +38,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController :twitter_sharing_enabled, :sign_in_text, :home_page_url, + :after_sign_out_path, :max_attachment_size, :default_project_visibility, :default_snippet_visibility, diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index e5da94b232..62d46a5482 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -89,7 +89,7 @@ class ApplicationController < ActionController::Base end def after_sign_out_path_for(resource) - new_user_session_path + current_application_settings.after_sign_out_path || new_user_session_path end def abilities diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb index c465158f76..80463ee884 100644 --- a/app/models/application_setting.rb +++ b/app/models/application_setting.rb @@ -19,6 +19,7 @@ # default_snippet_visibility :integer # restricted_signup_domains :text # user_oauth_applications :bool default(TRUE) +# after_sign_out_path :string(255) # class ApplicationSetting < ActiveRecord::Base @@ -31,6 +32,10 @@ class ApplicationSetting < ActiveRecord::Base format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :home_page_url_column_exist + validates :after_sign_out_path, + allow_blank: true, + format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" } + validates_each :restricted_visibility_levels do |record, attr, value| unless value.nil? value.each do |level| diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml index dd8978647c..188a08940a 100644 --- a/app/views/admin/application_settings/_form.html.haml +++ b/app/views/admin/application_settings/_form.html.haml @@ -69,6 +69,11 @@ .col-sm-10 = f.text_field :home_page_url, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'home_help_block' %span.help-block#home_help_block We will redirect non-logged in users to this page + .form-group + = f.label :after_sign_out_path, class: 'control-label col-sm-2' + .col-sm-10 + = f.text_field :after_sign_out_path, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'after_sign_out_path_help_block' + %span.help-block#after_sign_out_path_help_block We will redirect users to this page after they sign out .form-group = f.label :sign_in_text, class: 'control-label col-sm-2' .col-sm-10 diff --git a/db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb b/db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb new file mode 100644 index 0000000000..83e0810140 --- /dev/null +++ b/db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb @@ -0,0 +1,5 @@ +class AddAfterSignOutPathForApplicationSettings < ActiveRecord::Migration + def change + add_column :application_settings, :after_sign_out_path, :string + end +end \ No newline at end of file diff --git a/db/schema.rb b/db/schema.rb index dfd93d056e..aea0742cf3 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: 20150529111607) do +ActiveRecord::Schema.define(version: 20150529150354) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -34,6 +34,7 @@ ActiveRecord::Schema.define(version: 20150529111607) do t.integer "default_snippet_visibility" t.text "restricted_signup_domains" t.boolean "user_oauth_applications", default: true + t.string "after_sign_out_path" end create_table "broadcast_messages", force: true do |t| From 85145d1d77ed919949d59c83cccecd43789cc781 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 29 May 2015 09:40:35 -0700 Subject: [PATCH 182/255] Disable changing of the source branch in merge request update API --- CHANGELOG | 1 + app/services/merge_requests/update_service.rb | 3 ++- doc/api/merge_requests.md | 4 +--- lib/api/merge_requests.rb | 8 ++++++-- spec/requests/api/merge_requests_spec.rb | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 66d23dcfd4..9d4a15c7df 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 7.12.0 (unreleased) + - Disable changing of the source branch in merge request update API (Stan Hu) - Shorten merge request WIP text. - Add option to disallow users from registering any application to use GitLab as an OAuth provider - Support editing target branch of merge request (Stan Hu) diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 34c190bf62..4f6c6cba9a 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -5,10 +5,11 @@ require_relative 'close_service' module MergeRequests class UpdateService < MergeRequests::BaseService def execute(merge_request) - # We don't allow change of source/target projects + # We don't allow change of source/target projects and source branch # after merge request was created params.except!(:source_project_id) params.except!(:target_project_id) + params.except!(:source_branch) state = params[:state_event] diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index c1d82ad957..7b0873a911 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -221,7 +221,7 @@ If an error occurs, an error number and a message explaining the reason is retur ## Update MR -Updates an existing merge request. You can change branches, title, or even close the MR. +Updates an existing merge request. You can change the target branch, title, or even close the MR. ``` PUT /projects/:id/merge_request/:merge_request_id @@ -231,7 +231,6 @@ Parameters: - `id` (required) - The ID of a project - `merge_request_id` (required) - ID of MR -- `source_branch` - The source branch - `target_branch` - The target branch - `assignee_id` - Assignee user ID - `title` - Title of MR @@ -242,7 +241,6 @@ Parameters: { "id": 1, "target_branch": "master", - "source_branch": "test1", "project_id": 3, "title": "test1", "description": "description1", diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 2216a12a87..d835dce2de 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -137,7 +137,6 @@ module API # Parameters: # id (required) - The ID of a project # merge_request_id (required) - ID of MR - # source_branch - The source branch # target_branch - The target branch # assignee_id - Assignee user ID # title - Title of MR @@ -148,10 +147,15 @@ module API # PUT /projects/:id/merge_request/:merge_request_id # put ":id/merge_request/:merge_request_id" do - attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description] + attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description] merge_request = user_project.merge_requests.find(params[:merge_request_id]) authorize! :modify_merge_request, merge_request + # Ensure source_branch is not specified + if params[:source_branch].present? + render_api_error!('Source branch cannot be changed', 400) + end + # Validate label names in advance if (errors = validate_label_params(params)).any? render_api_error!({ labels: errors }, 400) diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index dcd50f7332..0ed5883914 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -349,10 +349,10 @@ describe API::API, api: true do expect(json_response['description']).to eq('New description') end - it "should return 422 when source_branch and target_branch are renamed the same" do + it "should return 400 when source_branch is specified" do put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), source_branch: "master", target_branch: "master" - expect(response.status).to eq(422) + expect(response.status).to eq(400) end it "should return merge_request with renamed target_branch" do From 9d733d14b89513e9ea2249b84fb2db25f67d7614 Mon Sep 17 00:00:00 2001 From: Alex Lossent Date: Fri, 29 May 2015 21:55:51 +0200 Subject: [PATCH 183/255] Fix misplaced changelog entry for SAML support --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 66d23dcfd4..4adbecfd2c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -34,6 +34,7 @@ v 7.12.0 (unreleased) - You can not remove user if he/she is an only owner of group - User should be able to leave group. If not - show him proper message - User has ability to leave project + - Add SAML support as an omniauth provider v 7.11.4 - Fix missing bullets when creating lists @@ -41,7 +42,6 @@ v 7.11.4 v 7.11.3 - no changes - - Add SAML support as an omniauth provider v 7.11.2 - no changes From 6181160504da5e43e9a15c37bdad96741b53a39c Mon Sep 17 00:00:00 2001 From: Martins Polakovs Date: Sat, 30 May 2015 19:15:10 +0300 Subject: [PATCH 184/255] Update mocking/stubbing syntax to the new RSpec 3 syntax --- spec/lib/gitlab/upgrader_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb index 8baa1662f3..baa4bd0f28 100644 --- a/spec/lib/gitlab/upgrader_spec.rb +++ b/spec/lib/gitlab/upgrader_spec.rb @@ -22,7 +22,7 @@ describe Gitlab::Upgrader do end it 'should get the latest version from tags' do - upgrader.stub(fetch_git_tags: [ + allow(upgrader).to receive(:fetch_git_tags).and_return([ '6f0733310546402c15d3ae6128a95052f6c8ea96 refs/tags/v7.1.1', 'facfec4b242ce151af224e20715d58e628aa5e74 refs/tags/v7.1.1^{}', 'f7068d99c79cf79befbd388030c051bb4b5e86d4 refs/tags/v7.10.4', From 9ea231b5a5e40ed3babdc939030eb098fa0d8787 Mon Sep 17 00:00:00 2001 From: Terrence Benade Date: Sun, 31 May 2015 11:20:39 +0000 Subject: [PATCH 185/255] small typo --- docker/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/README.md b/docker/README.md index 2e533ae9dd..a73ccd0dba 100644 --- a/docker/README.md +++ b/docker/README.md @@ -107,7 +107,7 @@ The directories on data container are: ### Configure GitLab -These container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`. +This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`. To access GitLab configuration, you can start an interactive command line in a new container using the shared data volume container, you will be able to browse the 3 directories and use your favorite text editor: From 584ac316ac960c12b9b67ca48728734dcbf1f5c8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Jun 2015 11:56:09 +0200 Subject: [PATCH 186/255] Improve UI for accept MR widget Signed-off-by: Dmitriy Zaporozhets --- .../stylesheets/pages/merge_requests.scss | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 3165396a94..9c4a7c70e9 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -123,38 +123,31 @@ .mr-state-widget { font-size: 13px; - background: #F9F9F9; + background: #FAFAFA; margin-bottom: 20px; color: #666; - border: 1px solid #EEE; - @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); + border: 1px solid #e5e5e5; + @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05)); + @include border-radius(3px); .ci_widget { padding: 10px 15px; font-size: 15px; - border-bottom: 1px solid #BBB; - color: #777; - background-color: $background-color; + border-bottom: 1px solid #EEE; &.ci-success { color: $gl-success; - border-color: $gl-success; - background-color: #F1FAF1; } &.ci-pending, &.ci-running { color: $gl-warning; - border-color: $gl-warning; - background-color: #FAF5F1; } &.ci-failed, &.ci-canceled, &.ci-error { color: $gl-danger; - border-color: $gl-danger; - background-color: #FAF1F1; } } @@ -162,7 +155,8 @@ padding: 10px 15px; h4 { - font-weight: normal; + font-weight: bold; + margin-top: 5px; } p:last-child { From 38bac5d7aec0589c49b02c44cd132489a37c1a24 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Jun 2015 12:17:55 +0200 Subject: [PATCH 187/255] Show avatars in merge widget and fix mr download button overflow Signed-off-by: Dmitriy Zaporozhets --- .../projects/merge_requests/_show.html.haml | 17 ++++++++--------- .../merge_requests/show/_state_widget.html.haml | 4 ++-- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index bf056462b7..74f8b9950c 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -22,15 +22,14 @@ %span into %strong.label-branch #{@merge_request.target_branch} - if @merge_request.open? - %span.pull-right - .btn-group - %a.btn.dropdown-toggle{ data: {toggle: :dropdown} } - = icon('download') - Download as - %span.caret - %ul.dropdown-menu - %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch) - %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff) + .btn-group.btn-group-sm.pull-right + %a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} } + = icon('download') + Download as + %span.caret + %ul.dropdown-menu + %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch) + %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff) = render "projects/merge_requests/show/how_to_merge" = render "projects/merge_requests/show/state_widget" diff --git a/app/views/projects/merge_requests/show/_state_widget.html.haml b/app/views/projects/merge_requests/show/_state_widget.html.haml index e4c71bfc1b..6396232db2 100644 --- a/app/views/projects/merge_requests/show/_state_widget.html.haml +++ b/app/views/projects/merge_requests/show/_state_widget.html.haml @@ -13,7 +13,7 @@ %h4 Rejected - if @merge_request.closed_event - by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)} + by #{link_to_member(@project, @merge_request.closed_event.author, avatar: true)} #{time_ago_with_tooltip(@merge_request.closed_event.created_at)} %p Changes were not merged into target branch @@ -21,7 +21,7 @@ %h4 Accepted - if @merge_request.merge_event - by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)} + by #{link_to_member(@project, @merge_request.merge_event.author, avatar: true)} #{time_ago_with_tooltip(@merge_request.merge_event.created_at)} = render "projects/merge_requests/show/remove_source_branch" From ed7f42fd5c494efcf61655e8b1aff7e9ccbf5377 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Jun 2015 12:25:05 +0200 Subject: [PATCH 188/255] Better margin for header in accept MR widget Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/pages/merge_requests.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss index 9c4a7c70e9..f5ac7bd880 100644 --- a/app/assets/stylesheets/pages/merge_requests.scss +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -156,7 +156,7 @@ h4 { font-weight: bold; - margin-top: 5px; + margin: 5px 0; } p:last-child { From 7927c320e80cad1ffbcdbc31cebce5d3dd92add7 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Jun 2015 12:37:08 +0200 Subject: [PATCH 189/255] Exmplain gitlab-linguist fork existence Signed-off-by: Dmitriy Zaporozhets --- Gemfile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Gemfile b/Gemfile index 35f6b42f87..d81b3252dc 100644 --- a/Gemfile +++ b/Gemfile @@ -57,6 +57,10 @@ gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap" gem 'gollum-lib', '~> 4.0.2' # Language detection +# Our fork of linguist does not require pygments/python dependency. +# New version of original gem also dropped pygments support but it has strict +# dependency to unstable rugged version. We have internal issue for replacing +# fork with original gem when we meet on same rugged version - https://dev.gitlab.org/gitlab/gitlabhq/issues/2052. gem "gitlab-linguist", "~> 3.0.1", require: "linguist" # API From ea498c4421b019b53e64ea5688e92e8240046c84 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Jun 2015 12:59:02 +0200 Subject: [PATCH 190/255] Add comments about gitlab forks Signed-off-by: Dmitriy Zaporozhets --- Gemfile | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Gemfile b/Gemfile index d81b3252dc..f5082f626a 100644 --- a/Gemfile +++ b/Gemfile @@ -48,16 +48,20 @@ gem "browser" gem "gitlab_git", '~> 7.1.13' # Ruby/Rack Git Smart-HTTP Server Handler +# GitLab fork with a lot of changes (improved thread-safety, better memory usage etc) +# For full list of changes see https://github.com/SaitoWu/grack/compare/master...gitlabhq:master gem 'gitlab-grack', '~> 2.0.2', require: 'grack' # LDAP Auth +# GitLab fork with several improvements to original library. For full list of changes +# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap" # Git Wiki gem 'gollum-lib', '~> 4.0.2' # Language detection -# Our fork of linguist does not require pygments/python dependency. +# GitLab fork of linguist does not require pygments/python dependency. # New version of original gem also dropped pygments support but it has strict # dependency to unstable rugged version. We have internal issue for replacing # fork with original gem when we meet on same rugged version - https://dev.gitlab.org/gitlab/gitlabhq/issues/2052. From d4dde374aa959873b87018f71abd62b541d08bd0 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Jun 2015 15:45:36 +0200 Subject: [PATCH 191/255] Refactor header css/html Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/base/variables.scss | 1 + app/assets/stylesheets/generic/header.scss | 206 +++++++++--------- app/assets/stylesheets/generic/sidebar.scss | 6 +- .../stylesheets/themes/gitlab-theme.scss | 3 +- app/views/layouts/_empty_head_panel.html.haml | 2 +- app/views/layouts/_head_panel.html.haml | 63 +++--- .../layouts/_public_head_panel.html.haml | 25 ++- 7 files changed, 153 insertions(+), 153 deletions(-) diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss index 596376c397..c44fa06fc5 100644 --- a/app/assets/stylesheets/base/variables.scss +++ b/app/assets/stylesheets/base/variables.scss @@ -5,6 +5,7 @@ $gl-link-color: #446e9b; $nprogress-color: #c0392b; $gl-font-size: 14px; $list-font-size: 15px; +$sidebar_collapsed_width: 52px; $sidebar_width: 230px; $avatar_radius: 50%; $code_font_size: 13px; diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss index 3b0ee264bc..5da4d14b3b 100644 --- a/app/assets/stylesheets/generic/header.scss +++ b/app/assets/stylesheets/generic/header.scss @@ -2,7 +2,13 @@ * Application Header * */ +$header-height: 46px; + header { + &.empty-header .container { + border-bottom: 1px solid #EEE; + } + &.navbar-gitlab { z-index: 100; margin-bottom: 0; @@ -13,54 +19,9 @@ header { .container { width: 100% !important; padding: 0; - padding-right: 35px; background: #FFF; - border-bottom: 1px solid #EEE; filter: none; - .title { - position: relative; - float: left; - margin: 0; - margin-left: 25px; - font-size: 18px; - line-height: 44px; - font-weight: bold; - color: #444; - - @include str-truncated(37%); - - a { - color: #444; - &:hover { - text-decoration: underline; - } - } - } - - .app_logo { - border-bottom: 1px solid transparent; - margin-bottom: -1px; - - a { - padding: 5px 8px; - - img { - float: left; - } - - h3 { - width: 158px; - float: left; - margin: 0; - margin-left: 20px; - font-size: 18px; - line-height: 34px; - font-weight: normal; - } - } - } - .nav > li > a { color: #888; font-size: 14px; @@ -80,7 +41,6 @@ header { } } - /** NAV block with links and profile **/ .nav { float: right; margin-right: 0; @@ -96,73 +56,68 @@ header { } } } - - .turbolink-spinner { - font-size: 20px; - margin-right: 10px; - } - - @media (max-width: $screen-xs-max) { - border-width: 0; - font-size: 18px; - - .title { - @include str-truncated(70%); - } - - .navbar-collapse { - margin-top: 47px; - } - - .navbar-nav { - margin: 5px 0; - - .visible-xs, .visable-sm { - display: table-cell !important; - } - } - - li { - display: table-cell; - width: 1%; - - a { - text-align: center; - font-size: 18px !important; - } - } - } } - /** - * - * Logo holder - * - */ - .app_logo { + .header-logo { + border-bottom: 1px solid transparent; float: left; - margin-right: 9px; + height: $header-height; + width: $sidebar_width; a { float: left; - height: 46px; + height: $header-height; width: 100%; + padding: 5px 8px; + + h3 { + width: 158px; + float: left; + margin: 0; + margin-left: 20px; + font-size: 18px; + line-height: 34px; + font-weight: normal; + } img { width: 36px; height: 36px; + float: left; } } + &:hover { background-color: #EEE; } } - /** - * - * Search box - * - */ + .header-content { + border-bottom: 1px solid #EEE; + padding-right: 35px; + height: $header-height; + + .title { + position: relative; + float: left; + margin: 0; + margin-left: 35px; + font-size: 18px; + line-height: 44px; + font-weight: bold; + color: #444; + + @include str-truncated(37%); + + a { + color: #444; + &:hover { + text-decoration: underline; + } + } + } + } + .search { margin-right: 10px; margin-left: 10px; @@ -198,6 +153,22 @@ header { width: 300px; } +@mixin collapsed-header { + .header-logo { + width: $sidebar_collapsed_width; + + h3 { + display: none; + } + } + + .header-content { + .title { + margin-left: 30px; + } + } +} + @media (max-width: 1200px) { .search .search-input { width: 200px; @@ -212,23 +183,48 @@ header { @media (max-width: $screen-md-max) { .header-collapsed, .header-expanded { - width: 52px; - - h3 { - display: none; - } + @include collapsed-header; } } @media(min-width: $screen-md-max) { .header-collapsed { - width: 52px; - - h3 { - display: none; - } + @include collapsed-header; } .header-expanded { } } + +@media (max-width: $screen-xs-max) { + header .container { + border-width: 0; + font-size: 18px; + + .title { + @include str-truncated(70%); + } + + .navbar-collapse { + margin-top: 47px; + } + + .navbar-nav { + margin: 5px 0; + + .visible-xs, .visable-sm { + display: table-cell !important; + } + } + + li { + display: table-cell; + width: 1%; + + a { + text-align: center; + font-size: 18px !important; + } + } + } +} diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss index a80b585080..5d4dee5691 100644 --- a/app/assets/stylesheets/generic/sidebar.scss +++ b/app/assets/stylesheets/generic/sidebar.scss @@ -102,13 +102,13 @@ padding-left: 50px; .sidebar-wrapper { - width: 52px; + width: $sidebar_collapsed_width; .nav-sidebar { margin-top: 29px; position: fixed; top: 45px; - width: 52px; + width: $sidebar_collapsed_width; li a { padding-left: 18px; @@ -125,7 +125,7 @@ .collapse-nav a { left: 0px; - width: 52px; + width: $sidebar_collapsed_width; } .sidebar-user { diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss index 9b8e3d8e29..a52c847da6 100644 --- a/app/assets/stylesheets/themes/gitlab-theme.scss +++ b/app/assets/stylesheets/themes/gitlab-theme.scss @@ -1,8 +1,9 @@ @mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) { header { &.navbar-gitlab { - .app_logo { + .header-logo { background-color: $color-darker; + border-color: $color-darker; a { color: $color-light; diff --git a/app/views/layouts/_empty_head_panel.html.haml b/app/views/layouts/_empty_head_panel.html.haml index 358caa3868..4939634dec 100644 --- a/app/views/layouts/_empty_head_panel.html.haml +++ b/app/views/layouts/_empty_head_panel.html.haml @@ -1,4 +1,4 @@ -%header.navbar.navbar-fixed-top.navbar-gitlab +%header.navbar.navbar-fixed-top.navbar-gitlab.empty-header .container %h4.center = image_tag 'logo-white.png', width: 32, height: 32 diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index ddf1ffc761..43076ee886 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -1,42 +1,43 @@ %header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } .container - %div.app_logo + .header-logo = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do = brand_header_logo %h3 GitLab - %h1.title - = title + .header-content + %h1.title + = title - %button.navbar-toggle{type: 'button', data: {target: '.navbar-collapse', toggle: 'collapse'}} - %span.sr-only Toggle navigation - = icon('bars') + %button.navbar-toggle{type: 'button', data: {target: '.navbar-collapse', toggle: 'collapse'}} + %span.sr-only Toggle navigation + = icon('bars') - .navbar-collapse.collapse - %ul.nav.navbar-nav - %li.hidden-sm.hidden-xs - = render 'layouts/search' - %li.visible-sm.visible-xs - = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('search') - %li - = link_to help_path, title: 'Help', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('question-circle fw') - %li - = link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('globe fw') - %li - = link_to user_snippets_path(current_user), title: 'Your snippets', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('clipboard fw') - - if current_user.is_admin? + .navbar-collapse.collapse + %ul.nav.navbar-nav + %li.hidden-sm.hidden-xs + = render 'layouts/search' + %li.visible-sm.visible-xs + = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('search') %li - = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('wrench fw') - - if current_user.can_create_project? + = link_to help_path, title: 'Help', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('question-circle fw') %li - = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('plus fw') - %li - = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do - = icon('cog fw') + = link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('globe fw') + %li + = link_to user_snippets_path(current_user), title: 'Your snippets', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('clipboard fw') + - if current_user.is_admin? + %li + = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('wrench fw') + - if current_user.can_create_project? + %li + = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('plus fw') + %li + = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do + = icon('cog fw') = render 'shared/outdated_browser' diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml index 8a297566d6..5cccf9a6ad 100644 --- a/app/views/layouts/_public_head_panel.html.haml +++ b/app/views/layouts/_public_head_panel.html.haml @@ -1,22 +1,23 @@ %header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } .container - %div.app_logo + .header-logo = link_to explore_root_path, class: "home" do = brand_header_logo %h3 GitLab - %h1.title= title + .header-content + %h1.title= title - %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"} - %span.sr-only Toggle navigation - %i.fa.fa-bars + %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"} + %span.sr-only Toggle navigation + %i.fa.fa-bars - - unless current_controller?('sessions') - .pull-right.hidden-xs - = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new append-right-10' + - unless current_controller?('sessions') + .pull-right.hidden-xs + = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new append-right-10' - .navbar-collapse.collapse - %ul.nav.navbar-nav - %li.visible-xs - = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes') + .navbar-collapse.collapse + %ul.nav.navbar-nav + %li.visible-xs + = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes') = render 'shared/outdated_browser' From 46e3d13eac8f679044e4fcf16262346ccb8064df Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Jun 2015 16:36:26 +0200 Subject: [PATCH 192/255] More fixes to header css Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/generic/header.scss | 6 ++++-- app/assets/stylesheets/generic/mobile.scss | 2 +- app/assets/stylesheets/generic/sidebar.scss | 2 -- app/assets/stylesheets/themes/gitlab-theme.scss | 2 -- app/views/layouts/_empty_head_panel.html.haml | 2 +- app/views/layouts/errors.html.haml | 2 +- 6 files changed, 7 insertions(+), 9 deletions(-) diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss index 5da4d14b3b..3a227d11ee 100644 --- a/app/assets/stylesheets/generic/header.scss +++ b/app/assets/stylesheets/generic/header.scss @@ -5,7 +5,8 @@ $header-height: 46px; header { - &.empty-header .container { + &.navbar-empty { + background: #FFF; border-bottom: 1px solid #EEE; } @@ -17,9 +18,9 @@ header { width: 100%; .container { + background: #FFF; width: 100% !important; padding: 0; - background: #FFF; filter: none; .nav > li > a { @@ -96,6 +97,7 @@ header { border-bottom: 1px solid #EEE; padding-right: 35px; height: $header-height; + overflow: hidden; .title { position: relative; diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss index b7f6fac522..74108c1f08 100644 --- a/app/assets/stylesheets/generic/mobile.scss +++ b/app/assets/stylesheets/generic/mobile.scss @@ -57,7 +57,7 @@ } .container .title { - margin-left: 6px !important; + margin-left: 15px !important; max-width: 70% !important; } } diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss index 5d4dee5691..69bddc6f59 100644 --- a/app/assets/stylesheets/generic/sidebar.scss +++ b/app/assets/stylesheets/generic/sidebar.scss @@ -1,6 +1,4 @@ .page-with-sidebar { - background: $background-color; - .sidebar-wrapper { position: fixed; top: 0; diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss index a52c847da6..1b06b4aa92 100644 --- a/app/assets/stylesheets/themes/gitlab-theme.scss +++ b/app/assets/stylesheets/themes/gitlab-theme.scss @@ -20,8 +20,6 @@ } .page-with-sidebar { - background: $color-darker; - .collapse-nav a { color: #FFF; background: $color; diff --git a/app/views/layouts/_empty_head_panel.html.haml b/app/views/layouts/_empty_head_panel.html.haml index 4939634dec..16fbf6d402 100644 --- a/app/views/layouts/_empty_head_panel.html.haml +++ b/app/views/layouts/_empty_head_panel.html.haml @@ -1,4 +1,4 @@ -%header.navbar.navbar-fixed-top.navbar-gitlab.empty-header +%header.navbar.navbar-fixed-top.navbar-empty .container %h4.center = image_tag 'logo-white.png', width: 32, height: 32 diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml index aa0f3f0a81..25cce11990 100644 --- a/app/views/layouts/errors.html.haml +++ b/app/views/layouts/errors.html.haml @@ -2,7 +2,7 @@ %html{ lang: "en"} = render "layouts/head" %body{class: "#{app_theme} application"} - = render "layouts/head_panel", title: "" if current_user + = render "layouts/empty_head_panel" .container.navless-container = render "layouts/flash" .error-page From e7e07fab5d64db8b2881d7fa3477553807b0d3e1 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Jun 2015 17:09:30 +0200 Subject: [PATCH 193/255] Refactor header views Signed-off-by: Dmitriy Zaporozhets --- .../layouts/_public_head_panel.html.haml | 23 ------------------- app/views/layouts/application.html.haml | 4 ++-- app/views/layouts/devise.html.haml | 2 +- app/views/layouts/errors.html.haml | 2 +- .../_default.html.haml} | 0 .../_empty.html.haml} | 0 app/views/layouts/header/_public.html.haml | 14 +++++++++++ 7 files changed, 18 insertions(+), 27 deletions(-) delete mode 100644 app/views/layouts/_public_head_panel.html.haml rename app/views/layouts/{_head_panel.html.haml => header/_default.html.haml} (100%) rename app/views/layouts/{_empty_head_panel.html.haml => header/_empty.html.haml} (100%) create mode 100644 app/views/layouts/header/_public.html.haml diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml deleted file mode 100644 index 5cccf9a6ad..0000000000 --- a/app/views/layouts/_public_head_panel.html.haml +++ /dev/null @@ -1,23 +0,0 @@ -%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } - .container - .header-logo - = link_to explore_root_path, class: "home" do - = brand_header_logo - %h3 GitLab - .header-content - %h1.title= title - - %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"} - %span.sr-only Toggle navigation - %i.fa.fa-bars - - - unless current_controller?('sessions') - .pull-right.hidden-xs - = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new append-right-10' - - .navbar-collapse.collapse - %ul.nav.navbar-nav - %li.visible-xs - = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes') - -= render 'shared/outdated_browser' diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index a97feeb1ec..155825cc4c 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -3,8 +3,8 @@ = render "layouts/head" %body{class: "#{app_theme}", :'data-page' => body_data_page} - if current_user - = render "layouts/head_panel", title: header_title + = render "layouts/header/default", title: header_title - else - = render "layouts/public_head_panel", title: header_title + = render "layouts/header/public", title: header_title = render 'layouts/page', sidebar: sidebar diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index 5a59c9fd59..d406f5764a 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -2,7 +2,7 @@ %html{ lang: "en"} = render "layouts/head" %body.ui_mars.login-page.application - = render "layouts/empty_head_panel" + = render "layouts/header/empty" = render "layouts/broadcast" .container.navless-container .content diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml index 25cce11990..2e3a2b16eb 100644 --- a/app/views/layouts/errors.html.haml +++ b/app/views/layouts/errors.html.haml @@ -2,7 +2,7 @@ %html{ lang: "en"} = render "layouts/head" %body{class: "#{app_theme} application"} - = render "layouts/empty_head_panel" + = render "layouts/header/empty" .container.navless-container = render "layouts/flash" .error-page diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/header/_default.html.haml similarity index 100% rename from app/views/layouts/_head_panel.html.haml rename to app/views/layouts/header/_default.html.haml diff --git a/app/views/layouts/_empty_head_panel.html.haml b/app/views/layouts/header/_empty.html.haml similarity index 100% rename from app/views/layouts/_empty_head_panel.html.haml rename to app/views/layouts/header/_empty.html.haml diff --git a/app/views/layouts/header/_public.html.haml b/app/views/layouts/header/_public.html.haml new file mode 100644 index 0000000000..6a031722aa --- /dev/null +++ b/app/views/layouts/header/_public.html.haml @@ -0,0 +1,14 @@ +%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class } + .container + .header-logo + = link_to explore_root_path, class: "home" do + = brand_header_logo + %h3 GitLab + .header-content + %h1.title= title + + - unless current_controller?('sessions') + .pull-right + = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success btn-sm' + += render 'shared/outdated_browser' From a74dfa6e74947a97f10b6c13bf77a135df29ad56 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Jun 2015 17:10:04 +0200 Subject: [PATCH 194/255] Fix header overflow for big title Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/generic/common.scss | 2 +- app/assets/stylesheets/generic/header.scss | 29 +++++----------------- 2 files changed, 7 insertions(+), 24 deletions(-) diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index b69c5c4b57..1419a9cded 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -307,7 +307,7 @@ table { } .btn-sign-in { - margin-top: 5px; + margin-top: 7px; text-shadow: none; } diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss index 3a227d11ee..5e6102e14a 100644 --- a/app/assets/stylesheets/generic/header.scss +++ b/app/assets/stylesheets/generic/header.scss @@ -97,7 +97,6 @@ header { border-bottom: 1px solid #EEE; padding-right: 35px; height: $header-height; - overflow: hidden; .title { position: relative; @@ -131,6 +130,7 @@ header { } .search-input { + width: 220px; background-image: image-url("icon-search.png"); background-repeat: no-repeat; background-position: 10px; @@ -151,10 +151,6 @@ header { } } -.search .search-input { - width: 300px; -} - @mixin collapsed-header { .header-logo { width: $sidebar_collapsed_width; @@ -171,19 +167,11 @@ header { } } -@media (max-width: 1200px) { - .search .search-input { - width: 200px; - } -} - -@media (max-width: $screen-xs-max) { - #nprogress .spinner { - right: 35px !important; - } -} - @media (max-width: $screen-md-max) { + header .container .title { + max-width: 43%; + } + .header-collapsed, .header-expanded { @include collapsed-header; } @@ -200,15 +188,10 @@ header { @media (max-width: $screen-xs-max) { header .container { - border-width: 0; font-size: 18px; .title { - @include str-truncated(70%); - } - - .navbar-collapse { - margin-top: 47px; + max-width: 70%; } .navbar-nav { From 01f6ae235173ff46ff23498ec91accbeb1845863 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Jun 2015 17:55:20 +0200 Subject: [PATCH 195/255] improve navbar collapse for mobile views Signed-off-by: Dmitriy Zaporozhets --- app/assets/javascripts/application.js.coffee | 4 ++++ app/assets/stylesheets/generic/header.scss | 15 ++++----------- app/views/layouts/header/_default.html.haml | 4 ++-- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index ea2a4b9710..9fc313db9d 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -177,6 +177,10 @@ $ -> $(@).next('table').show() $(@).remove() + $('.navbar-toggle').on 'click', -> + $('.header-content .title').toggle() + $('.header-content .navbar-collapse').toggle() + # Show/hide comments on diff $("body").on "click", ".js-toggle-diff-comments", (e) -> $(@).toggleClass('active') diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss index 5e6102e14a..71afccba00 100644 --- a/app/assets/stylesheets/generic/header.scss +++ b/app/assets/stylesheets/generic/header.scss @@ -42,15 +42,12 @@ header { } } - .nav { - float: right; - margin-right: 0; - } - .navbar-toggle { color: #666; margin: 0; border-radius: 0; + position: absolute; + right: 2px; &:hover { background-color: #EEE; @@ -195,7 +192,8 @@ header { } .navbar-nav { - margin: 5px 0; + margin: 0px; + float: none !important; .visible-xs, .visable-sm { display: table-cell !important; @@ -205,11 +203,6 @@ header { li { display: table-cell; width: 1%; - - a { - text-align: center; - font-size: 18px !important; - } } } } diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 43076ee886..2970af377f 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -8,12 +8,12 @@ %h1.title = title - %button.navbar-toggle{type: 'button', data: {target: '.navbar-collapse', toggle: 'collapse'}} + %button.navbar-toggle %span.sr-only Toggle navigation = icon('bars') .navbar-collapse.collapse - %ul.nav.navbar-nav + %ul.nav.navbar-nav.pull-right %li.hidden-sm.hidden-xs = render 'layouts/search' %li.visible-sm.visible-xs From 734a4ba87de7bc8cf152c5bc7f93ba04210b282d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Jun 2015 20:49:11 +0200 Subject: [PATCH 196/255] Create and edit files in web editor via rugged Signed-off-by: Dmitriy Zaporozhets --- Gemfile | 2 +- Gemfile.lock | 4 ++-- app/models/repository.rb | 25 +++++++++++++++++++++++++ app/services/files/create_service.rb | 24 +++++++++++++++++------- app/services/files/update_service.rb | 24 +++++++++++++++++------- 5 files changed, 62 insertions(+), 17 deletions(-) diff --git a/Gemfile b/Gemfile index f5082f626a..535b59caa3 100644 --- a/Gemfile +++ b/Gemfile @@ -45,7 +45,7 @@ gem "browser" # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '~> 7.1.13' +gem "gitlab_git", '~> 7.2.0' # Ruby/Rack Git Smart-HTTP Server Handler # GitLab fork with a lot of changes (improved thread-safety, better memory usage etc) diff --git a/Gemfile.lock b/Gemfile.lock index cc373f5a0d..3d21dff299 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -225,7 +225,7 @@ GEM mime-types (~> 1.19) gitlab_emoji (0.1.0) gemojione (~> 2.0) - gitlab_git (7.1.13) + gitlab_git (7.2.0) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-linguist (~> 3.0) @@ -733,7 +733,7 @@ DEPENDENCIES gitlab-grack (~> 2.0.2) gitlab-linguist (~> 3.0.1) gitlab_emoji (~> 0.1) - gitlab_git (~> 7.1.13) + gitlab_git (~> 7.2.0) gitlab_meta (= 7.0) gitlab_omniauth-ldap (= 1.2.1) gollum-lib (~> 4.0.2) diff --git a/app/models/repository.rb b/app/models/repository.rb index 1b8c74028d..c558050328 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -370,6 +370,31 @@ class Repository @root_ref ||= raw_repository.root_ref end + def commit_file(user, path, content, message, ref) + path[0] = '' if path[0] == '/' + + author = { + email: user.email, + name: user.name, + time: Time.now + } + + options = {} + options[:committer] = author + options[:author] = author + options[:commit] = { + message: message, + branch: ref + } + + options[:file] = { + content: content, + path: path + } + + Gitlab::Git::Blob.commit(raw_repository, options) + end + private def cache diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index 23833aa78e..c0cf595632 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -33,14 +33,24 @@ module Files end end + if params[:encoding] == 'base64' + new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path) + created_successfully = new_file_action.commit!( + params[:content], + params[:commit_message], + params[:encoding], + params[:new_branch] + ) + else + created_successfull = repository.commit_file( + current_user, + file_path, + params[:content], + params[:commit_message], + params[:new_branch] || ref + ) + end - new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path) - created_successfully = new_file_action.commit!( - params[:content], - params[:commit_message], - params[:encoding], - params[:new_branch] - ) if created_successfully success diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index 0724d3ae63..5efd43d16c 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -19,13 +19,23 @@ module Files return error("You can only edit text files") end - edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path) - edit_file_action.commit!( - params[:content], - params[:commit_message], - params[:encoding], - params[:new_branch] - ) + if params[:encoding] == 'base64' + edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path) + edit_file_action.commit!( + params[:content], + params[:commit_message], + params[:encoding], + params[:new_branch] + ) + else + repository.commit_file( + current_user, + path, + params[:content], + params[:commit_message], + params[:new_branch] || ref + ) + end success rescue Gitlab::Satellite::CheckoutFailed => ex From 27a158506e033acd7195acf91995c1574e122832 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Jun 2015 21:17:13 +0200 Subject: [PATCH 197/255] Fix adding new file to empty repo Signed-off-by: Dmitriy Zaporozhets --- Gemfile | 2 +- Gemfile.lock | 4 ++-- app/services/files/create_service.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Gemfile b/Gemfile index 535b59caa3..70f7e5cf41 100644 --- a/Gemfile +++ b/Gemfile @@ -45,7 +45,7 @@ gem "browser" # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '~> 7.2.0' +gem "gitlab_git", '~> 7.2.1' # Ruby/Rack Git Smart-HTTP Server Handler # GitLab fork with a lot of changes (improved thread-safety, better memory usage etc) diff --git a/Gemfile.lock b/Gemfile.lock index 3d21dff299..ae411a3495 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -225,7 +225,7 @@ GEM mime-types (~> 1.19) gitlab_emoji (0.1.0) gemojione (~> 2.0) - gitlab_git (7.2.0) + gitlab_git (7.2.1) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-linguist (~> 3.0) @@ -733,7 +733,7 @@ DEPENDENCIES gitlab-grack (~> 2.0.2) gitlab-linguist (~> 3.0.1) gitlab_emoji (~> 0.1) - gitlab_git (~> 7.2.0) + gitlab_git (~> 7.2.1) gitlab_meta (= 7.0) gitlab_omniauth-ldap (= 1.2.1) gollum-lib (~> 4.0.2) diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index c0cf595632..21065f7151 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -42,7 +42,7 @@ module Files params[:new_branch] ) else - created_successfull = repository.commit_file( + created_successfully = repository.commit_file( current_user, file_path, params[:content], From d9d9c7d7bcb55fe8aa950464ffc691080eff9352 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Jun 2015 23:43:08 +0200 Subject: [PATCH 198/255] Allow base64 for edit blobs Signed-off-by: Dmitriy Zaporozhets --- app/views/projects/blob/_editor.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml index 96f188e4aa..9c3e1703c8 100644 --- a/app/views/projects/blob/_editor.html.haml +++ b/app/views/projects/blob/_editor.html.haml @@ -12,8 +12,8 @@ \/ = text_field_tag 'file_name', params[:file_name], placeholder: "File name", required: true, class: 'form-control new-file-name' - .pull-right - = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control' + .pull-right + = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control' .file-content.code %pre.js-edit-mode-pane#editor From 541133197be098732cdc14a12aa059e21cac3d71 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 1 Jun 2015 23:43:35 +0200 Subject: [PATCH 199/255] Use rugged in web editor for base64 encoding Signed-off-by: Dmitriy Zaporozhets --- app/services/files/create_service.rb | 31 +++++++++++++--------------- app/services/files/update_service.rb | 31 +++++++++++++--------------- 2 files changed, 28 insertions(+), 34 deletions(-) diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index 21065f7151..3516cf30db 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -33,23 +33,20 @@ module Files end end - if params[:encoding] == 'base64' - new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path) - created_successfully = new_file_action.commit!( - params[:content], - params[:commit_message], - params[:encoding], - params[:new_branch] - ) - else - created_successfully = repository.commit_file( - current_user, - file_path, - params[:content], - params[:commit_message], - params[:new_branch] || ref - ) - end + content = + if params[:encoding] == 'base64' + Base64.decode64(params[:content]) + else + params[:content] + end + + created_successfully = repository.commit_file( + current_user, + file_path, + content, + params[:commit_message], + params[:new_branch] || ref + ) if created_successfully diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index 5efd43d16c..4d7ac3b750 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -19,23 +19,20 @@ module Files return error("You can only edit text files") end - if params[:encoding] == 'base64' - edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path) - edit_file_action.commit!( - params[:content], - params[:commit_message], - params[:encoding], - params[:new_branch] - ) - else - repository.commit_file( - current_user, - path, - params[:content], - params[:commit_message], - params[:new_branch] || ref - ) - end + content = + if params[:encoding] == 'base64' + Base64.decode64(params[:content]) + else + params[:content] + end + + repository.commit_file( + current_user, + path, + content, + params[:commit_message], + params[:new_branch] || ref + ) success rescue Gitlab::Satellite::CheckoutFailed => ex From cf7707b4fe474e5399481a04911cb08043c14874 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 1 Jun 2015 22:42:53 -0700 Subject: [PATCH 200/255] Omit link to generate labels if user does not have access to create them Closes https://github.com/gitlabhq/gitlabhq/issues/8353 --- CHANGELOG | 1 + app/views/projects/labels/index.html.haml | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 61e9084a39..2bf49fd541 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 7.12.0 (unreleased) + - Omit link to generate labels if user does not have access to create them (Stan Hu) - Disable changing of the source branch in merge request update API (Stan Hu) - Shorten merge request WIP text. - Add option to disallow users from registering any application to use GitLab as an OAuth provider diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 7d19415a7f..d44fe48621 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -13,4 +13,7 @@ = paginate @labels, theme: 'gitlab' - else .light-well - .nothing-here-block Create first label or #{link_to 'generate', generate_namespace_project_labels_path(@project.namespace, @project), method: :post} default set of labels + - if can? current_user, :admin_label, @project + .nothing-here-block Create first label or #{link_to 'generate', generate_namespace_project_labels_path(@project.namespace, @project), method: :post} default set of labels + - else + .nothing-here-block No labels created From 2c403dfd924e0b1f5bc8a7a70d0ae757b350b7ee Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Jun 2015 10:24:50 +0200 Subject: [PATCH 201/255] Remove file api tests which depend on old satellite logic Signed-off-by: Dmitriy Zaporozhets --- spec/requests/api/files_spec.rb | 35 +++------------------------------ 1 file changed, 3 insertions(+), 32 deletions(-) diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index bab8888a63..87ae8c96cc 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -63,9 +63,9 @@ describe API::API, api: true do expect(response.status).to eq(400) end - it "should return a 400 if satellite fails to create file" do - Gitlab::Satellite::NewFileAction.any_instance.stub( - commit!: false, + it "should return a 400 if editor fails to create file" do + Repository.any_instance.stub( + commit_file: false, ) post api("/projects/#{project.id}/repository/files", user), valid_params @@ -97,35 +97,6 @@ describe API::API, api: true do put api("/projects/#{project.id}/repository/files", user) expect(response.status).to eq(400) end - - it 'should return a 400 if the checkout fails' do - Gitlab::Satellite::EditFileAction.any_instance.stub(:commit!) - .and_raise(Gitlab::Satellite::CheckoutFailed) - - put api("/projects/#{project.id}/repository/files", user), valid_params - expect(response.status).to eq(400) - - ref = valid_params[:branch_name] - expect(response.body).to match("ref '#{ref}' could not be checked out") - end - - it 'should return a 409 if the file was not modified' do - Gitlab::Satellite::EditFileAction.any_instance.stub(:commit!) - .and_raise(Gitlab::Satellite::CommitFailed) - - put api("/projects/#{project.id}/repository/files", user), valid_params - expect(response.status).to eq(409) - expect(response.body).to match("Maybe there was nothing to commit?") - end - - it 'should return a 409 if the push fails' do - Gitlab::Satellite::EditFileAction.any_instance.stub(:commit!) - .and_raise(Gitlab::Satellite::PushFailed) - - put api("/projects/#{project.id}/repository/files", user), valid_params - expect(response.status).to eq(409) - expect(response.body).to match("Maybe the file was changed by another process?") - end end describe "DELETE /projects/:id/repository/files" do From 3d416f1682c5e6a6ac1ea7013f66bbd0d23b452c Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Jun 2015 10:45:13 +0200 Subject: [PATCH 202/255] Create activity event and execute hooks on web editor commit Signed-off-by: Dmitriy Zaporozhets --- app/services/files/base_service.rb | 6 ++++++ app/services/files/create_service.rb | 5 +++-- app/services/files/update_service.rb | 3 ++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index bd24510095..29013be0f9 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -13,5 +13,11 @@ module Files def repository project.repository end + + def after_commit(sha) + commit = repository.commit(sha) + full_ref = 'refs/heads/' + (params[:new_branch] || ref) + GitPushService.new.execute(project, current_user, commit.parent_id, sha, full_ref) + end end end diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index 3516cf30db..bafc3565da 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -40,7 +40,7 @@ module Files params[:content] end - created_successfully = repository.commit_file( + sha = repository.commit_file( current_user, file_path, content, @@ -49,7 +49,8 @@ module Files ) - if created_successfully + if sha + after_commit(sha) success else error("Your changes could not be committed, because the file has been changed") diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index 4d7ac3b750..c972f8322b 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -26,7 +26,7 @@ module Files params[:content] end - repository.commit_file( + sha = repository.commit_file( current_user, path, content, @@ -34,6 +34,7 @@ module Files params[:new_branch] || ref ) + after_commit(sha) success rescue Gitlab::Satellite::CheckoutFailed => ex error("Your changes could not be committed because ref '#{ref}' could not be checked out", 400) From 8ad5f0848361b07d3f50f087da942aea63bc9f33 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Jun 2015 10:56:22 +0200 Subject: [PATCH 203/255] Remove now unnecessary satelittes logic for creating and editing file with web editor Signed-off-by: Dmitriy Zaporozhets --- .../satellite/files/edit_file_action.rb | 68 ------------------- lib/gitlab/satellite/files/file_action.rb | 8 --- lib/gitlab/satellite/files/new_file_action.rb | 67 ------------------ 3 files changed, 143 deletions(-) delete mode 100644 lib/gitlab/satellite/files/edit_file_action.rb delete mode 100644 lib/gitlab/satellite/files/new_file_action.rb diff --git a/lib/gitlab/satellite/files/edit_file_action.rb b/lib/gitlab/satellite/files/edit_file_action.rb deleted file mode 100644 index 3cb9c0b5ec..0000000000 --- a/lib/gitlab/satellite/files/edit_file_action.rb +++ /dev/null @@ -1,68 +0,0 @@ -require_relative 'file_action' - -module Gitlab - module Satellite - # GitLab server-side file update and commit - class EditFileAction < FileAction - # Updates the files content and creates a new commit for it - # - # Returns false if the ref has been updated while editing the file - # Returns false if committing the change fails - # Returns false if pushing from the satellite to bare repo failed or was rejected - # Returns true otherwise - def commit!(content, commit_message, encoding, new_branch = nil) - in_locked_and_timed_satellite do |repo| - prepare_satellite!(repo) - - # create target branch in satellite at the corresponding commit from bare repo - begin - repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}") - rescue Grit::Git::CommandFailed => ex - log_and_raise(CheckoutFailed, ex.message) - end - - # update the file in the satellite's working dir - file_path_in_satellite = File.join(repo.working_dir, file_path) - - # Prevent relative links - unless safe_path?(file_path_in_satellite) - Gitlab::GitLogger.error("FileAction: Relative path not allowed") - return false - end - - # Write file - write_file(file_path_in_satellite, content, encoding) - - # commit the changes - # will raise CommandFailed when commit fails - begin - repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) - rescue Grit::Git::CommandFailed => ex - log_and_raise(CommitFailed, ex.message) - end - - - target_branch = new_branch.present? ? "#{ref}:#{new_branch}" : ref - - # push commit back to bare repo - # will raise CommandFailed when push fails - begin - repo.git.push({ raise: true, timeout: true }, :origin, target_branch) - rescue Grit::Git::CommandFailed => ex - log_and_raise(PushFailed, ex.message) - end - - # everything worked - true - end - end - - private - - def log_and_raise(errorClass, message) - Gitlab::GitLogger.error(message) - raise(errorClass, message) - end - end - end -end diff --git a/lib/gitlab/satellite/files/file_action.rb b/lib/gitlab/satellite/files/file_action.rb index 6446b14568..0b441a59e3 100644 --- a/lib/gitlab/satellite/files/file_action.rb +++ b/lib/gitlab/satellite/files/file_action.rb @@ -12,14 +12,6 @@ module Gitlab def safe_path?(path) File.absolute_path(path) == path end - - def write_file(abs_file_path, content, file_encoding = 'text') - if file_encoding == 'base64' - File.open(abs_file_path, 'wb') { |f| f.write(Base64.decode64(content)) } - else - File.open(abs_file_path, 'w') { |f| f.write(content) } - end - end end end end diff --git a/lib/gitlab/satellite/files/new_file_action.rb b/lib/gitlab/satellite/files/new_file_action.rb deleted file mode 100644 index 724dfa0d04..0000000000 --- a/lib/gitlab/satellite/files/new_file_action.rb +++ /dev/null @@ -1,67 +0,0 @@ -require_relative 'file_action' - -module Gitlab - module Satellite - class NewFileAction < FileAction - # Updates the files content and creates a new commit for it - # - # Returns false if the ref has been updated while editing the file - # Returns false if committing the change fails - # Returns false if pushing from the satellite to bare repo failed or was rejected - # Returns true otherwise - def commit!(content, commit_message, encoding, new_branch = nil) - in_locked_and_timed_satellite do |repo| - prepare_satellite!(repo) - - # create target branch in satellite at the corresponding commit from bare repo - current_ref = - if @project.empty_repo? - # skip this step if we want to add first file to empty repo - Satellite::PARKING_BRANCH - else - repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}") - ref - end - - file_path_in_satellite = File.join(repo.working_dir, file_path) - dir_name_in_satellite = File.dirname(file_path_in_satellite) - - # Prevent relative links - unless safe_path?(file_path_in_satellite) - Gitlab::GitLogger.error("FileAction: Relative path not allowed") - return false - end - - # Create dir if not exists - FileUtils.mkdir_p(dir_name_in_satellite) - - # Write file - write_file(file_path_in_satellite, content, encoding) - - # add new file - repo.add(file_path_in_satellite) - - # commit the changes - # will raise CommandFailed when commit fails - repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) - - target_branch = if new_branch.present? && !@project.empty_repo? - "#{ref}:#{new_branch}" - else - "#{current_ref}:#{ref}" - end - - # push commit back to bare repo - # will raise CommandFailed when push fails - repo.git.push({ raise: true, timeout: true }, :origin, target_branch) - - # everything worked - true - end - rescue Grit::Git::CommandFailed => ex - Gitlab::GitLogger.error(ex.message) - false - end - end - end -end From 8997812626b85ff0838ec60047d17e0c5f2a5aca Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Jun 2015 11:41:21 +0200 Subject: [PATCH 204/255] Remove files in web editor using rugged Signed-off-by: Dmitriy Zaporozhets --- Gemfile | 2 +- Gemfile.lock | 4 +-- app/models/repository.rb | 38 ++++++++++++++++++++++------ app/services/files/delete_service.rb | 13 +++++----- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/Gemfile b/Gemfile index 70f7e5cf41..78af7f5db6 100644 --- a/Gemfile +++ b/Gemfile @@ -45,7 +45,7 @@ gem "browser" # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '~> 7.2.1' +gem "gitlab_git", '~> 7.2.2' # Ruby/Rack Git Smart-HTTP Server Handler # GitLab fork with a lot of changes (improved thread-safety, better memory usage etc) diff --git a/Gemfile.lock b/Gemfile.lock index ae411a3495..bbc5639c84 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -225,7 +225,7 @@ GEM mime-types (~> 1.19) gitlab_emoji (0.1.0) gemojione (~> 2.0) - gitlab_git (7.2.1) + gitlab_git (7.2.2) activesupport (~> 4.0) charlock_holmes (~> 0.6) gitlab-linguist (~> 3.0) @@ -733,7 +733,7 @@ DEPENDENCIES gitlab-grack (~> 2.0.2) gitlab-linguist (~> 3.0.1) gitlab_emoji (~> 0.1) - gitlab_git (~> 7.2.1) + gitlab_git (~> 7.2.2) gitlab_meta (= 7.0) gitlab_omniauth-ldap (= 1.2.1) gollum-lib (~> 4.0.2) diff --git a/app/models/repository.rb b/app/models/repository.rb index c558050328..1ca9701763 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -373,15 +373,10 @@ class Repository def commit_file(user, path, content, message, ref) path[0] = '' if path[0] == '/' - author = { - email: user.email, - name: user.name, - time: Time.now - } - + committer = user_to_comitter(user) options = {} - options[:committer] = author - options[:author] = author + options[:committer] = committer + options[:author] = committer options[:commit] = { message: message, branch: ref @@ -395,8 +390,35 @@ class Repository Gitlab::Git::Blob.commit(raw_repository, options) end + def remove_file(user, path, message, ref) + path[0] = '' if path[0] == '/' + + committer = user_to_comitter(user) + options = {} + options[:committer] = committer + options[:author] = committer + options[:commit] = { + message: message, + branch: ref + } + + options[:file] = { + path: path + } + + Gitlab::Git::Blob.remove(raw_repository, options) + end + private + def user_to_comitter(user) + { + email: user.email, + name: user.name, + time: Time.now + } + end + def cache @cache ||= RepositoryCache.new(path_with_namespace) end diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb index 1497a0f883..fabcdc1964 100644 --- a/app/services/files/delete_service.rb +++ b/app/services/files/delete_service.rb @@ -19,14 +19,15 @@ module Files return error("You can only edit text files") end - delete_file_action = Gitlab::Satellite::DeleteFileAction.new(current_user, project, ref, path) - - deleted_successfully = delete_file_action.commit!( - nil, - params[:commit_message] + sha = repository.remove_file( + current_user, + path, + params[:commit_message], + ref ) - if deleted_successfully + if sha + after_commit(sha) success else error("Your changes could not be committed, because the file has been changed") From 435f680b897b892103fa157d4699dbb6d9ecf758 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Jun 2015 12:06:40 +0200 Subject: [PATCH 205/255] Make web editor work correctly after switch from satellites Signed-off-by: Dmitriy Zaporozhets --- app/services/files/base_service.rb | 3 ++- app/services/files/create_service.rb | 2 +- app/services/files/delete_service.rb | 2 +- app/services/files/update_service.rb | 2 +- app/services/git_push_service.rb | 3 ++- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index 29013be0f9..4d02752454 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -17,7 +17,8 @@ module Files def after_commit(sha) commit = repository.commit(sha) full_ref = 'refs/heads/' + (params[:new_branch] || ref) - GitPushService.new.execute(project, current_user, commit.parent_id, sha, full_ref) + old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA + GitPushService.new.execute(project, current_user, old_sha, sha, full_ref) end end end diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index bafc3565da..0a80455bc6 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -1,7 +1,7 @@ require_relative "base_service" module Files - class CreateService < BaseService + class CreateService < Files::BaseService def execute allowed = Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref) diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb index fabcdc1964..2281777604 100644 --- a/app/services/files/delete_service.rb +++ b/app/services/files/delete_service.rb @@ -1,7 +1,7 @@ require_relative "base_service" module Files - class DeleteService < BaseService + class DeleteService < Files::BaseService def execute allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref) diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index c972f8322b..013cc1ee32 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -1,7 +1,7 @@ require_relative "base_service" module Files - class UpdateService < BaseService + class UpdateService < Files::BaseService def execute allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref) diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index bdf36af02f..cde65349d5 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -127,7 +127,8 @@ class GitPushService end def is_default_branch?(ref) - Gitlab::Git.branch_ref?(ref) && Gitlab::Git.ref_name(ref) == project.default_branch + Gitlab::Git.branch_ref?(ref) && + (Gitlab::Git.ref_name(ref) == project.default_branch || project.default_branch.nil?) end def commit_user(commit) From d684b11054ea2b5577f5d843170759609227bf22 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Jun 2015 12:07:00 +0200 Subject: [PATCH 206/255] Remove unnecessary satellite files and add CHANGELOG item Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + .../satellite/files/delete_file_action.rb | 50 ------------------- lib/gitlab/satellite/files/file_action.rb | 17 ------- spec/requests/api/files_spec.rb | 16 +----- 4 files changed, 3 insertions(+), 81 deletions(-) delete mode 100644 lib/gitlab/satellite/files/delete_file_action.rb delete mode 100644 lib/gitlab/satellite/files/file_action.rb diff --git a/CHANGELOG b/CHANGELOG index 61e9084a39..3940504d8b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -37,6 +37,7 @@ v 7.12.0 (unreleased) - User has ability to leave project - Add SAML support as an omniauth provider - Allow to configure a URL to show after sign out + - Better performance for web editor (switched from satellites to rugged) v 7.11.4 - Fix missing bullets when creating lists diff --git a/lib/gitlab/satellite/files/delete_file_action.rb b/lib/gitlab/satellite/files/delete_file_action.rb deleted file mode 100644 index 0d37b9dea8..0000000000 --- a/lib/gitlab/satellite/files/delete_file_action.rb +++ /dev/null @@ -1,50 +0,0 @@ -require_relative 'file_action' - -module Gitlab - module Satellite - class DeleteFileAction < FileAction - # Deletes file and creates a new commit for it - # - # Returns false if committing the change fails - # Returns false if pushing from the satellite to bare repo failed or was rejected - # Returns true otherwise - def commit!(content, commit_message) - in_locked_and_timed_satellite do |repo| - prepare_satellite!(repo) - - # create target branch in satellite at the corresponding commit from bare repo - repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}") - - # update the file in the satellite's working dir - file_path_in_satellite = File.join(repo.working_dir, file_path) - - # Prevent relative links - unless safe_path?(file_path_in_satellite) - Gitlab::GitLogger.error("FileAction: Relative path not allowed") - return false - end - - File.delete(file_path_in_satellite) - - # add removed file - repo.remove(file_path_in_satellite) - - # commit the changes - # will raise CommandFailed when commit fails - repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) - - - # push commit back to bare repo - # will raise CommandFailed when push fails - repo.git.push({ raise: true, timeout: true }, :origin, ref) - - # everything worked - true - end - rescue Grit::Git::CommandFailed => ex - Gitlab::GitLogger.error(ex.message) - false - end - end - end -end diff --git a/lib/gitlab/satellite/files/file_action.rb b/lib/gitlab/satellite/files/file_action.rb deleted file mode 100644 index 0b441a59e3..0000000000 --- a/lib/gitlab/satellite/files/file_action.rb +++ /dev/null @@ -1,17 +0,0 @@ -module Gitlab - module Satellite - class FileAction < Action - attr_accessor :file_path, :ref - - def initialize(user, project, ref, file_path) - super user, project - @file_path = file_path - @ref = ref - end - - def safe_path?(path) - File.absolute_path(path) == path - end - end - end -end diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index 87ae8c96cc..15f547e128 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -49,10 +49,6 @@ describe API::API, api: true do } it "should create a new file in project repo" do - Gitlab::Satellite::NewFileAction.any_instance.stub( - commit!: true, - ) - post api("/projects/#{project.id}/repository/files", user), valid_params expect(response.status).to eq(201) expect(json_response['file_path']).to eq('newfile.rb') @@ -84,10 +80,6 @@ describe API::API, api: true do } it "should update existing file in project repo" do - Gitlab::Satellite::EditFileAction.any_instance.stub( - commit!: true, - ) - put api("/projects/#{project.id}/repository/files", user), valid_params expect(response.status).to eq(200) expect(json_response['file_path']).to eq(file_path) @@ -109,10 +101,6 @@ describe API::API, api: true do } it "should delete existing file in project repo" do - Gitlab::Satellite::DeleteFileAction.any_instance.stub( - commit!: true, - ) - delete api("/projects/#{project.id}/repository/files", user), valid_params expect(response.status).to eq(200) expect(json_response['file_path']).to eq(file_path) @@ -124,8 +112,8 @@ describe API::API, api: true do end it "should return a 400 if satellite fails to create file" do - Gitlab::Satellite::DeleteFileAction.any_instance.stub( - commit!: false, + Repository.any_instance.stub( + remove_file: false, ) delete api("/projects/#{project.id}/repository/files", user), valid_params From fe78984f2045a79554ae52478d01d9102c6b6a77 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 2 Jun 2015 13:17:11 +0200 Subject: [PATCH 207/255] Actually ignore references in code blocks etc. --- app/helpers/gitlab_markdown_helper.rb | 27 +++++----- lib/gitlab/reference_extractor.rb | 59 ++++++++------------- lib/redcarpet/render/gitlab_html.rb | 2 + spec/lib/gitlab/reference_extractor_spec.rb | 20 +++++++ 4 files changed, 55 insertions(+), 53 deletions(-) diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index 3c207619ad..2777944fc9 100644 --- a/app/helpers/gitlab_markdown_helper.rb +++ b/app/helpers/gitlab_markdown_helper.rb @@ -41,29 +41,26 @@ module GitlabMarkdownHelper fragment.to_html.html_safe end + MARKDOWN_OPTIONS = { + no_intra_emphasis: true, + tables: true, + fenced_code_blocks: true, + strikethrough: true, + lax_spacing: true, + space_after_headers: true, + superscript: true, + footnotes: true + }.freeze + def markdown(text, options={}) unless @markdown && options == @options @options = options - options.merge!( - # Handled further down the line by Gitlab::Markdown::SanitizationFilter - escape_html: false - ) - # see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch rend = Redcarpet::Render::GitlabHTML.new(self, user_color_scheme_class, options) # see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use - @markdown = Redcarpet::Markdown.new(rend, - no_intra_emphasis: true, - tables: true, - fenced_code_blocks: true, - strikethrough: true, - lax_spacing: true, - space_after_headers: true, - superscript: true, - footnotes: true - ) + @markdown = Redcarpet::Markdown.new(rend, MARKDOWN_OPTIONS) end @markdown.render(text).html_safe diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index e35f848fa6..80b8ab8cbc 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -1,7 +1,7 @@ module Gitlab # Extract possible GFM references from an arbitrary String for further processing. class ReferenceExtractor - attr_accessor :project, :current_user, :references + attr_accessor :project, :current_user def initialize(project, current_user = nil) @project = project @@ -9,48 +9,31 @@ module Gitlab end def analyze(text) - @_text = text.dup + references.clear + @text = markdown.render(text.dup) end - def users - result = pipeline_result(:user) - result.uniq - end - - def labels - result = pipeline_result(:label) - result.uniq - end - - def issues - # TODO (rspeicher): What about external issues? - - result = pipeline_result(:issue) - result.uniq - end - - def merge_requests - result = pipeline_result(:merge_request) - result.uniq - end - - def snippets - result = pipeline_result(:snippet) - result.uniq - end - - def commits - result = pipeline_result(:commit) - result.uniq - end - - def commit_ranges - result = pipeline_result(:commit_range) - result.uniq + %i(user label issue merge_request snippet commit commit_range).each do |type| + define_method("#{type}s") do + references[type] + end end private + def markdown + @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML, GitlabMarkdownHelper::MARKDOWN_OPTIONS) + end + + def references + @references ||= Hash.new do |references, type| + type = type.to_sym + return references[type] if references.has_key?(type) + + references[type] = pipeline_result(type).uniq + end + end + # Instantiate and call HTML::Pipeline with a single reference filter type, # returning the result # @@ -69,7 +52,7 @@ module Gitlab } pipeline = HTML::Pipeline.new([filter], context) - result = pipeline.call(@_text) + result = pipeline.call(@text) result[:references][filter_type] end diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb index 7dcecc2ecf..133798852e 100644 --- a/lib/redcarpet/render/gitlab_html.rb +++ b/lib/redcarpet/render/gitlab_html.rb @@ -10,6 +10,8 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML @options = options.dup @options.reverse_merge!( + # Handled further down the line by Gitlab::Markdown::SanitizationFilter + escape_html: false project: @template.instance_variable_get("@project") ) diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index c14f4ac6bf..951e738cb6 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -16,6 +16,26 @@ describe Gitlab::ReferenceExtractor do expect(subject.users).to eq([@u_foo, @u_bar, @u_offteam]) end + it 'ignores user mentions inside specific elements' do + @u_foo = create(:user, username: 'foo') + @u_bar = create(:user, username: 'bar') + @u_offteam = create(:user, username: 'offteam') + + project.team << [@u_foo, :reporter] + project.team << [@u_bar, :guest] + + subject.analyze(%Q{ + Inline code: `@foo` + + Code block: + + ``` + @bar + ``` + }) + expect(subject.users).to eq([]) + end + it 'accesses valid issue objects' do @i0 = create(:issue, project: project) @i1 = create(:issue, project: project) From 94919c7ef6cf5786d380ae65623de0697eff9188 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 2 Jun 2015 13:17:21 +0200 Subject: [PATCH 208/255] Ignore references in blockquotes. --- lib/gitlab/markdown/reference_filter.rb | 14 ++++++++++---- lib/gitlab/reference_extractor.rb | 3 ++- spec/lib/gitlab/reference_extractor_spec.rb | 4 ++++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb index be4d26af0f..a84bacd3d4 100644 --- a/lib/gitlab/markdown/reference_filter.rb +++ b/lib/gitlab/markdown/reference_filter.rb @@ -25,12 +25,18 @@ module Gitlab ERB::Util.html_escape_once(html) end - # Don't look for references in text nodes that are children of these - # elements. - IGNORE_PARENTS = %w(pre code a style).to_set + def ignore_parents + @ignore_parents ||= begin + # Don't look for references in text nodes that are children of these + # elements. + parents = %w(pre code a style) + parents << 'blockquote' if context[:ignore_blockquotes] + parents.to_set + end + end def ignored_ancestry?(node) - has_ancestor?(node, IGNORE_PARENTS) + has_ancestor?(node, ignore_parents) end def project diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 80b8ab8cbc..e836b05ff2 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -48,7 +48,8 @@ module Gitlab project: project, current_user: current_user, # We don't actually care about the links generated - only_path: true + only_path: true, + ignore_blockquotes: true } pipeline = HTML::Pipeline.new([filter], context) diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index 951e738cb6..f921dd9cc0 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -32,6 +32,10 @@ describe Gitlab::ReferenceExtractor do ``` @bar ``` + + Quote: + + > @offteam }) expect(subject.users).to eq([]) end From 1c328fa4d7c8c2c1e8717a6f35c5ae21272846e8 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Tue, 2 Jun 2015 13:20:09 +0200 Subject: [PATCH 209/255] Improve hover color Signed-off-by: Dmitriy Zaporozhets --- app/assets/stylesheets/base/variables.scss | 2 +- app/assets/stylesheets/generic/lists.scss | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss index c44fa06fc5..3d7868fb7d 100644 --- a/app/assets/stylesheets/base/variables.scss +++ b/app/assets/stylesheets/base/variables.scss @@ -1,5 +1,5 @@ $style_color: #474D57; -$hover: #FFF3EB; +$hover: #FFFAF1; $gl-text-color: #222222; $gl-link-color: #446e9b; $nprogress-color: #c0392b; diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/generic/lists.scss index 08bf6e943d..c502d953c7 100644 --- a/app/assets/stylesheets/generic/lists.scss +++ b/app/assets/stylesheets/generic/lists.scss @@ -39,7 +39,6 @@ &:hover { background: $hover; - border-bottom: 1px solid darken($hover, 10%); } &:last-child { From 1f908dc48176d4f6f5e5d9c6709b137288ce2548 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 2 Jun 2015 13:21:34 +0200 Subject: [PATCH 210/255] Fix typo. --- lib/redcarpet/render/gitlab_html.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb index 133798852e..2f7aff03c2 100644 --- a/lib/redcarpet/render/gitlab_html.rb +++ b/lib/redcarpet/render/gitlab_html.rb @@ -11,7 +11,7 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML @options.reverse_merge!( # Handled further down the line by Gitlab::Markdown::SanitizationFilter - escape_html: false + escape_html: false, project: @template.instance_variable_get("@project") ) From 156c43c0dcdaad0eb3a351dfb8b62e600b7d9a08 Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 2 Jun 2015 13:23:22 +0200 Subject: [PATCH 211/255] Add changelog entry. --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 870ab59afa..0d65bb345e 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 7.12.0 (unreleased) + - Don't notify users mentioned in code blocks or blockquotes. - Disable changing of the source branch in merge request update API (Stan Hu) - Shorten merge request WIP text. - Add option to disallow users from registering any application to use GitLab as an OAuth provider From a916936f3feeda0a6d58fef2c06c51f95f10c45a Mon Sep 17 00:00:00 2001 From: Douwe Maan Date: Tue, 2 Jun 2015 15:00:51 +0200 Subject: [PATCH 212/255] Fix spec. --- spec/support/mentionable_shared_examples.rb | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index ede62e8f37..d29c8a55c8 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -107,17 +107,26 @@ shared_examples 'an editable mentionable' do it 'creates new cross-reference notes when the mentionable text is edited' do subject.save - new_text = <<-MSG + new_text = <<-MSG.strip_heredoc These references already existed: - Issue: #{mentioned_issue.to_reference} - Commit: #{mentioned_commit.to_reference} + + Issue: #{mentioned_issue.to_reference} + + Commit: #{mentioned_commit.to_reference} + + --- This cross-project reference already existed: - Issue: #{ext_issue.to_reference(project)} + + Issue: #{ext_issue.to_reference(project)} + + --- These two references are introduced in an edit: - Issue: #{new_issues[0].to_reference} - Cross: #{new_issues[1].to_reference(project)} + + Issue: #{new_issues[0].to_reference} + + Cross: #{new_issues[1].to_reference(project)} MSG # These three objects were already referenced, and should not receive new From b931c11e1b7acef5044e9c6a44145dd967196b99 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 21 May 2015 15:15:31 +0300 Subject: [PATCH 213/255] GitLab CI service sends gitlab-ci.yml file --- CHANGELOG | 1 + app/models/project_services/gitlab_ci_service.rb | 14 ++++++++++++++ .../project_services/gitlab_ci_service_spec.rb | 15 +++++++++++++++ 3 files changed, 30 insertions(+) diff --git a/CHANGELOG b/CHANGELOG index 870ab59afa..dbc54021d4 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -39,6 +39,7 @@ v 7.12.0 (unreleased) - Allow to configure a URL to show after sign out - Add an option to automatically sign-in with an Omniauth provider - Better performance for web editor (switched from satellites to rugged) + - GitLab CI service sends .gitlab-ci.yaml in each push call v 7.11.4 - Fix missing bullets when creating lists diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index 949a4d7111..a935475468 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -40,6 +40,12 @@ class GitlabCiService < CiService def execute(data) return unless supported_events.include?(data[:object_kind]) + ci_yaml_file = ci_yaml_file(data) + + if ci_yaml_file + data.merge!(ci_yaml_file: ci_yaml_file) + end + service_hook.execute(data) end @@ -123,6 +129,14 @@ class GitlabCiService < CiService private + def ci_yaml_file(data) + ref = data[:checkout_sha] + repo = project.repository + commit = repo.commit(ref) + blob = Gitlab::Git::Blob.find(repo, commit.id, ".gitlab-ci.yml") + blob && blob.data + end + def fork_registration_path project_url.sub(/projects\/\d*/, "#{API_PREFIX}/forks") end diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb index e5bf912531..ebd8b545aa 100644 --- a/spec/models/project_services/gitlab_ci_service_spec.rb +++ b/spec/models/project_services/gitlab_ci_service_spec.rb @@ -48,6 +48,21 @@ describe GitlabCiService do it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://ci.gitlab.org/projects/2/refs/master/commits/2ab7834c")} it { expect(@service.build_page("issue#2", 'master')).to eq("http://ci.gitlab.org/projects/2/refs/master/commits/issue%232")} end + + describe "execute" do + let(:user) { create(:user, username: 'username') } + let(:project) { create(:project, name: 'project') } + let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } + + it "calls ci_yaml_file" do + service_hook = double + service_hook.should_receive(:execute) + @service.should_receive(:service_hook).and_return(service_hook) + @service.should_receive(:ci_yaml_file).with(push_sample_data) + + @service.execute(push_sample_data) + end + end end describe "Fork registration" do From 2a5c963b7cd2dc1cf1e6b4d1a291c37d313b5813 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 28 May 2015 17:56:52 -0400 Subject: [PATCH 214/255] Render Group and Project descriptions with our Markdown pipeline --- app/assets/stylesheets/pages/projects.scss | 10 ++++++---- app/views/groups/show.html.haml | 2 +- app/views/projects/_home_panel.html.haml | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 12489ccc2d..b93ea0f020 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -48,14 +48,16 @@ } .project-home-desc { + color: $gray; + float: left; font-size: 16px; line-height: 1.3; margin-right: 250px; - } - .project-home-desc { - float: left; - color: $gray; + // Render Markdown-generated HTML inline for this block + p { + display: inline; + } } } diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 1678311141..f42007da07 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -11,7 +11,7 @@ @#{@group.path} - if @group.description.present? .description - = escaped_autolink(@group.description) + = markdown(@group.description) %hr = render 'shared/show_aside' diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index f9cdda4a3b..05f44acd3c 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -5,7 +5,7 @@ .project-home-row.project-home-row-top .project-home-desc - if @project.description.present? - = escaped_autolink(@project.description) + = markdown(@project.description) - if can?(current_user, :admin_project, @project) – = link_to 'Edit', edit_namespace_project_path From 1a52f19c456dfa307dd7fa0e5adbaa2ed1a68889 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 28 May 2015 17:57:23 -0400 Subject: [PATCH 215/255] Remove now-unused `escaped_autolink` helper and rails_autolink gem --- Gemfile | 3 --- Gemfile.lock | 3 --- app/helpers/application_helper.rb | 4 ---- 3 files changed, 10 deletions(-) diff --git a/Gemfile b/Gemfile index 78af7f5db6..94e2129f3c 100644 --- a/Gemfile +++ b/Gemfile @@ -10,9 +10,6 @@ end gem "rails", "~> 4.1.0" -# Make links from text -gem 'rails_autolink', '~> 1.1' - # Default values for AR models gem "default_value_for", "~> 3.0.0" diff --git a/Gemfile.lock b/Gemfile.lock index bbc5639c84..80ae41dc8f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -448,8 +448,6 @@ GEM sprockets-rails (~> 2.0) rails-observers (0.1.2) activemodel (~> 4.0) - rails_autolink (1.1.6) - rails (> 3.1) railties (4.1.9) actionpack (= 4.1.9) activesupport (= 4.1.9) @@ -779,7 +777,6 @@ DEPENDENCIES rack-mini-profiler rack-oauth2 (~> 1.0.5) rails (~> 4.1.0) - rails_autolink (~> 1.1) raphael-rails (~> 2.1.2) rb-fsevent rb-inotify diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index 89dcdf5779..a539ec49f7 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -279,10 +279,6 @@ module ApplicationHelper html_options end - def escaped_autolink(text) - auto_link ERB::Util.html_escape(text), link: :urls - end - def promo_host 'about.gitlab.com' end From 023dd2907b4afa0bae5f8482cae75e1edd6954a8 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 29 May 2015 19:01:12 -0400 Subject: [PATCH 216/255] Add a `pipeline` context option for SanitizationFilter When this option is `:description`, we use a more restrictive whitelist. This is used for Project and Group description fields. --- app/views/groups/show.html.haml | 2 +- app/views/projects/_home_panel.html.haml | 2 +- lib/gitlab/markdown.rb | 3 + lib/gitlab/markdown/sanitization_filter.rb | 64 ++++++++++++------- .../markdown/sanitization_filter_spec.rb | 14 ++++ 5 files changed, 59 insertions(+), 26 deletions(-) diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index f42007da07..0687840af3 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -11,7 +11,7 @@ @#{@group.path} - if @group.description.present? .description - = markdown(@group.description) + = markdown(@group.description, pipeline: :description) %hr = render 'shared/show_aside' diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 05f44acd3c..076afb11a9 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -5,7 +5,7 @@ .project-home-row.project-home-row-top .project-home-desc - if @project.description.present? - = markdown(@project.description) + = markdown(@project.description, pipeline: :description) - if can?(current_user, :admin_project, @project) – = link_to 'Edit', edit_namespace_project_path diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index 5db1566f55..fa9c0975bb 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -57,6 +57,9 @@ module Gitlab pipeline = HTML::Pipeline.new(filters) context = { + # SanitizationFilter + pipeline: options[:pipeline], + # EmojiFilter asset_root: Gitlab.config.gitlab.url, asset_host: Gitlab::Application.config.asset_host, diff --git a/lib/gitlab/markdown/sanitization_filter.rb b/lib/gitlab/markdown/sanitization_filter.rb index 88781fea0c..fc29d09081 100644 --- a/lib/gitlab/markdown/sanitization_filter.rb +++ b/lib/gitlab/markdown/sanitization_filter.rb @@ -8,33 +8,53 @@ module Gitlab # Extends HTML::Pipeline::SanitizationFilter with a custom whitelist. class SanitizationFilter < HTML::Pipeline::SanitizationFilter def whitelist - whitelist = super - - # Only push these customizations once - unless customized?(whitelist[:transformers]) - # Allow code highlighting - whitelist[:attributes]['pre'] = %w(class) - whitelist[:attributes]['span'] = %w(class) - - # Allow table alignment - whitelist[:attributes]['th'] = %w(style) - whitelist[:attributes]['td'] = %w(style) - - # Allow span elements - whitelist[:elements].push('span') - - # Remove `rel` attribute from `a` elements - whitelist[:transformers].push(remove_rel) - - # Remove `class` attribute from non-highlight spans - whitelist[:transformers].push(clean_spans) + # Descriptions are more heavily sanitized, allowing only a few elements. + # See http://git.io/vkuAN + if pipeline == :description + whitelist = LIMITED + else + whitelist = super end + customize_whitelist(whitelist) + whitelist end private + def pipeline + context[:pipeline] || :default + end + + def customized?(transformers) + transformers.last.source_location[0] == __FILE__ + end + + def customize_whitelist(whitelist) + # Only push these customizations once + return if customized?(whitelist[:transformers]) + + # Allow code highlighting + whitelist[:attributes]['pre'] = %w(class) + whitelist[:attributes]['span'] = %w(class) + + # Allow table alignment + whitelist[:attributes]['th'] = %w(style) + whitelist[:attributes]['td'] = %w(style) + + # Allow span elements + whitelist[:elements].push('span') + + # Remove `rel` attribute from `a` elements + whitelist[:transformers].push(remove_rel) + + # Remove `class` attribute from non-highlight spans + whitelist[:transformers].push(clean_spans) + + whitelist + end + def remove_rel lambda do |env| if env[:node_name] == 'a' @@ -53,10 +73,6 @@ module Gitlab end end end - - def customized?(transformers) - transformers.last.source_location[0] == __FILE__ - end end end end diff --git a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb index 4a1aa76614..80f3d2f263 100644 --- a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb +++ b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb @@ -42,6 +42,13 @@ module Gitlab::Markdown end describe 'custom whitelist' do + it 'customizes the whitelist only once' do + instance = described_class.new('Foo') + 3.times { instance.whitelist } + + expect(instance.whitelist[:transformers].size).to eq 4 + end + it 'allows syntax highlighting' do exp = act = %q{
def
} expect(filter(act).to_html).to eq exp @@ -87,5 +94,12 @@ module Gitlab::Markdown expect(doc.at_css('a')['href']).to be_nil end end + + context 'when pipeline is :description' do + it 'uses a stricter whitelist' do + doc = filter('

My Project

', pipeline: :description) + expect(doc.to_html.strip).to eq 'My Project' + end + end end end From 442a0663da437abcdec7fbd86967b6d8980d4090 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 29 May 2015 19:02:11 -0400 Subject: [PATCH 217/255] Add feature specs for Project and Group description rendering --- spec/features/groups_spec.rb | 36 ++++++++++++++++++++++ spec/features/markdown_spec.rb | 2 ++ spec/features/projects_spec.rb | 56 ++++++++++++++++++++++++++-------- 3 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 spec/features/groups_spec.rb diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb new file mode 100644 index 0000000000..edc1c63a0a --- /dev/null +++ b/spec/features/groups_spec.rb @@ -0,0 +1,36 @@ +require 'spec_helper' + +feature 'Group' do + describe 'description' do + let(:group) { create(:group) } + let(:path) { group_path(group) } + + before do + login_as(:admin) + end + + it 'parses Markdown' do + group.update_attribute(:description, 'This is **my** group') + visit path + expect(page).to have_css('.description > p > strong') + end + + it 'passes through html-pipeline' do + group.update_attribute(:description, 'This group is the :poop:') + visit path + expect(page).to have_css('.description > p > img') + end + + it 'sanitizes unwanted tags' do + group.update_attribute(:description, '# Group Description') + visit path + expect(page).not_to have_css('.description h1') + end + + it 'permits `rel` attribute on links' do + group.update_attribute(:description, 'https://google.com/') + visit path + expect(page).to have_css('.description a[rel]') + end + end +end diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb index ee1b3bf749..902968cebc 100644 --- a/spec/features/markdown_spec.rb +++ b/spec/features/markdown_spec.rb @@ -18,11 +18,13 @@ require 'erb' # -> `gfm_with_options` helper # -> HTML::Pipeline # -> Sanitize +# -> RelativeLink # -> Emoji # -> Table of Contents # -> Autolinks # -> Rinku (http, https, ftp) # -> Other schemes +# -> ExternalLink # -> References # -> TaskList # -> `html_safe` diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index cae11be7cd..56523f6e1a 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -1,24 +1,56 @@ require 'spec_helper' -describe "Projects", feature: true, js: true do - before { login_as :user } +feature 'Project' do + describe 'description' do + let(:project) { create(:project) } + let(:path) { namespace_project_path(project.namespace, project) } - describe "DELETE /projects/:id" do before do - @project = create(:project, namespace: @user.namespace) - @project.team << [@user, :master] - visit edit_namespace_project_path(@project.namespace, @project) + login_as(:admin) end - it "should remove project" do + it 'parses Markdown' do + project.update_attribute(:description, 'This is **my** project') + visit path + expect(page).to have_css('.project-home-desc > p > strong') + end + + it 'passes through html-pipeline' do + project.update_attribute(:description, 'This project is the :poop:') + visit path + expect(page).to have_css('.project-home-desc > p > img') + end + + it 'sanitizes unwanted tags' do + project.update_attribute(:description, '# Project Description') + visit path + expect(page).not_to have_css('.project-home-desc h1') + end + + it 'permits `rel` attribute on links' do + project.update_attribute(:description, 'https://google.com/') + visit path + expect(page).to have_css('.project-home-desc a[rel]') + end + end + + describe 'removal', js: true do + let(:user) { create(:user) } + let(:project) { create(:project, namespace: user.namespace) } + + before do + login_with(user) + project.team << [user, :master] + visit edit_namespace_project_path(project.namespace, project) + end + + it 'should remove project' do expect { remove_project }.to change {Project.count}.by(-1) end it 'should delete the project from disk' do - expect(GitlabShellWorker).to( - receive(:perform_async).with(:remove_repository, - /#{@project.path_with_namespace}/) - ).twice + expect(GitlabShellWorker).to receive(:perform_async). + with(:remove_repository, /#{project.path_with_namespace}/).twice remove_project end @@ -26,7 +58,7 @@ describe "Projects", feature: true, js: true do def remove_project click_link "Remove project" - fill_in 'confirm_name_input', with: @project.path + fill_in 'confirm_name_input', with: project.path click_button 'Confirm' end end From 79c4e3899fa7697afdefb13d64c4add08ca84aac Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 2 Jun 2015 13:27:53 -0400 Subject: [PATCH 218/255] Rename ReferenceFilterSpecHelper to FilterSpecHelper And make it more generalized for all filter specs. --- .../gitlab/markdown/autolink_filter_spec.rb | 6 +- .../commit_range_reference_filter_spec.rb | 2 +- .../markdown/commit_reference_filter_spec.rb | 2 +- spec/lib/gitlab/markdown/emoji_filter_spec.rb | 4 +- .../external_issue_reference_filter_spec.rb | 2 +- .../markdown/external_link_filter_spec.rb | 4 +- .../markdown/issue_reference_filter_spec.rb | 2 +- .../markdown/label_reference_filter_spec.rb | 2 +- .../merge_request_reference_filter_spec.rb | 2 +- .../markdown/sanitization_filter_spec.rb | 4 +- .../markdown/snippet_reference_filter_spec.rb | 2 +- .../markdown/table_of_contents_filter_spec.rb | 4 +- .../gitlab/markdown/task_list_filter_spec.rb | 4 +- .../markdown/user_reference_filter_spec.rb | 2 +- ...r_spec_helper.rb => filter_spec_helper.rb} | 75 ++++++++++--------- 15 files changed, 56 insertions(+), 61 deletions(-) rename spec/support/{reference_filter_spec_helper.rb => filter_spec_helper.rb} (75%) diff --git a/spec/lib/gitlab/markdown/autolink_filter_spec.rb b/spec/lib/gitlab/markdown/autolink_filter_spec.rb index 0bbdc11a97..a14cb2da08 100644 --- a/spec/lib/gitlab/markdown/autolink_filter_spec.rb +++ b/spec/lib/gitlab/markdown/autolink_filter_spec.rb @@ -2,11 +2,9 @@ require 'spec_helper' module Gitlab::Markdown describe AutolinkFilter do - let(:link) { 'http://about.gitlab.com/' } + include FilterSpecHelper - def filter(html, options = {}) - described_class.call(html, options) - end + let(:link) { 'http://about.gitlab.com/' } it 'does nothing when :autolink is false' do exp = act = link diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb index d3695ee46d..e8391cc7ac 100644 --- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' module Gitlab::Markdown describe CommitRangeReferenceFilter do - include ReferenceFilterSpecHelper + include FilterSpecHelper let(:project) { create(:project) } let(:commit1) { project.commit } diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb index a0d2cd7e22..a10d43c9a0 100644 --- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' module Gitlab::Markdown describe CommitReferenceFilter do - include ReferenceFilterSpecHelper + include FilterSpecHelper let(:project) { create(:project) } let(:commit) { project.commit } diff --git a/spec/lib/gitlab/markdown/emoji_filter_spec.rb b/spec/lib/gitlab/markdown/emoji_filter_spec.rb index 18d55c4818..11efd9bb4c 100644 --- a/spec/lib/gitlab/markdown/emoji_filter_spec.rb +++ b/spec/lib/gitlab/markdown/emoji_filter_spec.rb @@ -2,9 +2,7 @@ require 'spec_helper' module Gitlab::Markdown describe EmojiFilter do - def filter(html, contexts = {}) - described_class.call(html, contexts) - end + include FilterSpecHelper before do ActionController::Base.asset_host = 'https://foo.com' diff --git a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb index bf9409589f..f16095bc2b 100644 --- a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' module Gitlab::Markdown describe ExternalIssueReferenceFilter do - include ReferenceFilterSpecHelper + include FilterSpecHelper def helper IssuesHelper diff --git a/spec/lib/gitlab/markdown/external_link_filter_spec.rb b/spec/lib/gitlab/markdown/external_link_filter_spec.rb index c2ff4f80a4..a040b34577 100644 --- a/spec/lib/gitlab/markdown/external_link_filter_spec.rb +++ b/spec/lib/gitlab/markdown/external_link_filter_spec.rb @@ -2,9 +2,7 @@ require 'spec_helper' module Gitlab::Markdown describe ExternalLinkFilter do - def filter(html, options = {}) - described_class.call(html, options) - end + include FilterSpecHelper it 'ignores elements without an href attribute' do exp = act = %q(Ignore Me) diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb index a838d7570c..fa43d33794 100644 --- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' module Gitlab::Markdown describe IssueReferenceFilter do - include ReferenceFilterSpecHelper + include FilterSpecHelper def helper IssuesHelper diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb index 41987f57bc..cf3337b1ba 100644 --- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb @@ -3,7 +3,7 @@ require 'html/pipeline' module Gitlab::Markdown describe LabelReferenceFilter do - include ReferenceFilterSpecHelper + include FilterSpecHelper let(:project) { create(:empty_project) } let(:label) { create(:label, project: project) } diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb index 6aeb109360..5945302a2d 100644 --- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' module Gitlab::Markdown describe MergeRequestReferenceFilter do - include ReferenceFilterSpecHelper + include FilterSpecHelper let(:project) { create(:project) } let(:merge) { create(:merge_request, source_project: project) } diff --git a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb index 80f3d2f263..8627cb288a 100644 --- a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb +++ b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb @@ -2,9 +2,7 @@ require 'spec_helper' module Gitlab::Markdown describe SanitizationFilter do - def filter(html, options = {}) - described_class.call(html, options) - end + include FilterSpecHelper describe 'default whitelist' do it 'sanitizes tags that are not whitelisted' do diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb index 07ece66e90..38619a3c07 100644 --- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' module Gitlab::Markdown describe SnippetReferenceFilter do - include ReferenceFilterSpecHelper + include FilterSpecHelper let(:project) { create(:empty_project) } let(:snippet) { create(:project_snippet, project: project) } diff --git a/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb b/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb index f383a5850d..ddf583a72c 100644 --- a/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb +++ b/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb @@ -4,9 +4,7 @@ require 'spec_helper' module Gitlab::Markdown describe TableOfContentsFilter do - def filter(html, options = {}) - described_class.call(html, options) - end + include FilterSpecHelper def header(level, text) "#{text}\n" diff --git a/spec/lib/gitlab/markdown/task_list_filter_spec.rb b/spec/lib/gitlab/markdown/task_list_filter_spec.rb index 2a1e1cc512..94f39cc966 100644 --- a/spec/lib/gitlab/markdown/task_list_filter_spec.rb +++ b/spec/lib/gitlab/markdown/task_list_filter_spec.rb @@ -2,9 +2,7 @@ require 'spec_helper' module Gitlab::Markdown describe TaskListFilter do - def filter(html, options = {}) - described_class.call(html, options) - end + include FilterSpecHelper it 'does not apply `task-list` class to non-task lists' do exp = act = %(
  • Item
) diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb index 0ecbdee9b9..08e6941028 100644 --- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb +++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' module Gitlab::Markdown describe UserReferenceFilter do - include ReferenceFilterSpecHelper + include FilterSpecHelper let(:project) { create(:empty_project) } let(:user) { create(:user) } diff --git a/spec/support/reference_filter_spec_helper.rb b/spec/support/filter_spec_helper.rb similarity index 75% rename from spec/support/reference_filter_spec_helper.rb rename to spec/support/filter_spec_helper.rb index afbea55ab9..755964e9a3 100644 --- a/spec/support/reference_filter_spec_helper.rb +++ b/spec/support/filter_spec_helper.rb @@ -1,13 +1,38 @@ -# Common methods and setup for Gitlab::Markdown reference filter specs +# Helper methods for Gitlab::Markdown filter specs # # Must be included into specs manually -module ReferenceFilterSpecHelper +module FilterSpecHelper extend ActiveSupport::Concern - # Shortcut to Rails' auto-generated routes helpers, to avoid including the - # module - def urls - Rails.application.routes.url_helpers + # Perform `call` on the described class + # + # Automatically passes the current `project` value, if defined, to the context + # if none is provided. + # + # html - HTML String to pass to the filter's `call` method. + # contexts - Hash context for the filter. (default: {project: project}) + # + # Returns a Nokogiri::XML::DocumentFragment + def filter(html, contexts = {}) + if defined?(project) + contexts.reverse_merge!(project: project) + end + + described_class.call(html, contexts) + end + + # Run text through HTML::Pipeline with the current filter and return the + # result Hash + # + # body - String text to run through the pipeline + # contexts - Hash context for the filter. (default: {project: project}) + # + # Returns the Hash + def pipeline_result(body, contexts = {}) + contexts.reverse_merge!(project: project) + + pipeline = HTML::Pipeline.new([described_class], contexts) + pipeline.call(body) end # Modify a String reference to make it invalid @@ -30,41 +55,23 @@ module ReferenceFilterSpecHelper end end - # Perform `call` on the described class - # - # Automatically passes the current `project` value to the context if none is - # provided. - # - # html - String text to pass to the filter's `call` method. - # contexts - Hash context for the filter. (default: {project: project}) - # - # Returns the String text returned by the filter's `call` method. - def filter(html, contexts = {}) - contexts.reverse_merge!(project: project) - described_class.call(html, contexts) - end - - # Run text through HTML::Pipeline with the current filter and return the - # result Hash - # - # body - String text to run through the pipeline - # contexts - Hash context for the filter. (default: {project: project}) - # - # Returns the Hash of the pipeline result - def pipeline_result(body, contexts = {}) - contexts.reverse_merge!(project: project) - - pipeline = HTML::Pipeline.new([described_class], contexts) - pipeline.call(body) - end - + # Stub CrossProjectReference#user_can_reference_project? to return true for + # the current test def allow_cross_reference! allow_any_instance_of(described_class). to receive(:user_can_reference_project?).and_return(true) end + # Stub CrossProjectReference#user_can_reference_project? to return false for + # the current test def disallow_cross_reference! allow_any_instance_of(described_class). to receive(:user_can_reference_project?).and_return(false) end + + # Shortcut to Rails' auto-generated routes helpers, to avoid including the + # module + def urls + Rails.application.routes.url_helpers + end end From b49302f2fd7e2333ca40d27e1479e1843462a233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20L=C3=A4hdekorpi?= Date: Tue, 2 Jun 2015 20:28:52 +0300 Subject: [PATCH 219/255] Remove "(unreleased)" from v 7.11.0 --- CHANGELOG | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index dbc54021d4..3ce8358447 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -59,7 +59,7 @@ v 7.11.0 - Get editing comments to work in Chrome 43 again. - Allow special character in users bio. I.e.: I <3 GitLab -v 7.11.0 (unreleased) +v 7.11.0 - Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu) - Don't show duplicate deploy keys - Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger) From 9e7a9c63a59f4e673271b3600b735e3fa6702432 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 2 Jun 2015 13:41:12 -0400 Subject: [PATCH 220/255] Further limit the limited whitelist for project/group descriptions --- lib/gitlab/markdown/sanitization_filter.rb | 1 + .../markdown/sanitization_filter_spec.rb | 19 +++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/markdown/sanitization_filter.rb b/lib/gitlab/markdown/sanitization_filter.rb index fc29d09081..74b3a8d274 100644 --- a/lib/gitlab/markdown/sanitization_filter.rb +++ b/lib/gitlab/markdown/sanitization_filter.rb @@ -12,6 +12,7 @@ module Gitlab # See http://git.io/vkuAN if pipeline == :description whitelist = LIMITED + whitelist[:elements] -= %w(pre code img ol ul li) else whitelist = super end diff --git a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb index 8627cb288a..e50c82d0b3 100644 --- a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb +++ b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb @@ -95,8 +95,23 @@ module Gitlab::Markdown context 'when pipeline is :description' do it 'uses a stricter whitelist' do - doc = filter('

My Project

', pipeline: :description) - expect(doc.to_html.strip).to eq 'My Project' + doc = filter('

Description

', pipeline: :description) + expect(doc.to_html.strip).to eq 'Description' + end + + %w(pre code img ol ul li).each do |elem| + it "removes '#{elem}' elements" do + act = "<#{elem}>Description" + expect(filter(act, pipeline: :description).to_html.strip). + to eq 'Description' + end + end + + %w(b i strong em a ins del sup sub p).each do |elem| + it "still allows '#{elem}' elements" do + exp = act = "<#{elem}>Description" + expect(filter(act, pipeline: :description).to_html).to eq exp + end end end end From e860dedd1c9c64702ce7ca9e475cfe1c3242f17b Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 2 Jun 2015 15:05:44 -0400 Subject: [PATCH 221/255] Push event: Nest link in strong tag, not vice-versa Closes #1022 --- app/views/events/event/_push.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index 1da702be38..34a7c00dc4 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -4,8 +4,8 @@ - if event.rm_ref? %strong= event.ref_name - else - = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do - %strong= event.ref_name + %strong + = link_to event.ref_name, namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) at = link_to_project event.project From 42f36268629d2029e16b70f1b112e404e6439bd7 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 2 Jun 2015 18:39:20 -0400 Subject: [PATCH 222/255] Add 2FA docs [ci skip] --- doc/workflow/README.md | 3 +- doc/workflow/two_factor_authentication.md | 65 +++++++++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) create mode 100644 doc/workflow/two_factor_authentication.md diff --git a/doc/workflow/README.md b/doc/workflow/README.md index 0fca68f364..89005e5195 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -11,7 +11,8 @@ - [Migrating from SVN to GitLab](migrating_from_svn.md) - [Project importing from GitHub to GitLab](import_projects_from_github.md) - [Project importing from GitLab.com to your private GitLab instance](import_projects_from_gitlab_com.md) +- [Two-factor Authentication (2FA)](two_factor_authentication.md) - [Protected branches](protected_branches.md) - [Change your time zone](timezone.md) - [Keyboard shortcuts](shortcuts.md) -- [Web Editor](web_editor.md) \ No newline at end of file +- [Web Editor](web_editor.md) diff --git a/doc/workflow/two_factor_authentication.md b/doc/workflow/two_factor_authentication.md new file mode 100644 index 0000000000..81f51042bf --- /dev/null +++ b/doc/workflow/two_factor_authentication.md @@ -0,0 +1,65 @@ +# Two-factor Authentication (2FA) + +Two-factor Authentication (2FA) provides an additional level of security to your +GitLab account. Once enabled, in addition to supplying your username and +password to login, you'll be prompted for a code generated by an application on +your phone. + +By enabling 2FA, the only way someone other than you can log into your account +is to know your username and password *and* have access to your phone. + +## Enabling 2FA + +**In GitLab:** + +1. Log in to your GitLab account. +1. Go to your **Profile Settings**. +1. Go to **Acount**. +1. Click **Enable Two-factor Authentication**. + +TODO: Insert screenshot of 2FA page (with the "Can't scan the code?" text) + +**On your phone:** + +1. Install a compatible application. We recommend [Google Authenticator]. +1. In the application, add a new entry in one of two ways: + * Scan the code with your phone's camera to add the entry automatically. + * Enter the details provided to add the entry manually. + +**In GitLab:** + +1. Enter the six-digit pin number from the entry on your phone into the **Pin + code** field. +1. Click **Submit**. + +If the pin you entered was correct, you'll see a message indicating that +Two-factor Authentication has been enabled, and you'll be presented with a list +of recovery codes. + +## Recovery Codes + +Should you ever lose access to your phone, you can use one of the ten provided +backup codes to login to your account. We suggest copying or printing them for +storage in a safe place. **Each code can be used only once** to log in to your +account. + +If you lose the recovery codes or just want to generate new ones, you can do so +from the **Profile Settings** > **Acount** page where you first enabled 2FA. + +## Logging in with 2FA Enabled + +Logging in with 2FA enabled is only slightly different than a normal login. +Enter your username and password credentials as you normally would, and you'll +be presented with a second prompt for an authentication code. Enter the pin from +your phone's application or a recovery code to log in. + +TODO: Insert screenshot of 2FA login prompt? + +## Disabling 2FA + +1. Log in to your GitLab account. +1. Go to your **Profile Settings**. +1. Go to **Acount**. +1. Click **Disable Two-factor Authentication**. + +[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en From f76a3f3db767da92149545ea54115d5ff40d722e Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 21 May 2015 01:25:18 -0400 Subject: [PATCH 223/255] Add ZenMode javascript specs --- app/assets/javascripts/zen_mode.js.coffee | 12 +++-- app/assets/stylesheets/generic/zen.scss | 10 +++- spec/javascripts/fixtures/zen_mode.html.haml | 9 ++++ spec/javascripts/zen_mode_spec.js.coffee | 52 ++++++++++++++++++++ 4 files changed, 79 insertions(+), 4 deletions(-) create mode 100644 spec/javascripts/fixtures/zen_mode.html.haml create mode 100644 spec/javascripts/zen_mode_spec.js.coffee diff --git a/app/assets/javascripts/zen_mode.js.coffee b/app/assets/javascripts/zen_mode.js.coffee index dc6a84c6c5..8a0564a909 100644 --- a/app/assets/javascripts/zen_mode.js.coffee +++ b/app/assets/javascripts/zen_mode.js.coffee @@ -1,3 +1,7 @@ +#= require dropzone +#= require mousetrap +#= require mousetrap/pause + class @ZenMode constructor: -> @active_zen_area = null @@ -26,7 +30,7 @@ class @ZenMode @exitZenMode() $(document).on 'keydown', (e) => - if e.keyCode is $.ui.keyCode.ESCAPE + if e.keyCode is 27 # Esc @exitZenMode() e.preventDefault() @@ -42,7 +46,9 @@ class @ZenMode @active_checkbox.prop('checked', false) @active_zen_area = null @active_checkbox = null - window.location.hash = '' - window.scrollTo(window.pageXOffset, @scroll_position) + @restoreScroll(@scroll_position) # Enable dropzone when leaving ZEN mode Dropzone.forElement('.div-dropzone').enable() + + restoreScroll: (y) -> + window.scrollTo(window.pageXOffset, y) diff --git a/app/assets/stylesheets/generic/zen.scss b/app/assets/stylesheets/generic/zen.scss index 26afc21a6a..bcb8bbe313 100644 --- a/app/assets/stylesheets/generic/zen.scss +++ b/app/assets/stylesheets/generic/zen.scss @@ -1,7 +1,7 @@ .zennable { position: relative; - input { + .zen-toggle-comment { display: none; } @@ -26,10 +26,12 @@ } } + // Hide the Enter link when we're in Zen mode input:checked ~ .zen-backdrop .zen-enter-link { display: none; } + // Show the Leave link when we're in Zen mode input:checked ~ .zen-backdrop .zen-leave-link { display: block; position: absolute; @@ -62,6 +64,9 @@ } } + // Make the placeholder text in the standard textarea the same color as the + // background, effectively hiding it + .zen-backdrop textarea::-webkit-input-placeholder { color: white; } @@ -78,6 +83,9 @@ color: white; } + // Make the color of the placeholder text in the Zenned-out textarea darker, + // so it becomes visible + input:checked ~ .zen-backdrop textarea::-webkit-input-placeholder { color: #999; } diff --git a/spec/javascripts/fixtures/zen_mode.html.haml b/spec/javascripts/fixtures/zen_mode.html.haml new file mode 100644 index 0000000000..e867e4de2b --- /dev/null +++ b/spec/javascripts/fixtures/zen_mode.html.haml @@ -0,0 +1,9 @@ +.zennable + %input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' } + .zen-backdrop + %textarea#note_note.js-gfm-input.markdown-area{placeholder: 'Leave a comment'} + %a.zen-enter-link{tabindex: '-1'} + %i.fa.fa-expand + Edit in fullscreen + %a.zen-leave-link + %i.fa.fa-compress diff --git a/spec/javascripts/zen_mode_spec.js.coffee b/spec/javascripts/zen_mode_spec.js.coffee new file mode 100644 index 0000000000..1f4ea58ad4 --- /dev/null +++ b/spec/javascripts/zen_mode_spec.js.coffee @@ -0,0 +1,52 @@ +#= require zen_mode + +describe 'ZenMode', -> + fixture.preload('zen_mode.html') + + beforeEach -> + fixture.load('zen_mode.html') + + # Stub Dropzone.forElement(...).enable() + spyOn(Dropzone, 'forElement').and.callFake -> + enable: -> true + + @zen = new ZenMode() + + # Set this manually because we can't actually scroll the window + @zen.scroll_position = 456 + + # Ohmmmmmmm + enterZen = -> + $('.zen-toggle-comment').prop('checked', true).trigger('change') + + # Wh- what was that?! + exitZen = -> + $('.zen-toggle-comment').prop('checked', false).trigger('change') + + describe 'on enter', -> + it 'pauses Mousetrap', -> + spyOn(Mousetrap, 'pause') + enterZen() + expect(Mousetrap.pause).toHaveBeenCalled() + + describe 'in use', -> + beforeEach -> + enterZen() + + it 'exits on Escape', -> + $(document).trigger(jQuery.Event('keydown', {keyCode: 27})) + expect($('.zen-toggle-comment').prop('checked')).toBe(false) + + describe 'on exit', -> + beforeEach -> + enterZen() + + it 'unpauses Mousetrap', -> + spyOn(Mousetrap, 'unpause') + exitZen() + expect(Mousetrap.unpause).toHaveBeenCalled() + + it 'restores the scroll position', -> + spyOn(@zen, 'restoreScroll') + exitZen() + expect(@zen.restoreScroll).toHaveBeenCalledWith(456) From 2313d42b37977afc884a2a2fc7e91180e0acbaf3 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Sat, 23 May 2015 00:33:46 -0400 Subject: [PATCH 224/255] Bump jquery-rails version --- Gemfile | 26 +++++++++++++------------- Gemfile.lock | 4 ++-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/Gemfile b/Gemfile index 94e2129f3c..0ab0a45cdb 100644 --- a/Gemfile +++ b/Gemfile @@ -195,20 +195,20 @@ gem "uglifier" gem 'turbolinks', '~> 2.5.0' gem 'jquery-turbolinks' -gem 'select2-rails' -gem 'jquery-atwho-rails', '~> 1.0.0' -gem "jquery-rails" -gem "jquery-ui-rails" -gem "jquery-scrollto-rails" -gem "raphael-rails", "~> 2.1.2" -gem 'bootstrap-sass', '~> 3.0' -gem "font-awesome-rails", '~> 4.2' -gem "gitlab_emoji", "~> 0.1" -gem "gon", '~> 5.0.0' -gem 'nprogress-rails' -gem 'request_store' -gem "virtus" gem 'addressable' +gem 'bootstrap-sass', '~> 3.0' +gem 'font-awesome-rails', '~> 4.2' +gem 'gitlab_emoji', '~> 0.1' +gem 'gon', '~> 5.0.0' +gem 'jquery-atwho-rails', '~> 1.0.0' +gem 'jquery-rails', '3.1.2' +gem 'jquery-scrollto-rails' +gem 'jquery-ui-rails' +gem 'nprogress-rails' +gem 'raphael-rails', '~> 2.1.2' +gem 'request_store' +gem 'select2-rails' +gem 'virtus' group :development do gem 'brakeman', require: false diff --git a/Gemfile.lock b/Gemfile.lock index 80ae41dc8f..c9b8fc1f55 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -301,7 +301,7 @@ GEM ice_cube (0.11.1) ice_nine (0.10.0) jquery-atwho-rails (1.0.1) - jquery-rails (3.1.0) + jquery-rails (3.1.2) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) jquery-scrollto-rails (1.4.3) @@ -746,7 +746,7 @@ DEPENDENCIES html-pipeline (~> 1.11.0) httparty jquery-atwho-rails (~> 1.0.0) - jquery-rails + jquery-rails (= 3.1.2) jquery-scrollto-rails jquery-turbolinks jquery-ui-rails From 456095440425f91028f218748f366bf16e5c7d4b Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 26 May 2015 18:33:04 -0400 Subject: [PATCH 225/255] Move jQuery enable/disable extensions to extensions/jquery Removes redundant enableButton/disableButton extensions, and adds specs for the jQuery extensions. --- app/assets/javascripts/application.js.coffee | 11 ------ .../javascripts/extensions/jquery.js.coffee | 17 ++++++---- app/assets/javascripts/profile.js.coffee | 4 +-- app/views/projects/update.js.haml | 2 +- .../extensions/jquery_spec.js.coffee | 34 +++++++++++++++++++ spec/javascripts/spec_helper.coffee | 18 +++++----- 6 files changed, 56 insertions(+), 30 deletions(-) create mode 100644 spec/javascripts/extensions/jquery_spec.js.coffee diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 9fc313db9d..075bcddf8b 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -196,14 +196,3 @@ $ -> new ConfirmDangerModal(form, text) new Aside() - -(($) -> - # Disable an element and add the 'disabled' Bootstrap class - $.fn.extend disable: -> - $(@).attr('disabled', 'disabled').addClass('disabled') - - # Enable an element and remove the 'disabled' Bootstrap class - $.fn.extend enable: -> - $(@).removeAttr('disabled').removeClass('disabled') - -)(jQuery) diff --git a/app/assets/javascripts/extensions/jquery.js.coffee b/app/assets/javascripts/extensions/jquery.js.coffee index 40fb6cb9fc..2a7dae4a86 100644 --- a/app/assets/javascripts/extensions/jquery.js.coffee +++ b/app/assets/javascripts/extensions/jquery.js.coffee @@ -3,11 +3,14 @@ $.fn.showAndHide = -> delay(3000). fadeOut() -$.fn.enableButton = -> - $(@).removeAttr('disabled'). - removeClass('disabled') - -$.fn.disableButton = -> - $(@).attr('disabled', 'disabled'). - addClass('disabled') +# Disable an element and add the 'disabled' Bootstrap class +$.fn.extend disable: -> + $(@) + .attr('disabled', 'disabled') + .addClass('disabled') +# Enable an element and remove the 'disabled' Bootstrap class +$.fn.extend enable: -> + $(@) + .removeAttr('disabled') + .removeClass('disabled') diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index de356fbec7..40459a9a15 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -12,11 +12,11 @@ class @Profile $(this).find('.update-failed').hide() $('.update-username form').on 'ajax:complete', -> - $(this).find('.btn-save').enableButton() + $(this).find('.btn-save').enable() $(this).find('.loading-gif').hide() $('.update-notifications').on 'ajax:complete', -> - $(this).find('.btn-save').enableButton() + $(this).find('.btn-save').enable() $('.js-choose-user-avatar-button').bind "click", -> diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml index 4f3f4cab8d..7d9bd08385 100644 --- a/app/views/projects/update.js.haml +++ b/app/views/projects/update.js.haml @@ -6,4 +6,4 @@ $(".project-edit-errors").html("#{escape_javascript(render('errors'))}"); $('.save-project-loader').hide(); $('.project-edit-container').show(); - $('.project-edit-content .btn-save').enableButton(); + $('.project-edit-content .btn-save').enable(); diff --git a/spec/javascripts/extensions/jquery_spec.js.coffee b/spec/javascripts/extensions/jquery_spec.js.coffee new file mode 100644 index 0000000000..b10e16b7d0 --- /dev/null +++ b/spec/javascripts/extensions/jquery_spec.js.coffee @@ -0,0 +1,34 @@ +#= require extensions/jquery + +describe 'jQuery extensions', -> + describe 'disable', -> + beforeEach -> + fixture.set '' + + it 'adds the disabled attribute', -> + $input = $('input').first() + + $input.disable() + expect($input).toHaveAttr('disabled', 'disabled') + + it 'adds the disabled class', -> + $input = $('input').first() + + $input.disable() + expect($input).toHaveClass('disabled') + + describe 'enable', -> + beforeEach -> + fixture.set '' + + it 'removes the disabled attribute', -> + $input = $('input').first() + + $input.enable() + expect($input).not.toHaveAttr('disabled') + + it 'removes the disabled class', -> + $input = $('input').first() + + $input.enable() + expect($input).not.toHaveClass('disabled') diff --git a/spec/javascripts/spec_helper.coffee b/spec/javascripts/spec_helper.coffee index 892a539d96..47b41dd2c8 100644 --- a/spec/javascripts/spec_helper.coffee +++ b/spec/javascripts/spec_helper.coffee @@ -1,12 +1,3 @@ -# Teaspoon includes some support files, but you can use anything from your own -# support path too. - -# require support/jasmine-jquery-1.7.0 -# require support/jasmine-jquery-2.0.0 -# require support/jasmine-jquery-2.1.0 -# require support/sinon -# require support/your-support-file - # PhantomJS (Teaspoons default driver) doesn't have support for # Function.prototype.bind, which has caused confusion. Use this polyfill to # avoid the confusion. @@ -21,6 +12,15 @@ #= require bootstrap #= require underscore +# Teaspoon includes some support files, but you can use anything from your own +# support path too. + +# require support/jasmine-jquery-1.7.0 +# require support/jasmine-jquery-2.0.0 +#= require support/jasmine-jquery-2.1.0 +# require support/sinon +# require support/your-support-file + # Deferring execution # If you're using CommonJS, RequireJS or some other asynchronous library you can From 39d5a4878fea8aa631c6b614bdebb9be7b975e79 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 26 May 2015 18:43:14 -0400 Subject: [PATCH 226/255] Add JS specs for Array extensions --- spec/javascripts/extensions/array_spec.js.coffee | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 spec/javascripts/extensions/array_spec.js.coffee diff --git a/spec/javascripts/extensions/array_spec.js.coffee b/spec/javascripts/extensions/array_spec.js.coffee new file mode 100644 index 0000000000..4ceac61942 --- /dev/null +++ b/spec/javascripts/extensions/array_spec.js.coffee @@ -0,0 +1,12 @@ +#= require extensions/array + +describe 'Array extensions', -> + describe 'first', -> + it 'returns the first item', -> + arr = [0, 1, 2, 3, 4, 5] + expect(arr.first()).toBe(0) + + describe 'last', -> + it 'returns the last item', -> + arr = [0, 1, 2, 3, 4, 5] + expect(arr.last()).toBe(5) From 29626b08850bd5c868b4d71129d542f4fdc135ce Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Tue, 26 May 2015 18:43:02 -0400 Subject: [PATCH 227/255] Remove unused `showAndHide`, `simpleFormat`, and `linkify` functions Also removes redundant `unbind` call --- app/assets/javascripts/application.js.coffee | 10 ---------- app/assets/javascripts/extensions/jquery.js.coffee | 5 ----- 2 files changed, 15 deletions(-) diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 075bcddf8b..6a3f7386d5 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -49,8 +49,6 @@ window.slugify = (text) -> window.ajaxGet = (url) -> $.ajax({type: "GET", url: url, dataType: "script"}) -window.showAndHide = (selector) -> - window.split = (val) -> return val.split( /,\s*/ ) @@ -92,15 +90,7 @@ window.disableButtonIfAnyEmptyField = (form, form_selector, button_selector) -> window.sanitize = (str) -> return str.replace(/<(?:.|\n)*?>/gm, '') -window.linkify = (str) -> - exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig - return str.replace(exp,"$1") - -window.simpleFormat = (str) -> - linkify(sanitize(str).replace(/\n/g, '
')) - window.unbindEvents = -> - $(document).unbind('scroll') $(document).off('scroll') window.shiftWindow = -> diff --git a/app/assets/javascripts/extensions/jquery.js.coffee b/app/assets/javascripts/extensions/jquery.js.coffee index 2a7dae4a86..0a9db8eb5e 100644 --- a/app/assets/javascripts/extensions/jquery.js.coffee +++ b/app/assets/javascripts/extensions/jquery.js.coffee @@ -1,8 +1,3 @@ -$.fn.showAndHide = -> - $(@).show(). - delay(3000). - fadeOut() - # Disable an element and add the 'disabled' Bootstrap class $.fn.extend disable: -> $(@) From a38dd9bd3a47f6bbadf00bfa069a46747b0df791 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 10 May 2015 07:44:58 -0700 Subject: [PATCH 228/255] Add "Resend confirmation e-mail" link in profile settings Fixes https://github.com/gitlabhq/gitlabhq/issues/9274 --- CHANGELOG | 1 + app/views/profiles/show.html.haml | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 61e9084a39..bedface6f2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -17,6 +17,7 @@ v 7.12.0 (unreleased) - Remove Rack Attack monkey patches and bump to version 4.3.0 (Stan Hu) - Fix clone URL losing selection after a single click in Safari and Chrome (Stan Hu) - Fix git blame syntax highlighting when different commits break up lines (Stan Hu) + - Add "Resend confirmation e-mail" link in profile settings (Stan Hu) - Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka) - Disabled expansion of top/bottom blobs for new file diffs - Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka) diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 62fac46df2..6534afb0e8 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -37,8 +37,11 @@ = f.text_field :email, class: "form-control", required: true - if @user.unconfirmed_email.present? %span.help-block - Please click the link in the confirmation email before continuing, it was sent to - %strong #{@user.unconfirmed_email} + Please click the link in the confirmation email before continuing. It was sent to + = succeed "." do + %strong #{@user.unconfirmed_email} + %p + = link_to "Resend confirmation e-mail", user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post - else %span.help-block We also use email for avatar detection if no avatar is uploaded. From 5f7d6c7d746e4f71d3f48eedf473a74d1131907a Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 3 Jun 2015 11:26:57 +0200 Subject: [PATCH 229/255] Remove gitlab:env:check task. --- doc/install/installation.md | 6 ---- doc/raketasks/maintenance.md | 3 +- lib/tasks/gitlab/check.rake | 55 +----------------------------------- 3 files changed, 2 insertions(+), 62 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index 1db2b43829..be0dd37a48 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -241,12 +241,6 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Copy the example Rack attack config sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb - # Configure Git global settings for git user, useful when editing via web - # Edit user.email according to what is set in gitlab.yml - sudo -u git -H git config --global user.name "GitLab" - sudo -u git -H git config --global user.email "example@example.com" - sudo -u git -H git config --global core.autocrlf input - # Configure Redis connection settings sudo -u git -H cp config/resque.yml.example config/resque.yml diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md index 41a994f3f6..2aca91d537 100644 --- a/doc/raketasks/maintenance.md +++ b/doc/raketasks/maintenance.md @@ -47,7 +47,6 @@ Git: /usr/bin/git Runs the following rake tasks: -- `gitlab:env:check` - `gitlab:gitlab_shell:check` - `gitlab:sidekiq:check` - `gitlab:app:check` @@ -147,7 +146,7 @@ Do you want to continue (yes/no)? yes ## Clear redis cache -If for some reason the dashboard shows wrong information you might want to +If for some reason the dashboard shows wrong information you might want to clear Redis' cache. For Omnibus-packages: diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 1a6303b6c8..3f4f673791 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -1,7 +1,6 @@ namespace :gitlab do desc "GITLAB | Check the configuration of GitLab and its environment" - task check: %w{gitlab:env:check - gitlab:gitlab_shell:check + task check: %w{gitlab:gitlab_shell:check gitlab:sidekiq:check gitlab:ldap:check gitlab:app:check} @@ -298,58 +297,6 @@ namespace :gitlab do end end - - - namespace :env do - desc "GITLAB | Check the configuration of the environment" - task check: :environment do - warn_user_is_not_gitlab - start_checking "Environment" - - check_gitlab_git_config - - finished_checking "Environment" - end - - - # Checks - ######################## - - def check_gitlab_git_config - print "Git configured for #{gitlab_user} user? ... " - - options = { - "user.name" => "GitLab", - "user.email" => Gitlab.config.gitlab.email_from, - "core.autocrlf" => "input" - } - correct_options = options.map do |name, value| - run(%W(#{Gitlab.config.git.bin_path} config --global --get #{name})).try(:squish) == value - end - - if correct_options.all? - puts "yes".green - else - print "Trying to fix Git error automatically. ..." - if auto_fix_git_config(options) - puts "Success".green - else - puts "Failed".red - try_fixing_it( - sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global user.name \"#{options["user.name"]}\""), - sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global user.email \"#{options["user.email"]}\""), - sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global core.autocrlf \"#{options["core.autocrlf"]}\"") - ) - for_more_information( - see_installation_guide_section "GitLab" - ) - end - end - end - end - - - namespace :gitlab_shell do desc "GITLAB | Check the configuration of GitLab Shell" task check: :environment do From 97ff86e07cdfce1915d574772f80e21263ad43e6 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 3 Jun 2015 11:50:08 +0200 Subject: [PATCH 230/255] Move repository when project is removed Ths commit does next: * When we remove project we move repository to path+deleted.git * Then we schedule removal of path+deleted with sidekiq * If repository move failed we abort project removal This should help us with NFS issue when project get removed but repository stayed. The full explanation of problem is below: * rm -rf project.git * rm -rf removes project.git/objects/foo * NFS server renames foo to foo.nfsXXXX because some NFS client (think * Unicorn) still has the file open * rm -rf exits, but project.git/objects/foo.nfsXXX still exists * Unicorn closes the file, the NFS client closes the file (foo), and the * NFS server removes foo.nfsXXX * the directory project.git/objects/ still exists => problem So now we move repository and even if repository removal failed Repository directory is moved so no bugs with project removed but repository directory taken. User still able to create new project with same name. From administrator perspective you can easily find stalled repositories by searching `*+deleted.git` Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + app/controllers/projects_controller.rb | 17 +++---- app/services/projects/destroy_service.rb | 65 +++++++++++++++++++----- lib/gitlab/backend/shell.rb | 14 +++-- 4 files changed, 70 insertions(+), 27 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index dbc54021d4..381369f0de 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -40,6 +40,7 @@ v 7.12.0 (unreleased) - Add an option to automatically sign-in with an Omniauth provider - Better performance for web editor (switched from satellites to rugged) - GitLab CI service sends .gitlab-ci.yaml in each push call + - When remove project - move repository and schedule it removal v 7.11.4 - Fix missing bullets when creating lists diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index dc43035155..4ca5fc6545 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -97,18 +97,15 @@ class ProjectsController < ApplicationController return access_denied! unless can?(current_user, :remove_project, @project) ::Projects::DestroyService.new(@project, current_user, {}).execute + flash[:alert] = 'Project deleted.' - respond_to do |format| - format.html do - flash[:alert] = 'Project deleted.' - - if request.referer.include?('/admin') - redirect_to admin_namespaces_projects_path - else - redirect_to dashboard_path - end - end + if request.referer.include?('/admin') + redirect_to admin_namespaces_projects_path + else + redirect_to dashboard_path end + rescue Projects::DestroyService::DestroyError => ex + redirect_to edit_project_path(@project), alert: ex.message end def autocomplete_sources diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 7e1d753b02..53bf36b101 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -1,28 +1,67 @@ module Projects class DestroyService < BaseService + include Gitlab::ShellAdapter + + class DestroyError < StandardError; end + + DELETED_FLAG = '+deleted' + def execute return false unless can?(current_user, :remove_project, project) project.team.truncate project.repository.expire_cache unless project.empty_repo? - if project.destroy - GitlabShellWorker.perform_async( - :remove_repository, - project.path_with_namespace - ) + repo_path = project.path_with_namespace + wiki_path = repo_path + '.wiki' - GitlabShellWorker.perform_async( - :remove_repository, - project.path_with_namespace + ".wiki" - ) + Project.transaction do + project.destroy! - project.satellite.destroy + unless remove_repository(repo_path) + raise_error('Failed to remove project repository. Please try again or contact administrator') + end - log_info("Project \"#{project.name}\" was removed") - system_hook_service.execute_hooks_for(project, :destroy) - true + unless remove_repository(wiki_path) + raise_error('Failed to remove wiki repository. Please try again or contact administrator') + end end + + project.satellite.destroy + log_info("Project \"#{project.name}\" was removed") + system_hook_service.execute_hooks_for(project, :destroy) + true + end + + private + + def remove_repository(path) + unless gitlab_shell.exists?(path + '.git') + return true + end + + new_path = removal_path(path) + + if gitlab_shell.mv_repository(path, new_path) + log_info("Repository \"#{path}\" moved to \"#{new_path}\"") + GitlabShellWorker.perform_in(30.seconds, :remove_repository, new_path) + else + false + end + end + + def raise_error(message) + raise DestroyError.new(message) + end + + # Build a path for removing repositories + # We use `+` because its not allowed by GitLab so user can not create + # project with name cookies+119+deleted and capture someone stalled repository + # + # gitlab/cookies.git -> gitlab/cookies+119+deleted.git + # + def removal_path(path) + "#{path}+#{project.id}#{DELETED_FLAG}" end end end diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index 530f9d93de..172d4902ad 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -244,6 +244,16 @@ module Gitlab end end + # Check if such directory exists in repositories. + # + # Usage: + # exists?('gitlab') + # exists?('gitlab/cookies.git') + # + def exists?(dir_name) + File.exists?(full_path(dir_name)) + end + protected def gitlab_shell_path @@ -264,10 +274,6 @@ module Gitlab File.join(repos_path, dir_name) end - def exists?(dir_name) - File.exists?(full_path(dir_name)) - end - def gitlab_shell_projects_path File.join(gitlab_shell_path, 'bin', 'gitlab-projects') end From fd1723f0fcd0e8d5dbea3fd5ec18f271e3d6da0d Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 3 Jun 2015 13:08:17 +0200 Subject: [PATCH 231/255] Add tests for project destroy service Signed-off-by: Dmitriy Zaporozhets --- .../services/projects/destroy_service_spec.rb | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 spec/services/projects/destroy_service_spec.rb diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb new file mode 100644 index 0000000000..cdf576cc0c --- /dev/null +++ b/spec/services/projects/destroy_service_spec.rb @@ -0,0 +1,34 @@ +require 'spec_helper' + +describe Projects::DestroyService do + let!(:user) { create(:user) } + let!(:project) { create(:project, namespace: user.namespace) } + let!(:path) { project.repository.path_to_repo } + let!(:remove_path) { path.sub(/\.git\Z/, "+#{project.id}+deleted.git") } + + context 'Sidekiq inline' do + before do + # Run sidekiq immediatly to check that renamed repository will be removed + Sidekiq::Testing.inline! { destroy_project(project, user, {}) } + end + + it { Project.all.should_not include(project) } + it { Dir.exists?(path).should be_falsey } + it { Dir.exists?(remove_path).should be_falsey } + end + + context 'Sidekiq fake' do + before do + # Dont run sidekiq to check if renamed repository exists + Sidekiq::Testing.fake! { destroy_project(project, user, {}) } + end + + it { Project.all.should_not include(project) } + it { Dir.exists?(path).should be_falsey } + it { Dir.exists?(remove_path).should be_truthy } + end + + def destroy_project(project, user, params) + Projects::DestroyService.new(project, user, params).execute + end +end From 98ff4131cd82933b28989df33256f1eb75af1a14 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 3 Jun 2015 13:40:47 +0200 Subject: [PATCH 232/255] LDAP users should not control their LDAP email --- doc/integration/ldap.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md index b67f793c59..904d5d7fee 100644 --- a/doc/integration/ldap.md +++ b/doc/integration/ldap.md @@ -6,6 +6,13 @@ The first time a user signs in with LDAP credentials, GitLab will create a new G GitLab user attributes such as nickname and email will be copied from the LDAP user entry. +## Security + +GitLab assumes that LDAP users are not able to change their LDAP 'mail', 'email' or 'userPrincipalName' attribute. +An LDAP user who is allowed to change their email on the LDAP server can [take over any account](#enabling-ldap-sign-in-for-existing-gitlab-users) on your GitLab server. + +We recommend against using GitLab LDAP integration if your LDAP users are allowed to change their 'mail', 'email' or 'userPrincipalName' attribute on the LDAP server. + ## Configuring GitLab for LDAP integration To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`. From 61cfd1d2733a717934a723d36f60e7bcd09fad05 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 3 Jun 2015 14:07:20 +0200 Subject: [PATCH 233/255] Wrap group removal into service Signed-off-by: Dmitriy Zaporozhets --- app/controllers/admin/groups_controller.rb | 2 +- app/controllers/groups_controller.rb | 2 +- app/services/destroy_group_service.rb | 11 +++++++++++ lib/api/groups.rb | 2 +- 4 files changed, 14 insertions(+), 3 deletions(-) create mode 100644 app/services/destroy_group_service.rb diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 2dfae13ac5..4d3e48f7f8 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -47,7 +47,7 @@ class Admin::GroupsController < Admin::ApplicationController end def destroy - @group.destroy + DestroyGroupService.new(@group, current_user).execute redirect_to admin_groups_path, notice: 'Group was successfully deleted.' end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index 34f0b257db..2e381822e4 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -82,7 +82,7 @@ class GroupsController < Groups::ApplicationController end def destroy - @group.destroy + DestroyGroupService.new(@group, current_user).execute redirect_to root_path, notice: 'Group was removed.' end diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb new file mode 100644 index 0000000000..9637a1480a --- /dev/null +++ b/app/services/destroy_group_service.rb @@ -0,0 +1,11 @@ +class DestroyGroupService + attr_accessor :group, :current_user + + def initialize(group, user) + @group, @current_user = group, user + end + + def execute + @group.destroy + end +end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index f768c75040..e88b6e3177 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -62,7 +62,7 @@ module API delete ":id" do group = find_group(params[:id]) authorize! :admin_group, group - group.destroy + DestroyGroupService.new(group, current_user).execute end # Transfer a project to the Group namespace From 1edff53444ea493ee010a83220cf13ccb381b411 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 3 Jun 2015 14:57:12 +0200 Subject: [PATCH 234/255] Remove projects before group/user. Remove namespace directory async Signed-off-by: Dmitriy Zaporozhets --- app/models/namespace.rb | 11 +++++++++-- app/services/delete_user_service.rb | 5 +++++ app/services/destroy_group_service.rb | 5 +++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 211dfa76b8..8918e4a682 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -72,7 +72,7 @@ class Namespace < ActiveRecord::Base path.gsub!(/[^a-zA-Z0-9_\-\.]/, "") # 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. + # Work around that by setting their username to "blank", followed by a counter. path = "blank" if path.blank? counter = 0 @@ -99,7 +99,14 @@ class Namespace < ActiveRecord::Base end def rm_dir - gitlab_shell.rm_namespace(path) + # Move namespace directory into trash. + # We will remove it later async + new_path = "#{path}+#{id}+deleted" + gitlab_shell.mv_namespace(path, new_path) + + # Remove namespace directroy async with delay so + # GitLab has time to remove all projects first + GitlabShellWorker.perform_in(5.minutes, :rm_namespace, new_path) end def move_dir diff --git a/app/services/delete_user_service.rb b/app/services/delete_user_service.rb index d259b4efca..ca350eb2a8 100644 --- a/app/services/delete_user_service.rb +++ b/app/services/delete_user_service.rb @@ -4,6 +4,11 @@ class DeleteUserService user.errors[:base] << 'You must transfer ownership or delete groups before you can remove user' user else + # TODO: Skip remove repository so Namespace#rm_dir works + user.personal_projects.each do |project| + ::Projects::DestroyService.new(project, current_user, {}).execute + end + user.destroy end end diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb index 9637a1480a..c1add7b92e 100644 --- a/app/services/destroy_group_service.rb +++ b/app/services/destroy_group_service.rb @@ -6,6 +6,11 @@ class DestroyGroupService end def execute + # TODO: Skip remove repository so Namespace#rm_dir works + @group.projects.each do |project| + ::Projects::DestroyService.new(project, current_user, {}).execute + end + @group.destroy end end From 58ab8a4a9d0e051cf6355c1b9a4d8dc13fc892cc Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 3 Jun 2015 15:09:12 +0200 Subject: [PATCH 235/255] Fix tests and increase delay time before remove repository Signed-off-by: Dmitriy Zaporozhets --- app/services/projects/destroy_service.rb | 2 +- spec/features/projects_spec.rb | 9 --------- spec/requests/api/projects_spec.rb | 9 ++------- 3 files changed, 3 insertions(+), 17 deletions(-) diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 53bf36b101..29e8ba347d 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -44,7 +44,7 @@ module Projects if gitlab_shell.mv_repository(path, new_path) log_info("Repository \"#{path}\" moved to \"#{new_path}\"") - GitlabShellWorker.perform_in(30.seconds, :remove_repository, new_path) + GitlabShellWorker.perform_in(5.minutes, :remove_repository, new_path) else false end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index cae11be7cd..24d4a67d50 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -13,15 +13,6 @@ describe "Projects", feature: true, js: true do it "should remove project" do expect { remove_project }.to change {Project.count}.by(-1) end - - it 'should delete the project from disk' do - expect(GitlabShellWorker).to( - receive(:perform_async).with(:remove_repository, - /#{@project.path_with_namespace}/) - ).twice - - remove_project - end end def remove_project diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 46cd26eb92..dbfd72e5f1 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -57,14 +57,14 @@ describe API::API, api: true do expect(json_response.first['name']).to eq(project.name) expect(json_response.first['owner']['username']).to eq(user.username) end - + it 'should include the project labels as the tag_list' do get api('/projects', user) response.status.should == 200 json_response.should be_an Array json_response.first.keys.should include('tag_list') end - + context 'and using search' do it 'should return searched project' do get api('/projects', user), { search: project.name } @@ -792,11 +792,6 @@ describe API::API, api: true do describe 'DELETE /projects/:id' do context 'when authenticated as user' do it 'should remove project' do - expect(GitlabShellWorker).to( - receive(:perform_async).with(:remove_repository, - /#{project.path_with_namespace}/) - ).twice - delete api("/projects/#{project.id}", user) expect(response.status).to eq(200) end From 41ee2aa2d70ddb729904d90f12b0318f2ce58215 Mon Sep 17 00:00:00 2001 From: Job van der Voort Date: Wed, 3 Jun 2015 15:35:13 +0200 Subject: [PATCH 236/255] fix typo and add screenshots --- doc/workflow/2fa.png | Bin 0 -> 23415 bytes doc/workflow/2fa_auth.png | Bin 0 -> 15569 bytes doc/workflow/two_factor_authentication.md | 6 +++--- 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 doc/workflow/2fa.png create mode 100644 doc/workflow/2fa_auth.png diff --git a/doc/workflow/2fa.png b/doc/workflow/2fa.png new file mode 100644 index 0000000000000000000000000000000000000000..bbf415210d5c0b188571171687eac948b2d25039 GIT binary patch literal 23415 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/two_factor_authentication.md b/doc/workflow/two_factor_authentication.md index 81f51042bf..8ac1ca4b35 100644 --- a/doc/workflow/two_factor_authentication.md +++ b/doc/workflow/two_factor_authentication.md @@ -14,10 +14,10 @@ is to know your username and password *and* have access to your phone. 1. Log in to your GitLab account. 1. Go to your **Profile Settings**. -1. Go to **Acount**. +1. Go to **Account**. 1. Click **Enable Two-factor Authentication**. -TODO: Insert screenshot of 2FA page (with the "Can't scan the code?" text) +![Two-factor setup](2fa.png) **On your phone:** @@ -53,7 +53,7 @@ Enter your username and password credentials as you normally would, and you'll be presented with a second prompt for an authentication code. Enter the pin from your phone's application or a recovery code to log in. -TODO: Insert screenshot of 2FA login prompt? +![Two-factor authentication on sign in](2fa_auth.png) ## Disabling 2FA From 7044d649a39c51bf0543baec33c3f7497e038171 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 3 Jun 2015 15:42:22 +0200 Subject: [PATCH 237/255] Add autocrlf back to installation docs, add a check for it. --- doc/install/installation.md | 3 +++ lib/tasks/gitlab/check.rake | 31 ++++++++++++++++++++++++++++++ lib/tasks/gitlab/task_helpers.rake | 4 ++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/doc/install/installation.md b/doc/install/installation.md index be0dd37a48..badea4de21 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -241,6 +241,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Copy the example Rack attack config sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb + # Configure Git global settings for git user, used when editing via web editor + sudo -u git -H git config --global core.autocrlf input + # Configure Redis connection settings sudo -u git -H cp config/resque.yml.example config/resque.yml diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 3f4f673791..75bd41f283 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -13,6 +13,7 @@ namespace :gitlab do warn_user_is_not_gitlab start_checking "GitLab" + check_git_config check_database_config_exists check_database_is_not_sqlite check_migrations_are_up @@ -37,6 +38,36 @@ namespace :gitlab do # Checks ######################## + def check_git_config + print "Git configured with autocrlf=input? ... " + + options = { + "core.autocrlf" => "input" + } + + correct_options = options.map do |name, value| + run(%W(#{Gitlab.config.git.bin_path} config --global --get #{name})).try(:squish) == value + end + + if correct_options.all? + puts "yes".green + else + print "Trying to fix Git error automatically. ..." + + if auto_fix_git_config(options) + puts "Success".green + else + puts "Failed".red + try_fixing_it( + sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global core.autocrlf \"#{options["core.autocrlf"]}\"") + ) + for_more_information( + see_installation_guide_section "GitLab" + ) + end + end + end + def check_database_config_exists print "Database config exists? ... " diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake index 14a130be2c..c95b6540eb 100644 --- a/lib/tasks/gitlab/task_helpers.rake +++ b/lib/tasks/gitlab/task_helpers.rake @@ -118,9 +118,9 @@ namespace :gitlab do # Returns true if all subcommands were successfull (according to their exit code) # Returns false if any or all subcommands failed. def auto_fix_git_config(options) - if !@warned_user_not_gitlab && options['user.email'] != 'example@example.com' # default email should be overridden? + if !@warned_user_not_gitlab command_success = options.map do |name, value| - system(%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value})) + system(*%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value})) end command_success.all? From 47a95754de18a8f08aa78bf9f20223f263fa8c90 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 3 Jun 2015 16:15:58 +0200 Subject: [PATCH 238/255] Log group creation and removal Signed-off-by: Dmitriy Zaporozhets --- app/models/group.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/models/group.rb b/app/models/group.rb index b4e908c560..051c672cb3 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -101,10 +101,14 @@ class Group < Namespace end def post_create_hook + Gitlab::AppLogger.info("Group \"#{name}\" was created") + system_hook_service.execute_hooks_for(self, :create) end def post_destroy_hook + Gitlab::AppLogger.info("Group \"#{name}\" was removed") + system_hook_service.execute_hooks_for(self, :destroy) end From 53a0ac47344b9b1d93ec36d9e6b41e2394598beb Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 3 Jun 2015 16:16:27 +0200 Subject: [PATCH 239/255] Skip repo removing whem remove user or group Signed-off-by: Dmitriy Zaporozhets --- app/models/namespace.rb | 12 ++++-- app/services/delete_user_service.rb | 5 ++- app/services/destroy_group_service.rb | 5 ++- app/services/projects/destroy_service.rb | 8 ++-- spec/services/destroy_group_service_spec.rb | 44 +++++++++++++++++++++ 5 files changed, 63 insertions(+), 11 deletions(-) create mode 100644 spec/services/destroy_group_service_spec.rb diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 8918e4a682..03d2ab165e 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -102,11 +102,15 @@ class Namespace < ActiveRecord::Base # Move namespace directory into trash. # We will remove it later async new_path = "#{path}+#{id}+deleted" - gitlab_shell.mv_namespace(path, new_path) - # Remove namespace directroy async with delay so - # GitLab has time to remove all projects first - GitlabShellWorker.perform_in(5.minutes, :rm_namespace, new_path) + if gitlab_shell.mv_namespace(path, new_path) + message = "Namespace directory \"#{path}\" moved to \"#{new_path}\"" + Gitlab::AppLogger.info message + + # Remove namespace directroy async with delay so + # GitLab has time to remove all projects first + GitlabShellWorker.perform_in(5.minutes, :rm_namespace, new_path) + end end def move_dir diff --git a/app/services/delete_user_service.rb b/app/services/delete_user_service.rb index ca350eb2a8..9017a63af3 100644 --- a/app/services/delete_user_service.rb +++ b/app/services/delete_user_service.rb @@ -4,9 +4,10 @@ class DeleteUserService user.errors[:base] << 'You must transfer ownership or delete groups before you can remove user' user else - # TODO: Skip remove repository so Namespace#rm_dir works user.personal_projects.each do |project| - ::Projects::DestroyService.new(project, current_user, {}).execute + # Skip repository removal because we remove directory with namespace + # that contain all this repositories + ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute end user.destroy diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb index c1add7b92e..d929a67629 100644 --- a/app/services/destroy_group_service.rb +++ b/app/services/destroy_group_service.rb @@ -6,9 +6,10 @@ class DestroyGroupService end def execute - # TODO: Skip remove repository so Namespace#rm_dir works @group.projects.each do |project| - ::Projects::DestroyService.new(project, current_user, {}).execute + # Skip repository removal because we remove directory with namespace + # that contain all this repositories + ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute end @group.destroy diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb index 29e8ba347d..403f419ec5 100644 --- a/app/services/projects/destroy_service.rb +++ b/app/services/projects/destroy_service.rb @@ -36,9 +36,11 @@ module Projects private def remove_repository(path) - unless gitlab_shell.exists?(path + '.git') - return true - end + # Skip repository removal. We use this flag when remove user or group + return true if params[:skip_repo] == true + + # There is a possibility project does not have repository or wiki + return true unless gitlab_shell.exists?(path + '.git') new_path = removal_path(path) diff --git a/spec/services/destroy_group_service_spec.rb b/spec/services/destroy_group_service_spec.rb new file mode 100644 index 0000000000..24e439503e --- /dev/null +++ b/spec/services/destroy_group_service_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe DestroyGroupService do + let!(:user) { create(:user) } + let!(:group) { create(:group) } + let!(:project) { create(:project, namespace: group) } + let!(:gitlab_shell) { Gitlab::Shell.new } + let!(:remove_path) { group.path + "+#{group.id}+deleted" } + + context 'database records' do + before do + destroy_group(group, user) + end + + it { Group.all.should_not include(group) } + it { Project.all.should_not include(project) } + end + + context 'file system' do + context 'Sidekiq inline' do + before do + # Run sidekiq immediatly to check that renamed dir will be removed + Sidekiq::Testing.inline! { destroy_group(group, user) } + end + + it { gitlab_shell.exists?(group.path).should be_falsey } + it { gitlab_shell.exists?(remove_path).should be_falsey } + end + + context 'Sidekiq fake' do + before do + # Dont run sidekiq to check if renamed repository exists + Sidekiq::Testing.fake! { destroy_group(group, user) } + end + + it { gitlab_shell.exists?(group.path).should be_falsey } + it { gitlab_shell.exists?(remove_path).should be_truthy } + end + end + + def destroy_group(group, user) + DestroyGroupService.new(group, user).execute + end +end From a1be236c874909efafb4288a5234d28d5b5e2361 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 3 Jun 2015 15:01:13 +0200 Subject: [PATCH 240/255] Trigger hooks-create on gitlab backup restore. --- lib/tasks/gitlab/shell.rake | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index e835d6cb9b..afdaba11cb 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -59,6 +59,9 @@ namespace :gitlab do # Launch installation process system(*%W(bin/install)) + + # (Re)create hooks + system(*%W(bin/create-hooks)) end # Required for debian packaging with PKGR: Setup .ssh/environment with From 55715735d10c3c35e52af6b67c99dcdbb5c7bf97 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 3 Jun 2015 17:01:31 +0200 Subject: [PATCH 241/255] Add CHANGELOG item Signed-off-by: Dmitriy Zaporozhets --- CHANGELOG | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG b/CHANGELOG index 455b4dcf96..cbd6d92e13 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -42,6 +42,7 @@ v 7.12.0 (unreleased) - Better performance for web editor (switched from satellites to rugged) - GitLab CI service sends .gitlab-ci.yaml in each push call - When remove project - move repository and schedule it removal + - Improve group removing logic v 7.11.4 - Fix missing bullets when creating lists From 7af2fbbaf633a8073bd7c9f9993780659e3db263 Mon Sep 17 00:00:00 2001 From: Marin Jankovski Date: Wed, 3 Jun 2015 17:08:37 +0200 Subject: [PATCH 242/255] Add a changelog item. --- CHANGELOG | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 1a8012226a..fc82884701 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -38,10 +38,11 @@ v 7.12.0 (unreleased) - User should be able to leave group. If not - show him proper message - User has ability to leave project - Add SAML support as an omniauth provider - - Allow to configure a URL to show after sign out + - Allow to configure a URL to show after sign out - Add an option to automatically sign-in with an Omniauth provider - Better performance for web editor (switched from satellites to rugged) - GitLab CI service sends .gitlab-ci.yaml in each push call + - Trigger create-hooks on backup restore task v 7.11.4 - Fix missing bullets when creating lists From 6e5473f930c11be6719585b47c47d8552f6b41b5 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 3 Jun 2015 17:27:23 -0400 Subject: [PATCH 243/255] Remove show actions from Admin and Project DeployKeys --- .../admin/deploy_keys_controller.rb | 7 +--- .../projects/deploy_keys_controller.rb | 4 --- app/views/admin/deploy_keys/show.html.haml | 35 ------------------- app/views/projects/deploy_keys/show.html.haml | 14 -------- config/routes.rb | 4 +-- features/admin/deploy_keys.feature | 5 --- features/steps/admin/deploy_keys.rb | 11 ------ spec/routing/project_routing_spec.rb | 2 +- 8 files changed, 4 insertions(+), 78 deletions(-) delete mode 100644 app/views/admin/deploy_keys/show.html.haml delete mode 100644 app/views/projects/deploy_keys/show.html.haml diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb index c301e61d1c..285e849534 100644 --- a/app/controllers/admin/deploy_keys_controller.rb +++ b/app/controllers/admin/deploy_keys_controller.rb @@ -1,13 +1,8 @@ class Admin::DeployKeysController < Admin::ApplicationController before_action :deploy_keys, only: [:index] - before_action :deploy_key, only: [:show, :destroy] + before_action :deploy_key, only: [:destroy] def index - - end - - def show - end def new diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index 8c1bbf7691..40e2b37912 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -18,10 +18,6 @@ class Projects::DeployKeysController < Projects::ApplicationController @available_public_keys -= @available_project_keys end - def show - @key = @project.deploy_keys.find(params[:id]) - end - def new @key = @project.deploy_keys.new diff --git a/app/views/admin/deploy_keys/show.html.haml b/app/views/admin/deploy_keys/show.html.haml deleted file mode 100644 index ea361ca4bd..0000000000 --- a/app/views/admin/deploy_keys/show.html.haml +++ /dev/null @@ -1,35 +0,0 @@ -- page_title @deploy_key.title, "Deploy Keys" -.row - .col-md-4 - .panel.panel-default - .panel-heading - Deploy Key - %ul.well-list - %li - %span.light Title: - %strong= @deploy_key.title - %li - %span.light Created on: - %strong= @deploy_key.created_at.stamp("Aug 21, 2011") - - .panel.panel-default - .panel-heading Projects (#{@deploy_key.deploy_keys_projects.count}) - - if @deploy_key.deploy_keys_projects.any? - %ul.well-list - - @deploy_key.projects.each do |project| - %li - %span - %strong - = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] - .pull-right - = link_to disable_namespace_project_deploy_key_path(project.namespace, project, @deploy_key), data: { confirm: "Are you sure?" }, method: :put, class: "btn-xs btn btn-remove", title: 'Remove deploy key from project' do - %i.fa.fa-times.fa-inverse - - .col-md-8 - %p - %span.light Fingerprint: - %strong= @deploy_key.fingerprint - %pre.well-pre - = @deploy_key.key - .pull-right - = link_to 'Remove', admin_deploy_key_path(@deploy_key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key" diff --git a/app/views/projects/deploy_keys/show.html.haml b/app/views/projects/deploy_keys/show.html.haml deleted file mode 100644 index 7d44652af7..0000000000 --- a/app/views/projects/deploy_keys/show.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -- page_title @key.title, "Deploy Keys" -%h3.page-title - Deploy key: - = @key.title - %small - created on - = @key.created_at.stamp("Aug 21, 2011") -.back-link - = link_to namespace_project_deploy_keys_path(@project.namespace, @project) do - ← To keys list -%hr -%pre= @key.key -.pull-right - = link_to 'Remove', namespace_project_deploy_key_path(@project.namespace, @project, @key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn-remove btn delete-key" diff --git a/config/routes.rb b/config/routes.rb index b7380254ab..f4a104664f 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -165,7 +165,7 @@ Gitlab::Application.routes.draw do end end - resources :deploy_keys, only: [:index, :show, :new, :create, :destroy] + resources :deploy_keys, only: [:index, :new, :create, :destroy] resources :hooks, only: [:index, :create, :destroy] do get :test @@ -421,7 +421,7 @@ Gitlab::Application.routes.draw do end end - resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :show, :new, :create] do + resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create] do member do put :enable put :disable diff --git a/features/admin/deploy_keys.feature b/features/admin/deploy_keys.feature index 9df47eb51f..33439cd1e8 100644 --- a/features/admin/deploy_keys.feature +++ b/features/admin/deploy_keys.feature @@ -8,11 +8,6 @@ Feature: Admin Deploy Keys When I visit admin deploy keys page Then I should see all public deploy keys - Scenario: Deploy Keys show - When I visit admin deploy keys page - And I click on first deploy key - Then I should see deploy key details - Scenario: Deploy Keys new When I visit admin deploy keys page And I click 'New Deploy Key' diff --git a/features/steps/admin/deploy_keys.rb b/features/steps/admin/deploy_keys.rb index fb0b611762..844837d177 100644 --- a/features/steps/admin/deploy_keys.rb +++ b/features/steps/admin/deploy_keys.rb @@ -14,17 +14,6 @@ class Spinach::Features::AdminDeployKeys < Spinach::FeatureSteps end end - step 'I click on first deploy key' do - click_link DeployKey.are_public.first.title - end - - step 'I should see deploy key details' do - deploy_key = DeployKey.are_public.first - current_path.should == admin_deploy_key_path(deploy_key) - page.should have_content(deploy_key.title) - page.should have_content(deploy_key.key) - end - step 'I visit admin deploy key page' do visit admin_deploy_key_path(deploy_key) end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 3a0d9b88d7..0040718d9b 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -172,7 +172,7 @@ end # DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy describe Projects::DeployKeysController, 'routing' do it_behaves_like 'RESTful project resources' do - let(:actions) { [:index, :show, :new, :create] } + let(:actions) { [:index, :new, :create] } let(:controller) { 'deploy_keys' } end end From 793d9799b64c1a46b6e7f45a74c89c3298ad0221 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 3 Jun 2015 17:54:16 -0400 Subject: [PATCH 244/255] Show key fingerprint on DeployKeys#index Also style all key fingerprints consistently across the app. --- app/assets/stylesheets/generic/typography.scss | 7 +++++++ app/views/admin/deploy_keys/index.html.haml | 3 +-- app/views/profiles/keys/_key.html.haml | 3 +-- app/views/profiles/keys/_key_details.html.haml | 2 +- .../projects/deploy_keys/_deploy_key.html.haml | 16 ++++++---------- 5 files changed, 16 insertions(+), 15 deletions(-) diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss index e559089794..66767cb13c 100644 --- a/app/assets/stylesheets/generic/typography.scss +++ b/app/assets/stylesheets/generic/typography.scss @@ -23,6 +23,13 @@ pre { font-family: $monospace_font; } +code { + &.key-fingerprint { + background: $body-bg; + color: $text-color; + } +} + /** * Wiki typography * diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml index 367d25cd6a..6405a69fad 100644 --- a/app/views/admin/deploy_keys/index.html.haml +++ b/app/views/admin/deploy_keys/index.html.haml @@ -19,8 +19,7 @@ = link_to admin_deploy_key_path(deploy_key) do %strong= deploy_key.title %td - %span - (#{deploy_key.fingerprint}) + %code.key-fingerprint= deploy_key.fingerprint %td %span.cgray added #{time_ago_with_tooltip(deploy_key.created_at)} diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml index fe5770f45c..9bbccbc45e 100644 --- a/app/views/profiles/keys/_key.html.haml +++ b/app/views/profiles/keys/_key.html.haml @@ -3,8 +3,7 @@ = link_to path_to_key(key, is_admin) do %strong= key.title %td - %span - (#{key.fingerprint}) + %code.key-fingerprint= key.fingerprint %td %span.cgray added #{time_ago_with_tooltip(key.created_at)} diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml index 8bac22a2e1..e0ae4d9720 100644 --- a/app/views/profiles/keys/_key_details.html.haml +++ b/app/views/profiles/keys/_key_details.html.haml @@ -15,7 +15,7 @@ .col-md-8 %p %span.light Fingerprint: - %strong= @key.fingerprint + %code.key-fingerprint= @key.fingerprint %pre.well-pre = @key.key .pull-right diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml index c577dfa8d5..8d66bae8cd 100644 --- a/app/views/projects/deploy_keys/_deploy_key.html.haml +++ b/app/views/projects/deploy_keys/_deploy_key.html.haml @@ -2,24 +2,20 @@ .pull-right - if @available_keys.include?(deploy_key) = link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do - %i.fa.fa-plus + = icon('plus') Enable - else - if deploy_key.destroyed_when_orphaned? && deploy_key.almost_orphaned? = link_to 'Remove', disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :put, class: "btn btn-remove delete-key btn-sm pull-right" - else = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do - %i.fa.fa-power-off + = icon('power-off') Disable - - if project = project_for_deploy_key(deploy_key) - = link_to namespace_project_deploy_key_path(project.namespace, project, deploy_key) do - %i.fa.fa-key - %strong= deploy_key.title - - else - %i.fa.fa-key - %strong= deploy_key.title - + = icon('key') + %strong= deploy_key.title + %br + %code.key-fingerprint= deploy_key.fingerprint %p.light.prepend-top-10 - if deploy_key.public? From 682d4b6a861836d5cfe3e594fbeee0cff1af15e0 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Wed, 3 Jun 2015 20:45:53 -0400 Subject: [PATCH 245/255] Remove Guard None of the GitLab B.V. developers were using it. --- Gemfile | 17 ----------------- Gemfile.lock | 23 ----------------------- Guardfile | 27 --------------------------- bin/guard | 16 ---------------- 4 files changed, 83 deletions(-) delete mode 100644 Guardfile delete mode 100755 bin/guard diff --git a/Gemfile b/Gemfile index 0ab0a45cdb..0009a8affb 100644 --- a/Gemfile +++ b/Gemfile @@ -1,13 +1,5 @@ source "https://rubygems.org" -def darwin_only(require_as) - RUBY_PLATFORM.include?('darwin') && require_as -end - -def linux_only(require_as) - RUBY_PLATFORM.include?('linux') && require_as -end - gem "rails", "~> 4.1.0" # Default values for AR models @@ -247,15 +239,6 @@ group :development, :test do # Generate Fake data gem 'ffaker', '~> 2.0.0' - # Guard - gem 'guard-rspec' - gem 'guard-spinach' - - # Notification - gem 'rb-fsevent', require: darwin_only('rb-fsevent') - gem 'growl', require: darwin_only('growl') - gem 'rb-inotify', require: linux_only('rb-inotify') - # PhantomJS driver for Capybara gem 'poltergeist', '~> 1.5.1' diff --git a/Gemfile.lock b/Gemfile.lock index c9b8fc1f55..a341a5df40 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -261,19 +261,6 @@ GEM grape-entity (0.4.2) activesupport multi_json (>= 1.3.2) - growl (1.0.3) - guard (2.2.4) - formatador (>= 0.2.4) - listen (~> 2.1) - lumberjack (~> 1.0) - pry (>= 0.9.12) - thor (>= 0.18.1) - guard-rspec (4.2.0) - guard (>= 2.1.1) - rspec (>= 2.14, < 4.0) - guard-spinach (0.0.2) - guard (>= 1.1) - spinach haml (4.0.5) tilt haml-rails (0.5.3) @@ -326,7 +313,6 @@ GEM celluloid (~> 0.16.0) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) - lumberjack (1.0.4) macaddr (1.7.1) systemu (~> 2.6.2) mail (2.6.3) @@ -493,10 +479,6 @@ GEM rqrcode (0.4.2) rqrcode-rails3 (0.1.7) rqrcode (>= 0.4.2) - rspec (2.99.0) - rspec-core (~> 2.99.0) - rspec-expectations (~> 2.99.0) - rspec-mocks (~> 2.99.0) rspec-collection_matchers (1.1.2) rspec-expectations (>= 2.99.0.beta1) rspec-core (2.99.2) @@ -738,9 +720,6 @@ DEPENDENCIES gon (~> 5.0.0) grape (~> 0.6.1) grape-entity (~> 0.4.2) - growl - guard-rspec - guard-spinach haml-rails hipchat (~> 1.5.0) html-pipeline (~> 1.11.0) @@ -778,8 +757,6 @@ DEPENDENCIES rack-oauth2 (~> 1.0.5) rails (~> 4.1.0) raphael-rails (~> 2.1.2) - rb-fsevent - rb-inotify rdoc (~> 3.6) redcarpet (~> 3.2.3) redis-rails diff --git a/Guardfile b/Guardfile deleted file mode 100644 index 68ac3232b0..0000000000 --- a/Guardfile +++ /dev/null @@ -1,27 +0,0 @@ -# A sample Guardfile -# More info at https://github.com/guard/guard#readme - -guard 'rspec', cmd: "spring rspec", all_on_start: false, all_after_pass: false do - watch(%r{^spec/.+_spec\.rb$}) - watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } - watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" } - watch('spec/spec_helper.rb') { "spec" } - - # Rails example - watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } - watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" } - watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] } - watch(%r{^spec/support/(.+)\.rb$}) { "spec" } - watch('config/routes.rb') { "spec/routing" } - watch('app/controllers/application_controller.rb') { "spec/controllers" } - - # Capybara request specs - watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" } -end - -guard 'spinach', command_prefix: 'spring' do - watch(%r|^features/(.*)\.feature|) - watch(%r|^features/steps/(.*)([^/]+)\.rb|) do |m| - "features/#{m[1]}#{m[2]}.feature" - end -end diff --git a/bin/guard b/bin/guard deleted file mode 100755 index 0c1a532bd0..0000000000 --- a/bin/guard +++ /dev/null @@ -1,16 +0,0 @@ -#!/usr/bin/env ruby -# -# This file was generated by Bundler. -# -# The application 'guard' is installed as part of a gem, and -# this file is here to facilitate running it. -# - -require 'pathname' -ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", - Pathname.new(__FILE__).realpath) - -require 'rubygems' -require 'bundler/setup' - -load Gem.bin_path('guard', 'guard') From 3d5a51d416ec36c7ed04855d1c7480f5911615b0 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 3 Jun 2015 23:05:33 +0300 Subject: [PATCH 246/255] added ci yaml --- .gitlab-ci.yml | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 .gitlab-ci.yml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000..66bfa7e2fa --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,94 @@ +# This file is generated by GitLab CI +jobs: +- script: + - export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin + - ruby -v + - which ruby + - gem install bundler + - which bundle + - echo $PATH + - cp config/database.yml.mysql config/database.yml + - cp config/gitlab.yml.example config/gitlab.yml + - ! 'sed "s/username\:.*$/username\: runner/" -i config/database.yml' + - ! 'sed "s/password\:.*$/password\: ''password''/" -i config/database.yml' + - sed "s/gitlabhq_test/gitlabhq_test_$((RANDOM/5000))/" -i config/database.yml + - touch log/application.log + - touch log/test.log + - bundle install --without postgres production --jobs $(nproc) + - bundle exec rake db:create RAILS_ENV=test + - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec + name: Rspec + branches: true + tags: false + runner: ruby,mysql +- script: + - export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin + - ruby -v + - which ruby + - gem install bundler + - which bundle + - echo $PATH + - cp config/database.yml.mysql config/database.yml + - cp config/gitlab.yml.example config/gitlab.yml + - ! 'sed "s/username\:.*$/username\: runner/" -i config/database.yml' + - ! 'sed "s/password\:.*$/password\: ''password''/" -i config/database.yml' + - sed "s/gitlabhq_test/gitlabhq_test_$((RANDOM/5000))/" -i config/database.yml + - touch log/application.log + - touch log/test.log + - bundle install --without postgres production --jobs $(nproc) + - bundle exec rake db:create RAILS_ENV=test + - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach + name: Spinach + branches: true + tags: false + runner: ruby,mysql +- script: + - export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin + - ruby -v + - which ruby + - gem install bundler + - which bundle + - echo $PATH + - cp config/database.yml.mysql config/database.yml + - cp config/gitlab.yml.example config/gitlab.yml + - ! 'sed "s/username\:.*$/username\: runner/" -i config/database.yml' + - ! 'sed "s/password\:.*$/password\: ''password''/" -i config/database.yml' + - sed "s/gitlabhq_test/gitlabhq_test_$((RANDOM/5000))/" -i config/database.yml + - touch log/application.log + - touch log/test.log + - bundle install --without postgres production --jobs $(nproc) + - bundle exec rake db:create RAILS_ENV=test + - RAILS_ENV=test SIMPLECOV=true bundle exec rake jasmine:ci + name: Jasmine + branches: true + tags: false + runner: ruby,mysql +- script: + - export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin + - ruby -v + - which ruby + - gem install bundler + - which bundle + - echo $PATH + - bundle install --without postgres production --jobs $(nproc) + - bundle exec rubocop + name: Rubocop + branches: true + tags: false + runner: ruby,mysql +- script: + - export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin + - export LC_ALL=en_US.UTF-8 + - ruby -v + - which ruby + - gem install bundler + - which bundle + - echo $PATH + - bundle install --without postgres production --jobs $(nproc) + - bundle exec rake brakeman + name: Brakeman + branches: true + tags: false + runner: ruby,mysql +deploy_jobs: [] +skip_refs: '' From b18ac382b73cff20d71158c3951e65a0f527d625 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 4 Jun 2015 16:54:32 +0200 Subject: [PATCH 247/255] Refactor CI script Signed-off-by: Dmitriy Zaporozhets --- .gitlab-ci.yml | 51 +++----------------------------------------------- 1 file changed, 3 insertions(+), 48 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 66bfa7e2fa..021acdeca3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,6 +1,4 @@ -# This file is generated by GitLab CI -jobs: -- script: +before_script: - export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin - ruby -v - which ruby @@ -16,75 +14,32 @@ jobs: - touch log/test.log - bundle install --without postgres production --jobs $(nproc) - bundle exec rake db:create RAILS_ENV=test +jobs: +- script: - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec name: Rspec branches: true tags: false runner: ruby,mysql - script: - - export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin - - ruby -v - - which ruby - - gem install bundler - - which bundle - - echo $PATH - - cp config/database.yml.mysql config/database.yml - - cp config/gitlab.yml.example config/gitlab.yml - - ! 'sed "s/username\:.*$/username\: runner/" -i config/database.yml' - - ! 'sed "s/password\:.*$/password\: ''password''/" -i config/database.yml' - - sed "s/gitlabhq_test/gitlabhq_test_$((RANDOM/5000))/" -i config/database.yml - - touch log/application.log - - touch log/test.log - - bundle install --without postgres production --jobs $(nproc) - - bundle exec rake db:create RAILS_ENV=test - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach name: Spinach branches: true tags: false runner: ruby,mysql - script: - - export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin - - ruby -v - - which ruby - - gem install bundler - - which bundle - - echo $PATH - - cp config/database.yml.mysql config/database.yml - - cp config/gitlab.yml.example config/gitlab.yml - - ! 'sed "s/username\:.*$/username\: runner/" -i config/database.yml' - - ! 'sed "s/password\:.*$/password\: ''password''/" -i config/database.yml' - - sed "s/gitlabhq_test/gitlabhq_test_$((RANDOM/5000))/" -i config/database.yml - - touch log/application.log - - touch log/test.log - - bundle install --without postgres production --jobs $(nproc) - - bundle exec rake db:create RAILS_ENV=test - RAILS_ENV=test SIMPLECOV=true bundle exec rake jasmine:ci name: Jasmine branches: true tags: false runner: ruby,mysql - script: - - export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin - - ruby -v - - which ruby - - gem install bundler - - which bundle - - echo $PATH - - bundle install --without postgres production --jobs $(nproc) - bundle exec rubocop name: Rubocop branches: true tags: false runner: ruby,mysql - script: - - export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin - - export LC_ALL=en_US.UTF-8 - - ruby -v - - which ruby - - gem install bundler - - which bundle - - echo $PATH - - bundle install --without postgres production --jobs $(nproc) - bundle exec rake brakeman name: Brakeman branches: true From 65be969b785f610a66190f09af90b620cdd79d40 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 4 Jun 2015 11:40:43 -0400 Subject: [PATCH 248/255] Remove unnecessary require from RepositoryCache spec --- spec/lib/repository_cache_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/lib/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb index af399f3a73..37240d5131 100644 --- a/spec/lib/repository_cache_spec.rb +++ b/spec/lib/repository_cache_spec.rb @@ -1,4 +1,3 @@ -require 'rspec' require_relative '../../lib/repository_cache' describe RepositoryCache do From 44396e44a3c434eb260cfd928cc9fa544df288c3 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Thu, 4 Jun 2015 11:47:00 -0400 Subject: [PATCH 249/255] Remove unnecessary require from Spinach env --- features/support/env.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/features/support/env.rb b/features/support/env.rb index f34302721e..d4a878ea4c 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -9,7 +9,6 @@ end ENV['RAILS_ENV'] = 'test' require './config/environment' -require 'rspec' require 'rspec/expectations' require 'sidekiq/testing/inline' From 64b6dbea74bea6982c85680aa8a5d4d1a6f312f6 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Thu, 4 Jun 2015 20:03:27 +0000 Subject: [PATCH 250/255] CI script: remove directives with default value --- .gitlab-ci.yml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 021acdeca3..1411a9194b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -18,32 +18,22 @@ jobs: - script: - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec name: Rspec - branches: true - tags: false runner: ruby,mysql - script: - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach name: Spinach - branches: true - tags: false runner: ruby,mysql - script: - RAILS_ENV=test SIMPLECOV=true bundle exec rake jasmine:ci name: Jasmine - branches: true - tags: false runner: ruby,mysql - script: - bundle exec rubocop name: Rubocop - branches: true - tags: false runner: ruby,mysql - script: - bundle exec rake brakeman name: Brakeman - branches: true - tags: false runner: ruby,mysql deploy_jobs: [] skip_refs: '' From 7a5257368106fc41f8982e59dd205e80797aa07b Mon Sep 17 00:00:00 2001 From: Alex Lossent Date: Thu, 4 Jun 2015 10:02:13 +0200 Subject: [PATCH 251/255] Prevent LDAP group sync from removing a group's last owner --- CHANGELOG-EE | 1 + doc/integration/ldap.md | 4 ++-- lib/gitlab/ldap/access.rb | 2 ++ spec/lib/gitlab/ldap/access_spec.rb | 29 +++++++++++++++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/CHANGELOG-EE b/CHANGELOG-EE index 47664a291d..19dea6e57e 100644 --- a/CHANGELOG-EE +++ b/CHANGELOG-EE @@ -2,6 +2,7 @@ v 7.12 (Unreleased) - Fix error when viewing merge request with a commit that includes "Closes #". - Enhance LDAP group synchronization to check also for member attributes that only contain "uid=" - Enhance LDAP group synchronization to check also for submember attributes + - Prevent LDAP group sync from removing a group's last owner v 7.11.2 - Fixed license upload and verification mechanism diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md index abd8932a9a..b60ac6d785 100644 --- a/doc/integration/ldap.md +++ b/doc/integration/ldap.md @@ -161,9 +161,9 @@ If you have two LDAP group links, e.g. 'cn=Engineering' at level 'Developer' and ### Locking yourself out of your own group -As an LDAP-enabled GitLab user, if you create a group and then set it to synchronize with an LDAP group you do not belong to, you will be removed from the grop as soon as the synchronization takes effect for you. +As an LDAP-enabled GitLab user, if you create a group and then set it to synchronize with an LDAP group you do not belong to, you will be removed from the group as soon as the synchronization takes effect for you, unless you are the last owner of the group. -If you accidentally lock yourself out of your own GitLab group, ask a GitLab administrator to change the LDAP synchronization settings for your group. +If you accidentally lock yourself out of your own GitLab group, ask another owner of the group or a GitLab administrator to change the LDAP synchronization settings for your group. ### Non-LDAP GitLab users diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb index 8ded5fc006..672b6ce86d 100644 --- a/lib/gitlab/ldap/access.rb +++ b/lib/gitlab/ldap/access.rb @@ -137,6 +137,8 @@ module Gitlab if active_group_links.any? group.add_users([user.id], fetch_group_access(group, user, active_group_links)) + elsif group.last_owner?(user) + Rails.logger.warn "#{self.class.name}: LDAP group sync cannot remove #{user.name} (#{user.id}) from group #{group.name} (#{group.id}) as this is the group's last owner" else group.users.delete(user) end diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb index c9ca9a4126..a50e45aa90 100644 --- a/spec/lib/gitlab/ldap/access_spec.rb +++ b/spec/lib/gitlab/ldap/access_spec.rb @@ -287,6 +287,35 @@ objectclass: posixGroup change{ gitlab_group_1.members.where(user_id: user).any? }.from(true).to(false) end end + + context "existing access as owner for group-1 with no other owner, not allowed" do + before do + gitlab_group_1.group_members.owners.create(user_id: user.id) + gitlab_group_1.ldap_group_links.create({ + cn: 'ldap-group1', group_access: Gitlab::Access::OWNER, provider: 'ldapmain'}) + access.stub(cns_with_access: ['ldap-group2']) + end + + it "does not remove the user from gitlab_group_1 since it's the last owner" do + expect { access.update_ldap_group_links }.not_to \ + change{ gitlab_group_1.has_owner?(user) } + end + end + + context "existing access as owner for group-1 while other owners present, not allowed" do + before do + owner2 = create(:user) # a 2nd owner + gitlab_group_1.group_members.owners.create([ {user_id: user.id}, {user_id: owner2.id} ] ) + gitlab_group_1.ldap_group_links.create({ + cn: 'ldap-group1', group_access: Gitlab::Access::OWNER, provider: 'ldapmain'}) + access.stub(cns_with_access: ['ldap-group2']) + end + + it "removes user from gitlab_group_1" do + expect { access.update_ldap_group_links }.to \ + change{ gitlab_group_1.members.where(user_id: user).any? }.from(true).to(false) + end + end end describe 'ldap_groups' do From c8a0c0281759e7dd11e8f2021981b57a33a7bbbd Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 5 Jun 2015 12:05:37 +0200 Subject: [PATCH 252/255] Fix factory Signed-off-by: Dmitriy Zaporozhets --- spec/factories.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/factories.rb b/spec/factories.rb index 5a5ccfee5d..8b8a16eacc 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -212,8 +212,8 @@ FactoryGirl.define do factory :gitlab_license, class: "Gitlab::License" do starts_at { Date.today - 1.month } - licensee do - { "Name" => Faker::Name.name } + licensee do + { "Name" => FFaker::Name.name } end notify_users_at { |l| l.expires_at } notify_admins_at { |l| l.expires_at } From ada2be3c215987b4f83dc579059bf276d1155f89 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 5 Jun 2015 15:24:36 +0200 Subject: [PATCH 253/255] Fix spinach tests and some specs after merge CE into EE Signed-off-by: Dmitriy Zaporozhets --- features/steps/group_hooks.rb | 2 +- spec/lib/gitlab/upgrader_spec.rb | 24 ++++++++++++------------ spec/support/login_helpers.rb | 4 +++- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/features/steps/group_hooks.rb b/features/steps/group_hooks.rb index ce0d6ce504..789846040a 100644 --- a/features/steps/group_hooks.rb +++ b/features/steps/group_hooks.rb @@ -32,7 +32,7 @@ class Spinach::Features::GroupHooks < Spinach::FeatureSteps end step 'I submit new hook' do - @url = Faker::Internet.uri("http") + @url = FFaker::Internet.uri("http") fill_in "hook_url", with: @url expect { click_button "Add Web Hook" }.to change(GroupHook, :count).by(1) end diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb index 7bf30d2935..f4d122d236 100644 --- a/spec/lib/gitlab/upgrader_spec.rb +++ b/spec/lib/gitlab/upgrader_spec.rb @@ -17,22 +17,22 @@ describe Gitlab::Upgrader do describe 'latest_version_raw' do it 'should be latest version for GitLab 5' do - upgrader.stub(current_version_raw: "6.3.0-ee") - expect(upgrader.latest_version_raw).to match(/v6\.\d\.\d-ee/) + upgrader.stub(current_version_raw: "7.11.0-ee") + expect(upgrader.latest_version_raw).to match(/v7\.\d\.\d-ee/) end it 'should get the latest version from tags' do allow(upgrader).to receive(:fetch_git_tags).and_return([ - '6f0733310546402c15d3ae6128a95052f6c8ea96 refs/tags/v7.1.1', - 'facfec4b242ce151af224e20715d58e628aa5e74 refs/tags/v7.1.1^{}', - 'f7068d99c79cf79befbd388030c051bb4b5e86d4 refs/tags/v7.10.4', - '337225a4fcfa9674e2528cb6d41c46556bba9dfa refs/tags/v7.10.4^{}', - '880e0ba0adbed95d087f61a9a17515e518fc6440 refs/tags/v7.11.1', - '6584346b604f981f00af8011cd95472b2776d912 refs/tags/v7.11.1^{}', - '43af3e65a486a9237f29f56d96c3b3da59c24ae0 refs/tags/v7.11.2', - 'dac18e7728013a77410e926a1e64225703754a2d refs/tags/v7.11.2^{}', - '0bf21fd4b46c980c26fd8c90a14b86a4d90cc950 refs/tags/v7.9.4', - 'b10de29edbaff7219547dc506cb1468ee35065c3 refs/tags/v7.9.4^{}']) + '6f0733310546402c15d3ae6128a95052f6c8ea96 refs/tags/v7.1.1-ee', + 'facfec4b242ce151af224e20715d58e628aa5e74 refs/tags/v7.1.1-ee^{}', + 'f7068d99c79cf79befbd388030c051bb4b5e86d4 refs/tags/v7.10.4-ee', + '337225a4fcfa9674e2528cb6d41c46556bba9dfa refs/tags/v7.10.4-ee^{}', + '880e0ba0adbed95d087f61a9a17515e518fc6440 refs/tags/v7.11.1-ee', + '6584346b604f981f00af8011cd95472b2776d912 refs/tags/v7.11.1-ee^{}', + '43af3e65a486a9237f29f56d96c3b3da59c24ae0 refs/tags/v7.11.2-ee', + 'dac18e7728013a77410e926a1e64225703754a2d refs/tags/v7.11.2-ee^{}', + '0bf21fd4b46c980c26fd8c90a14b86a4d90cc950 refs/tags/v7.9.4-ee', + 'b10de29edbaff7219547dc506cb1468ee35065c3 refs/tags/v7.9.4-ee^{}']) expect(upgrader.latest_version_raw).to eq("v7.11.2") end end diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index 791d2a1fd6..ba02ae087e 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -21,6 +21,8 @@ module LoginHelpers # Requires Javascript driver. def logout - find(:css, ".fa.fa-sign-out").click + within '.logout-holder' do + find(:css, ".fa.fa-sign-out").click + end end end From f34e8a265ad567b2d64c88de75781c8dc1779016 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 5 Jun 2015 15:32:23 +0200 Subject: [PATCH 254/255] Fix JIRA integration Signed-off-by: Dmitriy Zaporozhets --- lib/gitlab/reference_extractor.rb | 6 +++++- spec/lib/gitlab/reference_extractor_spec.rb | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index e836b05ff2..2c2ee398eb 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -15,7 +15,11 @@ module Gitlab %i(user label issue merge_request snippet commit commit_range).each do |type| define_method("#{type}s") do - references[type] + if type == :issue && project.jira_tracker? + pipeline_result(:external_issue) + else + references[type] + end end end diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index c01f86edf1..743d8310c4 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -25,7 +25,7 @@ describe Gitlab::ReferenceExtractor do project.team << [@u_bar, :guest] subject.analyze(%Q{ - Inline code: `@foo` + Inline code: `@foo` Code block: @@ -33,7 +33,7 @@ describe Gitlab::ReferenceExtractor do @bar ``` - Quote: + Quote: > @offteam }) From c9f1fb574f44d0ce3af14469e7518b5bbd1d6ad4 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Fri, 5 Jun 2015 17:19:48 +0200 Subject: [PATCH 255/255] Remove upgrader test because it does not apply to ee tags set Signed-off-by: Dmitriy Zaporozhets --- spec/lib/gitlab/upgrader_spec.rb | 5 ----- 1 file changed, 5 deletions(-) diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb index f4d122d236..2b27d03dfd 100644 --- a/spec/lib/gitlab/upgrader_spec.rb +++ b/spec/lib/gitlab/upgrader_spec.rb @@ -16,11 +16,6 @@ describe Gitlab::Upgrader do end describe 'latest_version_raw' do - it 'should be latest version for GitLab 5' do - upgrader.stub(current_version_raw: "7.11.0-ee") - expect(upgrader.latest_version_raw).to match(/v7\.\d\.\d-ee/) - end - it 'should get the latest version from tags' do allow(upgrader).to receive(:fetch_git_tags).and_return([ '6f0733310546402c15d3ae6128a95052f6c8ea96 refs/tags/v7.1.1-ee',