summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml94
-rw-r--r--CHANGELOG66
-rw-r--r--CONTRIBUTING.md15
-rw-r--r--Gemfile54
-rw-r--r--Gemfile.lock76
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/application.js.coffee30
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee1
-rw-r--r--app/assets/javascripts/extensions/jquery.js.coffee22
-rw-r--r--app/assets/javascripts/issue.js.coffee1
-rw-r--r--app/assets/javascripts/merge_request.js.coffee101
-rw-r--r--app/assets/javascripts/notes.js.coffee2
-rw-r--r--app/assets/javascripts/profile.js.coffee4
-rw-r--r--app/assets/javascripts/shortcuts_issuable.coffee2
-rw-r--r--app/assets/javascripts/stat_graph_contributors.js.coffee1
-rw-r--r--app/assets/javascripts/stat_graph_contributors_util.js.coffee20
-rw-r--r--app/assets/javascripts/wikis.js.coffee18
-rw-r--r--app/assets/javascripts/zen_mode.js.coffee37
-rw-r--r--app/assets/stylesheets/base/variables.scss3
-rw-r--r--app/assets/stylesheets/generic/common.scss7
-rw-r--r--app/assets/stylesheets/generic/header.scss256
-rw-r--r--app/assets/stylesheets/generic/lists.scss1
-rw-r--r--app/assets/stylesheets/generic/mobile.scss2
-rw-r--r--app/assets/stylesheets/generic/sidebar.scss34
-rw-r--r--app/assets/stylesheets/generic/typography.scss7
-rw-r--r--app/assets/stylesheets/generic/zen.scss10
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss20
-rw-r--r--app/assets/stylesheets/pages/profile.scss19
-rw-r--r--app/assets/stylesheets/pages/projects.scss25
-rw-r--r--app/assets/stylesheets/themes/gitlab-theme.scss11
-rw-r--r--app/controllers/admin/application_settings_controller.rb2
-rw-r--r--app/controllers/admin/deploy_keys_controller.rb7
-rw-r--r--app/controllers/admin/groups_controller.rb2
-rw-r--r--app/controllers/admin/users_controller.rb6
-rw-r--r--app/controllers/application_controller.rb10
-rw-r--r--app/controllers/groups/group_members_controller.rb6
-rw-r--r--app/controllers/groups_controller.rb2
-rw-r--r--app/controllers/oauth/applications_controller.rb8
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb3
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb3
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb4
-rw-r--r--app/controllers/projects/hooks_controller.rb2
-rw-r--r--app/controllers/projects/merge_requests_controller.rb17
-rw-r--r--app/controllers/projects/project_members_controller.rb6
-rw-r--r--app/controllers/projects_controller.rb17
-rw-r--r--app/controllers/registrations_controller.rb2
-rw-r--r--app/controllers/sessions_controller.rb16
-rw-r--r--app/finders/README.md2
-rw-r--r--app/finders/issuable_finder.rb108
-rw-r--r--app/helpers/application_helper.rb11
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/blob_helper.rb10
-rw-r--r--app/helpers/gitlab_markdown_helper.rb70
-rw-r--r--app/helpers/labels_helper.rb38
-rw-r--r--app/helpers/projects_helper.rb28
-rw-r--r--app/mailers/emails/projects.rb3
-rw-r--r--app/models/ability.rb32
-rw-r--r--app/models/application_setting.rb6
-rw-r--r--app/models/commit.rb39
-rw-r--r--app/models/commit_range.rb30
-rw-r--r--app/models/concerns/mentionable.rb13
-rw-r--r--app/models/concerns/referable.rb61
-rw-r--r--app/models/external_issue.rb11
-rw-r--r--app/models/group.rb18
-rw-r--r--app/models/group_milestone.rb2
-rw-r--r--app/models/hooks/project_hook.rb2
-rw-r--r--app/models/hooks/service_hook.rb1
-rw-r--r--app/models/hooks/system_hook.rb1
-rw-r--r--app/models/hooks/web_hook.rb2
-rw-r--r--app/models/issue.rb29
-rw-r--r--app/models/label.rb41
-rw-r--r--app/models/merge_request.rb37
-rw-r--r--app/models/milestone.rb2
-rw-r--r--app/models/namespace.rb15
-rw-r--r--app/models/note.rb4
-rw-r--r--app/models/project.rb14
-rw-r--r--app/models/project_services/gitlab_ci_service.rb14
-rw-r--r--app/models/project_services/hipchat_service.rb2
-rw-r--r--app/models/project_wiki.rb2
-rw-r--r--app/models/repository.rb47
-rw-r--r--app/models/snippet.rb32
-rw-r--r--app/models/user.rb36
-rw-r--r--app/services/delete_user_service.rb16
-rw-r--r--app/services/destroy_group_service.rb17
-rw-r--r--app/services/files/base_service.rb7
-rw-r--r--app/services/files/create_service.rb22
-rw-r--r--app/services/files/delete_service.rb15
-rw-r--r--app/services/files/update_service.rb20
-rw-r--r--app/services/git_push_service.rb3
-rw-r--r--app/services/issuable_base_service.rb11
-rw-r--r--app/services/issues/update_service.rb4
-rw-r--r--app/services/merge_requests/update_service.rb18
-rw-r--r--app/services/notes/create_service.rb2
-rw-r--r--app/services/projects/destroy_service.rb67
-rw-r--r--app/services/system_note_service.rb98
-rw-r--r--app/views/admin/application_settings/_form.html.haml14
-rw-r--r--app/views/admin/deploy_keys/index.html.haml3
-rw-r--r--app/views/admin/deploy_keys/show.html.haml35
-rw-r--r--app/views/admin/users/index.html.haml9
-rw-r--r--app/views/admin/users/show.html.haml24
-rw-r--r--app/views/dashboard/groups/index.html.haml7
-rw-r--r--app/views/dashboard/issues.html.haml2
-rw-r--r--app/views/dashboard/merge_requests.html.haml2
-rw-r--r--app/views/dashboard/milestones/_milestone.html.haml4
-rw-r--r--app/views/dashboard/milestones/show.html.haml3
-rw-r--r--app/views/events/event/_push.html.haml4
-rw-r--r--app/views/groups/group_members/_group_member.html.haml3
-rw-r--r--app/views/groups/issues.html.haml2
-rw-r--r--app/views/groups/merge_requests.html.haml2
-rw-r--r--app/views/groups/milestones/_milestone.html.haml4
-rw-r--r--app/views/groups/milestones/show.html.haml3
-rw-r--r--app/views/groups/show.html.haml2
-rw-r--r--app/views/layouts/_head_panel.html.haml48
-rw-r--r--app/views/layouts/_page.html.haml9
-rw-r--r--app/views/layouts/_public_head_panel.html.haml22
-rw-r--r--app/views/layouts/_search.html.haml2
-rw-r--r--app/views/layouts/application.html.haml4
-rw-r--r--app/views/layouts/devise.html.haml2
-rw-r--r--app/views/layouts/errors.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml43
-rw-r--r--app/views/layouts/header/_empty.html.haml (renamed from app/views/layouts/_empty_head_panel.html.haml)2
-rw-r--r--app/views/layouts/header/_public.html.haml14
-rw-r--r--app/views/layouts/nav/_group.html.haml2
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml2
-rw-r--r--app/views/layouts/notify.html.haml2
-rw-r--r--app/views/layouts/profile.html.haml4
-rw-r--r--app/views/notify/new_issue_email.text.erb4
-rw-r--r--app/views/notify/new_merge_request_email.text.erb4
-rw-r--r--app/views/notify/repository_push_email.html.haml2
-rw-r--r--app/views/profiles/accounts/show.html.haml121
-rw-r--r--app/views/profiles/applications.html.haml62
-rw-r--r--app/views/profiles/design.html.haml80
-rw-r--r--app/views/profiles/emails/index.html.haml34
-rw-r--r--app/views/profiles/keys/_key.html.haml3
-rw-r--r--app/views/profiles/keys/_key_details.html.haml2
-rw-r--r--app/views/profiles/keys/index.html.haml2
-rw-r--r--app/views/profiles/notifications/show.html.haml2
-rw-r--r--app/views/profiles/passwords/edit.html.haml3
-rw-r--r--app/views/profiles/show.html.haml11
-rw-r--r--app/views/profiles/two_factor_auths/new.html.haml20
-rw-r--r--app/views/projects/_aside.html.haml156
-rw-r--r--app/views/projects/_dropdown.html.haml37
-rw-r--r--app/views/projects/_home_panel.html.haml2
-rw-r--r--app/views/projects/_issuable_form.html.haml26
-rw-r--r--app/views/projects/blame/show.html.haml2
-rw-r--r--app/views/projects/blob/_editor.html.haml4
-rw-r--r--app/views/projects/deploy_keys/_deploy_key.html.haml16
-rw-r--r--app/views/projects/deploy_keys/show.html.haml14
-rw-r--r--app/views/projects/diffs/_file.html.haml5
-rw-r--r--app/views/projects/hooks/index.html.haml9
-rw-r--r--app/views/projects/issues/_discussion.html.haml3
-rw-r--r--app/views/projects/issues/_issue.html.haml3
-rw-r--r--app/views/projects/issues/index.html.haml4
-rw-r--r--app/views/projects/labels/_label.html.haml4
-rw-r--r--app/views/projects/labels/index.html.haml5
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml3
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml9
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml43
-rw-r--r--app/views/projects/merge_requests/_show.html.haml50
-rw-r--r--app/views/projects/merge_requests/index.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_mr_accept.html.haml35
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml4
-rw-r--r--app/views/projects/merge_requests/show/_state_widget.html.haml8
-rw-r--r--app/views/projects/milestones/_milestone.html.haml4
-rw-r--r--app/views/projects/milestones/show.html.haml11
-rw-r--r--app/views/projects/project_members/_project_member.html.haml5
-rw-r--r--app/views/projects/update.js.haml2
-rw-r--r--app/views/projects/wikis/_new.html.haml2
-rw-r--r--app/views/search/results/_merge_request.html.haml4
-rw-r--r--app/views/shared/_issuable_filter.html.haml25
-rw-r--r--app/views/shared/snippets/_form.html.haml2
-rwxr-xr-xbin/rake5
-rwxr-xr-xbin/spring11
-rw-r--r--config.ru11
-rw-r--r--config/gitlab.yml.example13
-rw-r--r--config/initializers/1_settings.rb2
-rw-r--r--config/initializers/6_rack_profiler.rb3
-rw-r--r--config/initializers/7_omniauth.rb2
-rw-r--r--config/routes.rb6
-rw-r--r--db/fixtures/development/04_project.rb4
-rw-r--r--db/fixtures/development/05_users.rb6
-rw-r--r--db/fixtures/development/07_milestones.rb2
-rw-r--r--db/fixtures/development/09_issues.rb4
-rw-r--r--db/fixtures/development/10_merge_requests.rb4
-rw-r--r--db/fixtures/development/12_snippets.rb4
-rw-r--r--db/fixtures/development/13_comments.rb4
-rw-r--r--db/migrate/20150516060434_add_note_events_to_web_hooks.rb9
-rw-r--r--db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb5
-rw-r--r--db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb5
-rw-r--r--db/schema.rb5
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/merge_requests.md4
-rw-r--r--doc/api/namespaces.md44
-rw-r--r--doc/install/installation.md7
-rw-r--r--doc/integration/ldap.md7
-rw-r--r--doc/integration/omniauth.md1
-rw-r--r--doc/integration/saml.md77
-rw-r--r--doc/markdown/markdown.md22
-rw-r--r--doc/raketasks/backup_restore.md4
-rw-r--r--doc/raketasks/maintenance.md3
-rw-r--r--doc/web_hooks/web_hooks.md279
-rw-r--r--doc/workflow/2fa.pngbin0 -> 23415 bytes
-rw-r--r--doc/workflow/2fa_auth.pngbin0 -> 15569 bytes
-rw-r--r--doc/workflow/README.md4
-rw-r--r--doc/workflow/shortcuts.md5
-rw-r--r--doc/workflow/shortcuts.pngbin0 -> 78736 bytes
-rw-r--r--doc/workflow/timezone.md18
-rw-r--r--doc/workflow/two_factor_authentication.md65
-rw-r--r--doc_styleguide.md22
-rw-r--r--docker/README.md2
-rw-r--r--features/admin/deploy_keys.feature5
-rw-r--r--features/dashboard/group.feature3
-rw-r--r--features/project/merge_requests.feature12
-rw-r--r--features/project/project.feature11
-rw-r--r--features/project/wiki.feature5
-rw-r--r--features/steps/admin/deploy_keys.rb11
-rw-r--r--features/steps/dashboard/dashboard.rb4
-rw-r--r--features/steps/dashboard/group.rb4
-rw-r--r--features/steps/groups.rb4
-rw-r--r--features/steps/profile/notifications.rb2
-rw-r--r--features/steps/profile/profile.rb26
-rw-r--r--features/steps/project/hooks.rb2
-rw-r--r--features/steps/project/merge_requests.rb27
-rw-r--r--features/steps/project/project.rb12
-rw-r--r--features/steps/project/wiki.rb10
-rw-r--r--features/steps/shared/project.rb11
-rw-r--r--lib/api/groups.rb2
-rw-r--r--lib/api/merge_requests.rb8
-rw-r--r--lib/api/namespaces.rb11
-rw-r--r--lib/api/project_hooks.rb6
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/gitlab/backend/grack_auth.rb1
-rw-r--r--lib/gitlab/backend/rack_attack_helpers.rb31
-rw-r--r--lib/gitlab/backend/shell.rb14
-rw-r--r--lib/gitlab/closing_issue_extractor.rb2
-rw-r--r--lib/gitlab/markdown.rb5
-rw-r--r--lib/gitlab/markdown/commit_range_reference_filter.rb9
-rw-r--r--lib/gitlab/markdown/commit_reference_filter.rb11
-rw-r--r--lib/gitlab/markdown/cross_project_reference.rb3
-rw-r--r--lib/gitlab/markdown/external_issue_reference_filter.rb9
-rw-r--r--lib/gitlab/markdown/external_link_filter.rb33
-rw-r--r--lib/gitlab/markdown/issue_reference_filter.rb11
-rw-r--r--lib/gitlab/markdown/label_reference_filter.rb24
-rw-r--r--lib/gitlab/markdown/merge_request_reference_filter.rb11
-rw-r--r--lib/gitlab/markdown/reference_filter.rb16
-rw-r--r--lib/gitlab/markdown/sanitization_filter.rb59
-rw-r--r--lib/gitlab/markdown/snippet_reference_filter.rb11
-rw-r--r--lib/gitlab/markdown/user_reference_filter.rb16
-rw-r--r--lib/gitlab/reference_extractor.rb54
-rw-r--r--lib/gitlab/satellite/files/delete_file_action.rb50
-rw-r--r--lib/gitlab/satellite/files/edit_file_action.rb68
-rw-r--r--lib/gitlab/satellite/files/file_action.rb25
-rw-r--r--lib/gitlab/satellite/files/new_file_action.rb67
-rw-r--r--lib/redcarpet/render/gitlab_html.rb2
-rwxr-xr-xlib/support/init.d/gitlab3
-rwxr-xr-xlib/support/init.d/gitlab.default.example5
-rw-r--r--lib/tasks/gitlab/check.rake86
-rw-r--r--lib/tasks/gitlab/shell.rake3
-rw-r--r--lib/tasks/gitlab/task_helpers.rake4
-rw-r--r--lib/tasks/jasmine.rake2
-rw-r--r--spec/controllers/profiles/two_factor_auths_controller_spec.rb7
-rw-r--r--spec/factories.rb18
-rw-r--r--spec/features/admin/admin_hooks_spec.rb2
-rw-r--r--spec/features/gitlab_flavored_markdown_spec.rb34
-rw-r--r--spec/features/groups_spec.rb36
-rw-r--r--spec/features/markdown_spec.rb37
-rw-r--r--spec/features/projects_spec.rb55
-rw-r--r--spec/finders/issues_finder_spec.rb16
-rw-r--r--spec/finders/merge_requests_finder_spec.rb4
-rw-r--r--spec/fixtures/markdown.md.erb73
-rw-r--r--spec/helpers/blob_helper_spec.rb33
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb16
-rw-r--r--spec/helpers/labels_helper_spec.rb68
-rw-r--r--spec/javascripts/extensions/array_spec.js.coffee12
-rw-r--r--spec/javascripts/extensions/jquery_spec.js.coffee34
-rw-r--r--spec/javascripts/fixtures/issuable.html.haml2
-rw-r--r--spec/javascripts/fixtures/issue_note.html.haml12
-rw-r--r--spec/javascripts/fixtures/issues_show.html.haml13
-rw-r--r--spec/javascripts/fixtures/merge_requests_show.html.haml13
-rw-r--r--spec/javascripts/fixtures/zen_mode.html.haml9
-rw-r--r--spec/javascripts/issue_spec.js.coffee30
-rw-r--r--spec/javascripts/merge_request_spec.js.coffee31
-rw-r--r--spec/javascripts/notes_spec.js.coffee19
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js.coffee7
-rw-r--r--spec/javascripts/spec_helper.coffee46
-rw-r--r--spec/javascripts/stat_graph_contributors_util_spec.js8
-rw-r--r--spec/javascripts/support/jasmine.yml15
-rw-r--r--spec/javascripts/support/jasmine_helper.rb15
-rw-r--r--spec/javascripts/zen_mode_spec.js.coffee52
-rw-r--r--spec/lib/gitlab/backend/grack_auth_spec.rb2
-rw-r--r--spec/lib/gitlab/backend/rack_attack_helpers_spec.rb35
-rw-r--r--spec/lib/gitlab/closing_issue_extractor_spec.rb70
-rw-r--r--spec/lib/gitlab/markdown/autolink_filter_spec.rb6
-rw-r--r--spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb38
-rw-r--r--spec/lib/gitlab/markdown/commit_reference_filter_spec.rb18
-rw-r--r--spec/lib/gitlab/markdown/emoji_filter_spec.rb4
-rw-r--r--spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb18
-rw-r--r--spec/lib/gitlab/markdown/external_link_filter_spec.rb31
-rw-r--r--spec/lib/gitlab/markdown/issue_reference_filter_spec.rb21
-rw-r--r--spec/lib/gitlab/markdown/label_reference_filter_spec.rb76
-rw-r--r--spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb15
-rw-r--r--spec/lib/gitlab/markdown/sanitization_filter_spec.rb33
-rw-r--r--spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb13
-rw-r--r--spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb4
-rw-r--r--spec/lib/gitlab/markdown/task_list_filter_spec.rb4
-rw-r--r--spec/lib/gitlab/markdown/user_reference_filter_spec.rb47
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb28
-rw-r--r--spec/mailers/notify_spec.rb4
-rw-r--r--spec/models/commit_range_spec.rb23
-rw-r--r--spec/models/commit_spec.rb24
-rw-r--r--spec/models/external_issue_spec.rb24
-rw-r--r--spec/models/group_spec.rb26
-rw-r--r--spec/models/hooks/project_hook_spec.rb1
-rw-r--r--spec/models/hooks/service_hook_spec.rb1
-rw-r--r--spec/models/hooks/system_hook_spec.rb1
-rw-r--r--spec/models/hooks/web_hook_spec.rb1
-rw-r--r--spec/models/issue_spec.rb28
-rw-r--r--spec/models/label_spec.rb67
-rw-r--r--spec/models/merge_request_spec.rb41
-rw-r--r--spec/models/milestone_spec.rb2
-rw-r--r--spec/models/project_services/gitlab_ci_service_spec.rb15
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb31
-rw-r--r--spec/models/project_spec.rb21
-rw-r--r--spec/models/snippet_spec.rb36
-rw-r--r--spec/models/user_spec.rb43
-rw-r--r--spec/requests/api/files_spec.rb51
-rw-r--r--spec/requests/api/merge_requests_spec.rb4
-rw-r--r--spec/requests/api/namespaces_spec.rb29
-rw-r--r--spec/requests/api/projects_spec.rb13
-rw-r--r--spec/routing/project_routing_spec.rb34
-rw-r--r--spec/services/destroy_group_service_spec.rb44
-rw-r--r--spec/services/issues/update_service_spec.rb25
-rw-r--r--spec/services/merge_requests/update_service_spec.rb36
-rw-r--r--spec/services/notes/create_service_spec.rb2
-rw-r--r--spec/services/projects/destroy_service_spec.rb34
-rw-r--r--spec/services/system_note_service_spec.rb45
-rw-r--r--spec/support/filter_spec_helper.rb77
-rw-r--r--spec/support/mentionable_shared_examples.rb43
-rw-r--r--spec/support/reference_filter_spec_helper.rb50
-rw-r--r--spec/teaspoon_env.rb178
-rwxr-xr-xvendor/assets/javascripts/jasmine-fixture.js433
341 files changed, 4647 insertions, 2754 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 00000000000..66bfa7e2fac
--- /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: ''
diff --git a/CHANGELOG b/CHANGELOG
index 762f1bfe412..fe6b0bcee95 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,11 +1,59 @@
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.
+ - 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
+ - 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)
+ - 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.
+ - 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)
+ - 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)
- 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)
+ - Fix new/empty milestones showing 100% completion value (Jonah Bishop)
+ - Add a note when an Issue or Merge Request's title changes
+ - 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.
+ - 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
+ - User has ability to leave project
+ - Add SAML support as an omniauth provider
+ - 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
+ - When remove project - move repository and schedule it removal
+ - Improve group removing logic
+ - Trigger create-hooks on backup restore task
+
+v 7.11.4
+ - Fix missing bullets when creating lists
+ - Set rel="nofollow" on external links
+
+v 7.11.3
+ - no changes
- Fix upgrader script (Martins Polakovs)
v 7.11.2
@@ -14,9 +62,12 @@ v 7.11.2
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.
+ - Allow special character in users bio. I.e.: I <3 GitLab
+
+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)
@@ -47,7 +98,6 @@ v 7.11.0
- 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.
@@ -170,12 +220,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
@@ -184,7 +234,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
@@ -489,6 +539,12 @@ v 7.5.0
- 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
- Fix security issue for member api
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 895202b58e2..38fa66816a7 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.
@@ -86,7 +84,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.
@@ -160,8 +160,9 @@ 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. [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).
-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 +178,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
diff --git a/Gemfile b/Gemfile
index c47a947cab0..0ab0a45cdb5 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"
@@ -31,6 +28,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"
@@ -44,18 +42,26 @@ 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.2'
# 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
+# 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.
gem "gitlab-linguist", "~> 3.0.1", require: "linguist"
# API
@@ -172,7 +178,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'
@@ -186,23 +192,23 @@ 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'
+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"
-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 '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 "virtus"
-gem 'addressable'
+gem 'select2-rails'
+gem 'virtus'
group :development do
gem 'brakeman', require: false
@@ -239,7 +245,7 @@ group :development, :test do
gem 'minitest', '~> 5.3.0'
# Generate Fake data
- gem "ffaker"
+ gem 'ffaker', '~> 2.0.0'
# Guard
gem 'guard-rspec'
@@ -253,11 +259,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
@@ -277,4 +285,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 529131f09b0..c9b8fc1f554 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)
@@ -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
@@ -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.2)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
@@ -300,14 +300,8 @@ 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)
+ jquery-rails (3.1.2)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
jquery-scrollto-rails (1.4.3)
@@ -333,6 +327,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 +385,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)
@@ -400,7 +399,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)
@@ -418,10 +416,10 @@ 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.2.0)
+ rack-attack (4.3.0)
rack
rack-cors (0.2.9)
rack-mini-profiler (0.9.0)
@@ -450,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)
@@ -523,6 +519,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)
@@ -531,7 +530,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)
@@ -547,9 +546,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)
@@ -589,11 +588,13 @@ 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)
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)
@@ -606,8 +607,13 @@ 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
+ 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)
@@ -633,7 +639,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)
@@ -654,6 +660,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)
@@ -713,7 +721,7 @@ DEPENDENCIES
email_spec
enumerize
factory_girl_rails
- ffaker
+ ffaker (~> 2.0.0)
fog (~> 1.14)
font-awesome-rails (~> 4.2)
foreman
@@ -723,7 +731,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.2)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.2.1)
gollum-lib (~> 4.0.2)
@@ -737,9 +745,8 @@ DEPENDENCIES
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
httparty
- jasmine-rails
jquery-atwho-rails (~> 1.0.0)
- jquery-rails
+ jquery-rails (= 3.1.2)
jquery-scrollto-rails
jquery-turbolinks
jquery-ui-rails
@@ -757,6 +764,7 @@ DEPENDENCIES
omniauth-gitlab
omniauth-google-oauth2
omniauth-kerberos
+ omniauth-saml
omniauth-shibboleth
omniauth-twitter
org-ruby (= 0.9.12)
@@ -764,12 +772,11 @@ 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)
rails (~> 4.1.0)
- rails_autolink (~> 1.1)
raphael-rails (~> 2.1.2)
rb-fsevent
rb-inotify
@@ -781,7 +788,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
@@ -798,15 +805,18 @@ 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)
- turbolinks
+ turbolinks (~> 2.5.0)
uglifier
underscore-rails (~> 1.4.4)
unf
diff --git a/VERSION b/VERSION
index e85691e6ff7..5f0902c7c6a 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-7.11.0.pre
+7.12.0.pre \ No newline at end of file
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index caf18c0d860..6a3f7386d5b 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,"<a href='$1'>$1</a>")
-
-window.simpleFormat = (str) ->
- linkify(sanitize(str).replace(/\n/g, '<br />'))
-
window.unbindEvents = ->
- $(document).unbind('scroll')
$(document).off('scroll')
window.shiftWindow = ->
@@ -116,7 +106,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()
@@ -174,6 +167,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')
@@ -189,14 +186,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/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 2baaf430d98..da56e3cdbc8 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'
diff --git a/app/assets/javascripts/extensions/jquery.js.coffee b/app/assets/javascripts/extensions/jquery.js.coffee
index 40fb6cb9fc3..0a9db8eb5ef 100644
--- a/app/assets/javascripts/extensions/jquery.js.coffee
+++ b/app/assets/javascripts/extensions/jquery.js.coffee
@@ -1,13 +1,11 @@
-$.fn.showAndHide = ->
- $(@).show().
- 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/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index 86ad3d03bac..74d6b80be5e 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 7c1e2b822d7..b8f916b5223 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -1,17 +1,31 @@
-#= require jquery
-#= require bootstrap
+#= require jquery.waitforimages
#= 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.activateTabFromPath()
this.initMergeWidget()
this.$('.show-all-commits').on 'click', =>
@@ -65,8 +79,18 @@ 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)
+ tab_action = $target.data('action')
+
+ # Lazy-load diffs
+ if tab_action == 'diffs'
+ this.loadDiff() unless @diffs_loaded
+ $('.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()
@@ -84,21 +108,54 @@ 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()
+ # Activate a tab based on the current URL path
+ #
+ # 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
+ 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}"
+
+ # 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.
+ #
+ # See https://github.com/rails/turbolinks/issues/363
+ history.replaceState {turbolinks: true, url: new_state}, '', new_state
showState: (state) ->
$('.automerge_widget').hide()
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index f186fec2a0c..b9bd5c730bf 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/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
index de356fbec77..40459a9a155 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/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
index 6b534f29218..bb532194682 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 ed12bdcef22..3be14cb43dd 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
diff --git a/app/assets/javascripts/stat_graph_contributors_util.js.coffee b/app/assets/javascripts/stat_graph_contributors_util.js.coffee
index 1670f5c7bc1..cfe5508290f 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)
diff --git a/app/assets/javascripts/wikis.js.coffee b/app/assets/javascripts/wikis.js.coffee
index 66757565d3a..81cfc37b956 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/assets/javascripts/zen_mode.js.coffee b/app/assets/javascripts/zen_mode.js.coffee
index 0fb8f7ed75f..8a0564a9098 100644
--- a/app/assets/javascripts/zen_mode.js.coffee
+++ b/app/assets/javascripts/zen_mode.js.coffee
@@ -1,6 +1,8 @@
-class @ZenMode
- @fullscreen_prefix = 'fullscreen_'
+#= require dropzone
+#= require mousetrap
+#= require mousetrap/pause
+class @ZenMode
constructor: ->
@active_zen_area = null
@active_checkbox = null
@@ -12,34 +14,31 @@ 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
if checkbox.checked
# Disable other keyboard shortcuts in ZEN mode
Mousetrap.pause()
- @udpateActiveZenArea(checkbox)
+ @updateActiveZenArea(checkbox)
else
@exitZenMode()
$(document).on 'keydown', (e) =>
- if e.keyCode is $.ui.keyCode.ESCAPE
+ if e.keyCode is 27 # Esc
@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
@@ -47,21 +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()
- 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()
+ restoreScroll: (y) ->
+ window.scrollTo(window.pageXOffset, y)
diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss
index 596376c3970..3d7868fb7d2 100644
--- a/app/assets/stylesheets/base/variables.scss
+++ b/app/assets/stylesheets/base/variables.scss
@@ -1,10 +1,11 @@
$style_color: #474D57;
-$hover: #FFF3EB;
+$hover: #FFFAF1;
$gl-text-color: #222222;
$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/common.scss b/app/assets/stylesheets/generic/common.scss
index 1e569978cc8..1419a9cded9 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;
}
@@ -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 362b217a444..71afccba001 100644
--- a/app/assets/stylesheets/generic/header.scss
+++ b/app/assets/stylesheets/generic/header.scss
@@ -2,7 +2,14 @@
* Application Header
*
*/
+$header-height: 46px;
+
header {
+ &.navbar-empty {
+ background: #FFF;
+ border-bottom: 1px solid #EEE;
+ }
+
&.navbar-gitlab {
z-index: 100;
margin-bottom: 0;
@@ -11,161 +18,104 @@ header {
width: 100%;
.container {
+ background: #FFF;
width: 100% !important;
padding: 0;
-
- 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: #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;
}
}
- /** NAV block with links and profile **/
- .nav {
- float: right;
- margin-right: 0;
- }
-
.navbar-toggle {
color: #666;
margin: 0;
border-radius: 0;
+ position: absolute;
+ right: 2px;
&:hover {
background-color: #EEE;
}
}
}
-
- .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;
}
}
- .profile-pic {
- padding: 0px !important;
- width: 46px;
- height: 46px;
- margin-left: 5px;
- img {
- width: 46px;
- height: 46px;
+ .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 box
- *
- */
.search {
margin-right: 10px;
margin-left: 10px;
@@ -177,6 +127,7 @@ header {
}
.search-input {
+ width: 220px;
background-image: image-url("icon-search.png");
background-repeat: no-repeat;
background-position: 10px;
@@ -184,57 +135,74 @@ 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;
+ background-color: #f5f5f5;
+ border-color: #f5f5f5;
+
+ &:focus {
+ @include box-shadow(none);
+ outline: none;
+ border-color: #DDD;
+ background-color: #FFF;
+ }
}
}
}
-.search .search-input {
- width: 300px;
- &:focus {
- width: 330px;
- background-color: #FFF;
- }
-}
+@mixin collapsed-header {
+ .header-logo {
+ width: $sidebar_collapsed_width;
-@media (max-width: 1200px) {
- .search .search-input {
- width: 200px;
- &:focus {
- width: 230px;
+ h3 {
+ display: none;
}
}
-}
-@media (max-width: $screen-xs-max) {
- #nprogress .spinner {
- right: 35px !important;
+ .header-content {
+ .title {
+ margin-left: 30px;
+ }
}
}
@media (max-width: $screen-md-max) {
- .header-collapsed, .header-expanded {
- width: 52px;
+ header .container .title {
+ max-width: 43%;
+ }
- h3 {
- display: none;
- }
+ .header-collapsed, .header-expanded {
+ @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 {
+ font-size: 18px;
+
+ .title {
+ max-width: 70%;
+ }
+
+ .navbar-nav {
+ margin: 0px;
+ float: none !important;
+
+ .visible-xs, .visable-sm {
+ display: table-cell !important;
+ }
+ }
+
+ li {
+ display: table-cell;
+ width: 1%;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/generic/lists.scss
index 08bf6e943d2..c502d953c75 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 {
diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss
index b7f6fac5223..74108c1f086 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 754c5b53020..69bddc6f59e 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;
@@ -102,13 +100,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 +123,21 @@
.collapse-nav a {
left: 0px;
- width: 52px;
+ width: $sidebar_collapsed_width;
+ }
+
+ .sidebar-user {
+ .username {
+ display: none;
+ }
+
+ .avatar {
+ margin-bottom: 10px;
+ }
+
+ .logout-holder {
+ text-align: center;
+ }
}
}
}
@@ -170,3 +182,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/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss
index e5590897947..66767cb13cb 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/assets/stylesheets/generic/zen.scss b/app/assets/stylesheets/generic/zen.scss
index 26afc21a6ab..bcb8bbe3134 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/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 3165396a94d..f5ac7bd8805 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: 5px 0;
}
p:last-child {
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 280e8b57174..5a5fbc468a3 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;
@@ -93,3 +94,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/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index 83771480cbd..b93ea0f020e 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;
+ }
}
}
@@ -209,13 +211,14 @@ ul.nav.nav-projects-tabs {
line-height: 1.5;
}
- .well {
- padding: 14px;
+ .panel {
+ @include border-radius(3px);
- h4 {
+ .panel-heading, .panel-footer {
font-weight: normal;
- margin: 0;
- color: #555;
+ background-color: transparent;
+ color: #666;
+ border-color: #EEE;
}
.actions {
@@ -224,10 +227,12 @@ ul.nav.nav-projects-tabs {
.nav-pills a {
padding: 10px;
+ font-weight: bold;
+ color: $gl-link-color;
}
.nav {
- margin: 10px 0;
+ margin-bottom: 15px;
}
}
diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss
index 139b3cc1ac4..1b06b4aa925 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;
@@ -19,8 +20,6 @@
}
.page-with-sidebar {
- background: $color-darker;
-
.collapse-nav a {
color: #FFF;
background: $color;
@@ -29,6 +28,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/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 4c35622fff1..a01e2a907d7 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -38,11 +38,13 @@ 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,
:restricted_signup_domains_raw,
:version_check_enabled,
+ :user_oauth_applications,
restricted_visibility_levels: [],
)
end
diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb
index c301e61d1c7..285e8495342 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/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 2dfae13ac5c..4d3e48f7f81 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/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index d36e359934c..06d6d61e907 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/application_controller.rb b/app/controllers/application_controller.rb
index 8ce881c7414..62d46a5482e 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
@@ -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/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index a11c554a2af..040255f08e6 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/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 34f0b257db3..2e381822e42 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/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index 507b8290a2b..fc31118124b 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/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index dcd949a71de..a767815b311 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/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 30ee6891733..42579b3eb44 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(32)
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/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 8c1bbf76917..40e2b37912b 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/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index 57fc48ac7da..76062446c92 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/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index c7467e9b2f5..71d3051ab88 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/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index d7fbc979067..b110de11013 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/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index dc430351551..4ca5fc65459 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/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 830751a989f..6ccc7934f2f 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(current_user)
respond_to do |format|
format.html { redirect_to new_user_session_path, notice: "Account successfully removed." }
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index b89b4c27350..4d976fe6630 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/app/finders/README.md b/app/finders/README.md
index 1f46518d230..1a1c69dea38 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 b8f367c6339..0bed2115dc7 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
@@ -75,6 +148,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'
@@ -85,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
@@ -113,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
@@ -151,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/helpers/application_helper.rb b/app/helpers/application_helper.rb
index bcd400b7e7b..a539ec49f7a 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
@@ -330,7 +326,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/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 241d6075c9f..63c3ff5674d 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/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 9fe5f82f02f..50df3801703 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/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 7bcc011fd5f..2777944fc9d 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,36 +23,44 @@ module GitlabMarkdownHelper
gfm_body = gfm(escaped_body, {}, html_options)
- gfm_body.gsub!(%r{<a.*?>.*?</a>}m) do |match|
- "</a>#{match}#{link_to("", url, html_options)[0..-5]}" # "</a>".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
+ 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
@@ -135,15 +145,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
diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb
index 8272c177d59..8036303851b 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/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 96d2606f1a1..94ce6646634 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
@@ -286,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/mailers/emails/projects.rb b/app/mailers/emails/projects.rb
index 9cb7077e59d..4a6e18e6a74 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 << " "
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 85a15596f8d..4e6c60dc8ca 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -101,6 +101,27 @@ 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.issues_enabled or project.merge_requests_enabled
+ rules -= named_abilities('label')
+ rules -= named_abilities('milestone')
+ end
+
+ unless project.snippets_enabled
+ rules -= named_abilities('project_snippet')
+ end
+
+ unless project.wiki_enabled
+ rules -= named_abilities('wiki')
+ end
+
rules
end
end
@@ -272,5 +293,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/models/application_setting.rb b/app/models/application_setting.rb
index d5123249c53..80463ee8841 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -18,6 +18,8 @@
# default_project_visibility :integer
# default_snippet_visibility :integer
# restricted_signup_domains :text
+# user_oauth_applications :bool default(TRUE)
+# after_sign_out_path :string(255)
#
class ApplicationSetting < ActiveRecord::Base
@@ -30,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/models/commit.rb b/app/models/commit.rb
index be5a118bfec..f02fe240540 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
@@ -56,6 +58,34 @@ class Commit
@raw.id
end
+ def ==(other)
+ (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})?
+ (?<commit>\h{6,40})
+ }x
+ 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
@@ -126,11 +156,6 @@ class Commit
Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message)
end
- # Mentionable override.
- def gfm_reference
- "commit #{id}"
- end
-
def author
User.find_for_commit(author_email, author_name)
end
diff --git a/app/models/commit_range.rb b/app/models/commit_range.rb
index e6456198264..86fc9eb01a3 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
@@ -28,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})?
+ (?<commit_range>#{PATTERN})
+ }x
+ end
+
# Initialize a CommitRange
#
# range_string - The String commit range.
@@ -59,6 +74,17 @@ class CommitRange
"#{sha_from[0..7]}#{notation}#{sha_to[0..7]}"
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)
+ reference = project.to_reference + '@' + reference
+ end
+
+ reference
+ 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/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index b7c39df885d..6f9f54d08cc 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.
- def gfm_reference
- raise NotImplementedError.new("#{self.class} does not implement #gfm_reference")
+ # 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(from_project = nil)
+ # "MergeRequest" > "merge_request" > "Merge request" > "merge request"
+ friendly_name = self.class.to_s.underscore.humanize.downcase
+
+ "#{friendly_name} #{to_reference(from_project)}"
end
# Construct a String that contains possible GFM references.
diff --git a/app/models/concerns/referable.rb b/app/models/concerns/referable.rb
new file mode 100644
index 00000000000..cced66cc1e4
--- /dev/null
+++ b/app/models/concerns/referable.rb
@@ -0,0 +1,61 @@
+# == 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
+
+ # Regexp pattern used to match references to this object
+ #
+ # This must be overridden by the including class.
+ #
+ # Returns a Regexp
+ def reference_pattern
+ raise NotImplementedError, "#{self} does not implement #{__method__}"
+ end
+ end
+
+ private
+
+ # Check if a reference is being done cross-project
+ #
+ # from_project - Refering Project object
+ def cross_project_reference?(from_project)
+ if self.is_a?(Project)
+ self != from_project
+ else
+ from_project && self.project && self.project != from_project
+ end
+ end
+end
diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb
index 85fdb12bfdc..49f6c95e045 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
@@ -26,4 +28,13 @@ class ExternalIssue
def project
@project
end
+
+ # Pattern used to extract `JIRA-123` issue references from text
+ def self.reference_pattern
+ %r{(?<issue>([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 687458adac4..051c672cb33 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,18 @@ class Group < Namespace
def sort(method)
order_by(method)
end
+
+ def reference_prefix
+ User.reference_prefix
+ end
+
+ def reference_pattern
+ User.reference_pattern
+ end
+ end
+
+ def to_reference(_from_project = nil)
+ "#{self.class.reference_prefix}#{name}"
end
def human_name
@@ -87,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
diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb
index 7e4f16ebf16..ab055f6b80b 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/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index 21867a9316c..ca7066b959a 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 5b38ade2e6b..b55e217975f 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 ee32b49bc66..6fb2d421026 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 e9fd441352d..46fb85336e5 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/models/issue.rb b/app/models/issue.rb
index 6e102051387..2456b7d0dc1 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
@@ -53,10 +54,28 @@ class Issue < ActiveRecord::Base
attributes
end
- # Mentionable overrides.
+ def self.reference_prefix
+ '#'
+ end
+
+ # 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)}(?<issue>\d+)
+ }x
+ 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
- def gfm_reference
- "issue ##{iid}"
+ reference
end
# Reset issue events cache
diff --git a/app/models/label.rb b/app/models/label.rb
index eee28acefc1..230631b5180 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,45 @@ class Label < ActiveRecord::Base
alias_attribute :name, :title
+ def self.reference_prefix
+ '~'
+ end
+
+ # Pattern used to extract label references from text
+ def self.reference_pattern
+ %r{
+ #{reference_prefix}
+ (?:
+ (?<label_id>\d+) | # Integer-based label ID, or
+ (?<label_name>
+ [A-Za-z0-9_-]+ | # String-based single-word label title, or
+ "[^&\?,]+" # 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)
+ #
+ # 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 && !name.include?('"')
+ %(#{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 64f3c39f131..f1f9f23b12c 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"
@@ -133,7 +134,31 @@ 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 self.reference_prefix
+ '!'
+ 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)}(?<merge_request>\d+)
+ }x
+ 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
@@ -172,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
@@ -289,11 +313,6 @@ class MergeRequest < ActiveRecord::Base
end
end
- # Mentionable override.
- def gfm_reference
- "merge request !#{iid}"
- end
-
def target_project_path
if target_project
target_project.path_with_namespace
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 9bbb2bafb98..9c543b37023 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/app/models/namespace.rb b/app/models/namespace.rb
index 211dfa76b81..03d2ab165ea 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,18 @@ 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"
+
+ 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/models/note.rb b/app/models/note.rb
index 6939a7e73a0..d5f716b3de0 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/models/project.rb b/app/models/project.rb
index 09d3ffd22fe..3c9f0dad28b 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
@@ -247,6 +248,11 @@ class Project < ActiveRecord::Base
order_by(method)
end
end
+
+ def reference_pattern
+ name_pattern = Gitlab::Regex::NAMESPACE_REGEX_STR
+ %r{(?<project>#{name_pattern}/#{name_pattern})}
+ end
end
def team
@@ -305,6 +311,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/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index 949a4d7111b..a9354754686 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/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 38cb64f8c48..6761f00183e 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/app/models/project_wiki.rb b/app/models/project_wiki.rb
index 0706a1ca0d1..231973fa543 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)
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 1b8c74028d9..1ca97017637 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -370,8 +370,55 @@ class Repository
@root_ref ||= raw_repository.root_ref
end
+ def commit_file(user, path, content, 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] = {
+ content: content,
+ path: path
+ }
+
+ 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/models/snippet.rb b/app/models/snippet.rb
index d2af26539b6..3ab9e834c63 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -16,14 +16,16 @@
#
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
- belongs_to :author, class_name: "User"
+ belongs_to :author, class_name: 'User'
+ belongs_to :project
has_many :notes, as: :noteable, dependent: :destroy
@@ -50,6 +52,30 @@ class Snippet < ActiveRecord::Base
participant :author, :notes
+ def self.reference_prefix
+ '$'
+ 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)}(?<snippet>\d+)
+ }x
+ 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 4dd37e73564..596dc7ea33a 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,18 @@ class User < ActiveRecord::Base
def build_user(attrs = {})
User.new(attrs)
end
+
+ def reference_prefix
+ '@'
+ end
+
+ # Pattern used to extract `@user` user references from text
+ def reference_pattern
+ %r{
+ #{Regexp.escape(reference_prefix)}
+ (?<user>#{Gitlab::Regex::NAMESPACE_REGEX_STR})
+ }x
+ end
end
#
@@ -257,6 +271,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
@@ -465,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
@@ -637,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
@@ -671,4 +695,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 00000000000..9017a63af3b
--- /dev/null
+++ b/app/services/delete_user_service.rb
@@ -0,0 +1,16 @@
+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.personal_projects.each do |project|
+ # 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
+ end
+ end
+end
diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb
new file mode 100644
index 00000000000..d929a676293
--- /dev/null
+++ b/app/services/destroy_group_service.rb
@@ -0,0 +1,17 @@
+class DestroyGroupService
+ attr_accessor :group, :current_user
+
+ def initialize(group, user)
+ @group, @current_user = group, user
+ end
+
+ def execute
+ @group.projects.each do |project|
+ # 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
+ end
+end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index bd245100955..4d02752454e 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -13,5 +13,12 @@ module Files
def repository
project.repository
end
+
+ def after_commit(sha)
+ commit = repository.commit(sha)
+ full_ref = 'refs/heads/' + (params[:new_branch] || 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 23833aa78ec..0a80455bc6b 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)
@@ -33,16 +33,24 @@ module Files
end
end
+ content =
+ if params[:encoding] == 'base64'
+ Base64.decode64(params[:content])
+ else
+ params[:content]
+ end
- new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path)
- created_successfully = new_file_action.commit!(
- params[:content],
+ sha = repository.commit_file(
+ current_user,
+ file_path,
+ content,
params[:commit_message],
- params[:encoding],
- params[:new_branch]
+ params[:new_branch] || ref
)
- 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/delete_service.rb b/app/services/files/delete_service.rb
index 1497a0f883b..2281777604c 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)
@@ -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")
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index 0724d3ae634..013cc1ee322 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)
@@ -19,14 +19,22 @@ 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],
+ content =
+ if params[:encoding] == 'base64'
+ Base64.decode64(params[:content])
+ else
+ params[:content]
+ end
+
+ sha = repository.commit_file(
+ current_user,
+ path,
+ content,
params[:commit_message],
- params[:encoding],
- params[:new_branch]
+ 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)
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index bdf36af02fd..cde65349d5c 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)
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 8960235b093..1d99223cfe6 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -15,4 +15,15 @@ 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
+
+ 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/issues/update_service.rb b/app/services/issues/update_service.rb
index 8f04a69287a..6af942a5ca4 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 23af2656c37..4f6c6cba9a9 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 dont 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]
@@ -41,6 +42,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
@@ -50,6 +57,15 @@ 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
+
+ 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/notes/create_service.rb b/app/services/notes/create_service.rb
index d19a6c2eca3..0ff37c41743 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/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 7e1d753b021..403f419ec50 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -1,28 +1,69 @@
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)
+ # 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)
+
+ if gitlab_shell.mv_repository(path, new_path)
+ log_info("Repository \"#{path}\" moved to \"#{new_path}\"")
+ GitlabShellWorker.perform_in(5.minutes, :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/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 0614f8689a4..b6801a92330 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.
#
@@ -130,6 +130,44 @@ 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 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
@@ -138,11 +176,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 +188,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 +219,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 +245,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 +257,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 +281,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:
#
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 4ceae814805..188a08940ab 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
@@ -70,6 +70,11 @@
= 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
= f.text_area :sign_in_text, class: 'form-control', rows: 4
@@ -83,6 +88,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/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 367d25cd6a1..6405a69fad3 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/admin/deploy_keys/show.html.haml b/app/views/admin/deploy_keys/show.html.haml
deleted file mode 100644
index ea361ca4bdb..00000000000
--- 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/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index fe648470233..45dee86b017 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'
&nbsp;
- = 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 7fc85206109..f7195ac3326 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/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index 5ecd53cff84..cfb386e131f 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
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index dfdf0d68c8f..0dd2edbb1bc 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 a7e1b08a0a4..61d2fbe538c 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/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml
index 21e730bb7ff..d6f3e029a38 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'
&nbsp;
- = 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'
&nbsp;
%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 24f0bcb60d5..0d204ced7ea 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/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 1da702be384..34a7c00dc43 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
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index 56b1948a474..ec39a755f0f 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 @@
&nbsp;
- 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/groups/issues.html.haml b/app/views/groups/issues.html.haml
index 6a3da6adacf..e0756e909be 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 268f33d5761..3d9e857cc52 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/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml
index 30093d2d05d..ba30e6e07c6 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'
&nbsp;
- = 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'
&nbsp;
%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 6c41cd6b9e4..8f2decb851f 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
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 1678311141e..0687840af39 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, pipeline: :description)
%hr
= render 'shared/show_aside'
diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml
deleted file mode 100644
index ef685a0434e..00000000000
--- a/app/views/layouts/_head_panel.html.haml
+++ /dev/null
@@ -1,48 +0,0 @@
-%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
- .container
- %div.app_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
-
- %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')
- %li
- = link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('globe')
- %li
- = link_to user_snippets_path(current_user), title: 'Your snippets', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('clipboard')
- - if current_user.is_admin?
- %li
- = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('cogs')
- - 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')
- %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 5c55bdb5465..c1283734d25 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
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 8a297566d6c..00000000000
--- a/app/views/layouts/_public_head_panel.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
- .container
- %div.app_logo
- = link_to explore_root_path, class: "home" do
- = brand_header_logo
- %h3 GitLab
- %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/_search.html.haml b/app/views/layouts/_search.html.haml
index 04f79846858..e2d2dec7ab8 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
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index a97feeb1ecd..155825cc4c2 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 5a59c9fd59a..d406f5764a7 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 aa0f3f0a819..2e3a2b16eb7 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/header/empty"
.container.navless-container
= render "layouts/flash"
.error-page
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
new file mode 100644
index 00000000000..2970af377f5
--- /dev/null
+++ b/app/views/layouts/header/_default.html.haml
@@ -0,0 +1,43 @@
+%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
+ .container
+ .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
+ .header-content
+ %h1.title
+ = title
+
+ %button.navbar-toggle
+ %span.sr-only Toggle navigation
+ = icon('bars')
+
+ .navbar-collapse.collapse
+ %ul.nav.navbar-nav.pull-right
+ %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?
+ %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/_empty_head_panel.html.haml b/app/views/layouts/header/_empty.html.haml
index 358caa3868b..16fbf6d4020 100644
--- a/app/views/layouts/_empty_head_panel.html.haml
+++ b/app/views/layouts/header/_empty.html.haml
@@ -1,4 +1,4 @@
-%header.navbar.navbar-fixed-top.navbar-gitlab
+%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/header/_public.html.haml b/app/views/layouts/header/_public.html.haml
new file mode 100644
index 00000000000..6a031722aaa
--- /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'
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 62f0579d48b..9f1654b25b4 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 21260302a09..7dd14449def 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')
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index 00c7cedce40..ee1b57278b6 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
diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml
index 9799b4cc4d7..3193206fe12 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/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb
index 0cc62935498..fc64c98038b 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 f08039ad045..bdcca6e4ab7 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 %>
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index a374a662333..12f83aae04b 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
- &plus;
+ &#43;
= diff.new_path
- else
= diff.new_path
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 06bad7dd84a..a26d4e0c757 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -1,13 +1,18 @@
- 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
.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
@@ -21,21 +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?
- %fieldset
- - if current_user.otp_required_for_login
- %legend.text-success
- = icon('check')
- Two-factor Authentication enabled
- %div
+ .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
@@ -43,9 +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
- %legend Two-factor Authentication
- %div
+ - else
%p
Increase your account's security by enabling two-factor authentication (2FA).
%p
@@ -55,51 +60,57 @@
= 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'
- &nbsp;
- .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'
+ &nbsp;
+ .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
- %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"
-
+ .panel-body
+ - 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/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml
index c4f6f59624b..2c4f0804f0b 100644
--- a/app/views/profiles/applications.html.haml
+++ b/app/views/profiles/applications.html.haml
@@ -1,34 +1,44 @@
- page_title "Applications"
%h3.page-title
- Application Settings
+ = 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
-%fieldset.oauth-applications
- %legend Your applications
- %p= 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
-%fieldset.oauth-authorized-applications.prepend-top-20
- %legend Authorized applications
+.oauth-authorized-applications.prepend-top-20
+ - if user_oauth_applications?
+ %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 af284f60409..f450ec1c018 100644
--- a/app/views/profiles/design.html.haml
+++ b/app/views/profiles/design.html.haml
@@ -1,54 +1,56 @@
- 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
= 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
+ .panel-body
+ .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.classic
+ = f.radio_button :theme_id, 2
+ Charcoal
- = label_tag do
- .prev.modern
- = f.radio_button :theme_id, 3
- Green
+ = 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.gray
+ = f.radio_button :theme_id, 4
+ Gray
- = label_tag do
- .prev.violet
- = f.radio_button :theme_id, 5
- Violet
+ = 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
+ = label_tag do
+ .prev.blue
+ = f.radio_button :theme_id, 6
+ Blue
+ %br
+ .clearfix
- %fieldset.code-preview-theme
- %legend
+ .panel.panel-default.code-preview-theme
+ .panel-heading
Code preview theme
- .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
+ .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
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 2c0d0e10a4c..66812872c41 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -1,23 +1,27 @@
- page_title "Emails"
%h3.page-title
- Email Settings
+ = 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})
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index fe5770f45c3..9bbccbc45ea 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 8bac22a2e1a..e0ae4d9720f 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/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml
index e3af0d4e189..06655f7ba3a 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 a74d97dac3b..9480a19f5b2 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 21dabbdfe2c..399ae98adf9 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 29c30905117..6534afb0e89 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?
@@ -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.
diff --git a/app/views/profiles/two_factor_auths/new.html.haml b/app/views/profiles/two_factor_auths/new.html.haml
index fe03a259a12..b9f3e2380fe 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/app/views/projects/_aside.html.haml b/app/views/projects/_aside.html.haml
index e90c7b26dd2..c9c17110d2b 100644
--- a/app/views/projects/_aside.html.haml
+++ b/app/views/projects/_aside.html.haml
@@ -1,96 +1,108 @@
.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 forked_from_project = @project.forked_from_project
+ .panel-footer
+ = 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
- .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'
+ - @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
- = icon("archive fw")
+ .panel.panel-default
+ .panel-heading
+ = icon("folder-o 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")
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
diff --git a/app/views/projects/_dropdown.html.haml b/app/views/projects/_dropdown.html.haml
deleted file mode 100644
index d623a3716ed..00000000000
--- 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/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index f9cdda4a3ba..076afb11a9d 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, pipeline: :description)
- if can?(current_user, :admin_project, @project)
&ndash;
= link_to 'Edit', edit_namespace_project_path
diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml
index 2292aaaa214..2c5b24b8130 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 <strong>Work In Progress</strong>.
- When it's ready, remove the <code>WIP</code> prefix from the title to allow it to be accepted.
+ Remove the <code>WIP</code> prefix from the title to allow this
+ <strong>Work In Progress</strong> 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 <strong>Work In Progress</strong> by starting the title with <code>[WIP]</code> or <code>WIP:</code>.
+ Start the title with <code>[WIP]</code> or <code>WIP:</code> to prevent a
+ <strong>Work In Progress</strong> merge request from being accepted before it's ready.
.form-group.issuable-description
= f.label :description, 'Description', class: 'control-label'
.col-sm-10
@@ -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/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml
index 462f5b7afb0..8019c7f4569 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/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 96f188e4aa7..9c3e1703c89 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
diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml
index c577dfa8d55..8d66bae8cdf 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?
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 7d44652af72..00000000000
--- 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
- &larr; 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/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index d4b019780f5..99ee23a1ddc 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
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index 808c03148f4..eadbf61fdd4 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -35,6 +35,13 @@
%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
= f.label :issues_events, class: 'list-label' do
@@ -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/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 2016f5c709c..48858fa32da 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 ef36d1f9547..a4e25e5ce88 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/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 709ea1f7897..1d5597602d1 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -14,11 +14,11 @@
= 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
- = render 'shared/issuable_filter'
+ = render 'shared/issuable_filter', type: :issues
.issues-holder
= render "issues"
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index 82829452862..7fa1ee53f76 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -1,8 +1,8 @@
%li{id: dom_id(label)}
- = render_colored_label(label)
+ = link_to_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/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 7d19415a7f4..d44fe486212 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
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index 9a2aa9c3de0..eb3dba6858d 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 5d5a23b5409..65f5c3d6a19 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -4,17 +4,16 @@
= 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
%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/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 24a9563dd4d..9a2edbf0a8c 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 url_for(params), data: {target: '#commits', 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 url_for(params), data: {target: '#diffs', 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: '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 c2f5cdacae7..74f8b9950cf 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -22,44 +22,44 @@
%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} }
- %i.fa.fa-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"
- 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 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{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_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{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_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
- .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/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index ab845a7e719..841d1e1cfe9 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/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml
index 882b219f6e2..906cc11dc67 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
&nbsp;
- 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 <strong>Work In Progress</strong>.
+ This request cannot be merged because it is marked as <strong>Work In Progress</strong>.
%p
%button.btn.disabled{:type => 'button'}
%i.fa.fa-warning
Accept Merge Request
&nbsp;
-
- 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
&nbsp;
- Merge is in progress. Please wait. Page will be automatically reloaded. &nbsp;
+ Merge is in progress. Please wait… Page will be reloaded automatically. &nbsp;
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 46e92a9c558..0690fdb769f 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 44bd9347f51..6396232db22 100644
--- a/app/views/projects/merge_requests/show/_state_widget.html.haml
+++ b/app/views/projects/merge_requests/show/_state_widget.html.haml
@@ -11,17 +11,17 @@
- if @merge_request.closed?
%h4
- Closed
+ 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
- if @merge_request.merged?
%h4
- Merged
+ 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"
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 62360158ff9..14a0580f966 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'
&nbsp;
- = 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'
&nbsp;
%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 bba2b8764ac..417eaa1b09d 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -61,12 +61,13 @@
Participants
%span.badge= @users.count
- - if @project.issues_enabled
- .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
- = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link btn-grouped"
+ - if can?(current_user, :read_issue, @project)
+ = 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
@@ -85,10 +86,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/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml
index 635e4d70941..860a997cff8 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 @@
&nbsp;
- 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
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index 4f3f4cab8d5..7d9bd08385a 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/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index 6834969de8b..b2c085f34b1 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/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index 2efa616d664..adfdd1c7506 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
diff --git a/app/views/shared/_issuable_filter.html.haml b/app/views/shared/_issuable_filter.html.haml
index 4ab9421f013..a5187fa4ea7 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
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index 9610f9ce414..2feeeecc48b 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"
diff --git a/bin/rake b/bin/rake
index 8017a0271d2..0fb4e07e13a 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 253ec37c345..7b45d374fcd 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
diff --git a/config.ru b/config.ru
index e90863a5c21..a2525c81361 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__)
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index fbc7f515f34..c7f22b9388b 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.
@@ -210,6 +214,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/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 2351ef7b0ce..c234bd69e9a 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/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb
index 38a5fa98dc2..5312fd8e89a 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 << '/specs'
+ Rack::MiniProfiler.config.skip_paths << '/teaspoon'
end
diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb
index 103aa06ca32..6f1f267bf97 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
diff --git a/config/routes.rb b/config/routes.rb
index bf2cb6421c5..f4a104664f3 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',
@@ -166,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
@@ -422,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
@@ -450,6 +449,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/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index ae4c0550a4f..87839770924 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 24952a1f661..378354efd5a 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 2296821e528..a43116829d9 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 e8b01b46d22..c636e96381c 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 f9b2fd8b05f..0825776ffaa 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 b3a6f39c7d5..3bd4b442ade 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 d37be53c7b9..566c0705638 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/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 00000000000..0097587b4f6
--- /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/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 00000000000..6a78294f0b2
--- /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/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 00000000000..83e08101407
--- /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 f7581eaf7fb..aea0742cf3b 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: 20150529150354) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -33,6 +33,8 @@ ActiveRecord::Schema.define(version: 20150509180749) do
t.integer "default_project_visibility"
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|
@@ -533,6 +535,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/api/README.md b/doc/api/README.md
index f6757b0a6aa..ca58c184543 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/merge_requests.md b/doc/api/merge_requests.md
index c1d82ad9576..7b0873a9111 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/doc/api/namespaces.md b/doc/api/namespaces.md
new file mode 100644
index 00000000000..7b3238441f6
--- /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/doc/install/installation.md b/doc/install/installation.md
index d167d2889bb..badea4de214 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -241,10 +241,7 @@ 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"
+ # 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
@@ -302,6 +299,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
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index b67f793c591..904d5d7fee2 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`.
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 24f7b4bb4b4..8e2a602ec35 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 00000000000..a8cc5c8f74a
--- /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.
+
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index 30c29084e34..9c7f723c06d 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.
-
- http://www.google.com
-
-http://www.google.com
+GFM will autolink almost any URL you copy and paste into your text.
+
+ * 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
+* 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
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index bca4fcfb404..ae2d465e0c1 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -299,3 +299,7 @@ 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.
+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
diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md
index 41a994f3f68..2aca91d5371 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/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index d140f3a457a..73717ffc7d6 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/doc/workflow/2fa.png b/doc/workflow/2fa.png
new file mode 100644
index 00000000000..bbf415210d5
--- /dev/null
+++ b/doc/workflow/2fa.png
Binary files differ
diff --git a/doc/workflow/2fa_auth.png b/doc/workflow/2fa_auth.png
new file mode 100644
index 00000000000..4a4fbe68984
--- /dev/null
+++ b/doc/workflow/2fa_auth.png
Binary files differ
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index b90a6a50af9..89005e51958 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -11,6 +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)
-- [Web Editor](web_editor.md) \ No newline at end of file
+- [Keyboard shortcuts](shortcuts.md)
+- [Web Editor](web_editor.md)
diff --git a/doc/workflow/shortcuts.md b/doc/workflow/shortcuts.md
new file mode 100644
index 00000000000..ffcb832cdd7
--- /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](shortcuts.png) \ No newline at end of file
diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png
new file mode 100644
index 00000000000..68756ed1f98
--- /dev/null
+++ b/doc/workflow/shortcuts.png
Binary files differ
diff --git a/doc/workflow/timezone.md b/doc/workflow/timezone.md
index b513088dbc5..7e08c0e51ac 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 config/application.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
+```
diff --git a/doc/workflow/two_factor_authentication.md b/doc/workflow/two_factor_authentication.md
new file mode 100644
index 00000000000..8ac1ca4b351
--- /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 **Account**.
+1. Click **Enable Two-factor Authentication**.
+
+![Two-factor setup](2fa.png)
+
+**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.
+
+![Two-factor authentication on sign in](2fa_auth.png)
+
+## 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
diff --git a/doc_styleguide.md b/doc_styleguide.md
new file mode 100644
index 00000000000..670af765f3a
--- /dev/null
+++ b/doc_styleguide.md
@@ -0,0 +1,22 @@
+# Documentation styleguide
+
+This styleguide recommends best practices to improve documentation and to keep it organized and easy to find.
+
+## 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 in each document, by adding '#' at the begining of it (when using markdown). For subtitles, use '##', '###' and so on.
+
+* Do not duplicate information.
+
+* Be brief and clear.
+
+
+## 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. \ No newline at end of file
diff --git a/docker/README.md b/docker/README.md
index 2e533ae9dd5..a73ccd0dba0 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:
diff --git a/features/admin/deploy_keys.feature b/features/admin/deploy_keys.feature
index 9df47eb51fd..33439cd1e85 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/dashboard/group.feature b/features/dashboard/group.feature
index cf4b8d7283b..e3c01db2ebb 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/project/merge_requests.feature b/features/project/merge_requests.feature
index 60caf783fe4..eb091c291e9 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
@@ -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/project/project.feature b/features/project/project.feature
index ae28312a69a..56ae5c78d01 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -62,3 +62,14 @@ 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
+
+ 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/project/wiki.feature b/features/project/wiki.feature
index 977cd609a11..7a70f348754 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/admin/deploy_keys.rb b/features/steps/admin/deploy_keys.rb
index fb0b611762e..844837d177d 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/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 8508b2a8096..bb1f2f444f9 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/dashboard/group.rb b/features/steps/dashboard/group.rb
index 8384df2fb59..aeea49320ff 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
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 228b83e5fd0..84348d1709a 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
diff --git a/features/steps/profile/notifications.rb b/features/steps/profile/notifications.rb
index 13e93618eb7..b6e03b549af 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 791982d16c3..32888eb17d9 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -3,25 +3,27 @@ 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
- 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
diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb
index 4b135202593..d06905285fe 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/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index f67e6e3d8ca..4ca7cf5e5fe 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
@@ -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
@@ -202,7 +205,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
@@ -302,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/features/steps/project/project.rb b/features/steps/project/project.rb
index 00706ab30e9..fcc15aacc21 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -102,4 +102,16 @@ 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
+
+ step 'I should not see "Snippets" button' do
+ page.should_not have_link 'Snippets'
+ end
end
diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb
index 717132da45d..58cb0ceb3f1 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
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index b60ac5e3423..3059c4ee041 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -14,6 +14,17 @@ 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
+ @project.save
+ end
+
# Add another user to project "Shop"
step 'I add a user to project "Shop"' do
@project = Project.find_by(name: "Shop")
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index f768c750402..e88b6e31775 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
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 2216a12a87a..d835dce2ded 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/lib/api/namespaces.rb b/lib/api/namespaces.rb
index b90ed6af5fb..50d3729449e 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/lib/api/project_hooks.rb b/lib/api/project_hooks.rb
index be9850367b9..ad4d2e65dfd 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/lib/api/users.rb b/lib/api/users.rb
index 032a5d76e43..7d4c68c7412 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/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index 050b5ba29dd..03cef30c97d 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 8538f3f6eca..00000000000
--- 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/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 530f9d93de4..172d4902add 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
diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb
index ab184d95c05..aeec595782c 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.rb b/lib/gitlab/markdown.rb
index c0fb22e7f36..fa9c0975bb8 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'
@@ -56,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,
@@ -103,6 +107,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/commit_range_reference_filter.rb b/lib/gitlab/markdown/commit_range_reference_filter.rb
index 8764f7e474f..61591a9914b 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}@)?(?<commit_range>#{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 b20b29f5d0c..f6932e76e70 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}@)?(?<commit>\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 c436fabd658..66c256c5104 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 = "(?<project>#{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 0fc3f4cca06..afd28dd8cf3 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 = /(?<issue>([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
@@ -51,7 +48,7 @@ module Gitlab
%(<a href="#{url}"
title="#{title}"
- class="#{klass}">#{issue}</a>)
+ class="#{klass}">#{match}</a>)
end
end
diff --git a/lib/gitlab/markdown/external_link_filter.rb b/lib/gitlab/markdown/external_link_filter.rb
new file mode 100644
index 00000000000..c539e0fb823
--- /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/lib/gitlab/markdown/issue_reference_filter.rb b/lib/gitlab/markdown/issue_reference_filter.rb
index 1e885615163..dea04761ead 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}?\#(?<issue>([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
@@ -57,7 +52,7 @@ module Gitlab
%(<a href="#{url}"
title="#{title}"
- class="#{klass}">#{project_ref}##{id}</a>)
+ class="#{klass}">#{match}</a>)
else
match
end
diff --git a/lib/gitlab/markdown/label_reference_filter.rb b/lib/gitlab/markdown/label_reference_filter.rb
index a357f28458d..e022ca69c91 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{
- ~(
- (?<label_id>\d+) | # Integer-based label ID, or
- (?<label_name>
- [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
@@ -84,11 +71,10 @@ module Gitlab
#
# Returns a Hash.
def label_params(id, name)
- if id > 0
- { id: id }
+ if name
+ { name: name.tr('"', '') }
else
- # TODO (rspeicher): Don't strip single quotes if we decide to only use double quotes for surrounding.
- { name: name.tr('\'"', '') }
+ { id: id }
end
end
end
diff --git a/lib/gitlab/markdown/merge_request_reference_filter.rb b/lib/gitlab/markdown/merge_request_reference_filter.rb
index 740d72abb36..80779819485 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}?!(?<merge_request>\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
@@ -57,7 +52,7 @@ module Gitlab
%(<a href="#{url}"
title="#{title}"
- class="#{klass}">#{project_ref}!#{id}</a>)
+ class="#{klass}">#{match}</a>)
else
match
end
diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb
index a4303d96bef..a84bacd3d4f 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
@@ -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/markdown/sanitization_filter.rb b/lib/gitlab/markdown/sanitization_filter.rb
index 88781fea0c8..74b3a8d274f 100644
--- a/lib/gitlab/markdown/sanitization_filter.rb
+++ b/lib/gitlab/markdown/sanitization_filter.rb
@@ -8,33 +8,54 @@ module Gitlab
# Extends HTML::Pipeline::SanitizationFilter with a custom whitelist.
class SanitizationFilter < HTML::Pipeline::SanitizationFilter
def whitelist
- whitelist = super
+ # Descriptions are more heavily sanitized, allowing only a few elements.
+ # See http://git.io/vkuAN
+ if pipeline == :description
+ whitelist = LIMITED
+ whitelist[:elements] -= %w(pre code img ol ul li)
+ 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
- unless customized?(whitelist[:transformers])
- # Allow code highlighting
- whitelist[:attributes]['pre'] = %w(class)
- whitelist[:attributes]['span'] = %w(class)
+ return if customized?(whitelist[:transformers])
- # Allow table alignment
- whitelist[:attributes]['th'] = %w(style)
- whitelist[:attributes]['td'] = %w(style)
+ # Allow code highlighting
+ whitelist[:attributes]['pre'] = %w(class)
+ whitelist[:attributes]['span'] = %w(class)
- # Allow span elements
- whitelist[:elements].push('span')
+ # Allow table alignment
+ whitelist[:attributes]['th'] = %w(style)
+ whitelist[:attributes]['td'] = %w(style)
- # Remove `rel` attribute from `a` elements
- whitelist[:transformers].push(remove_rel)
+ # Allow span elements
+ whitelist[:elements].push('span')
- # Remove `class` attribute from non-highlight spans
- whitelist[:transformers].push(clean_spans)
- end
+ # 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
- private
-
def remove_rel
lambda do |env|
if env[:node_name] == 'a'
@@ -53,10 +74,6 @@ module Gitlab
end
end
end
-
- def customized?(transformers)
- transformers.last.source_location[0] == __FILE__
- end
end
end
end
diff --git a/lib/gitlab/markdown/snippet_reference_filter.rb b/lib/gitlab/markdown/snippet_reference_filter.rb
index 64a0a2696f7..174ba58af6c 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}?\$(?<snippet>\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
@@ -57,7 +52,7 @@ module Gitlab
%(<a href="#{url}"
title="#{title}"
- class="#{klass}">#{project_ref}$#{id}</a>)
+ class="#{klass}">#{match}</a>)
else
match
end
diff --git a/lib/gitlab/markdown/user_reference_filter.rb b/lib/gitlab/markdown/user_reference_filter.rb
index 28ec041b1d4..c9972957182 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 = /@(?<user>#{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
@@ -68,7 +65,8 @@ module Gitlab
url = urls.namespace_project_url(project.namespace, project,
only_path: context[:only_path])
- %(<a href="#{url}" class="#{link_class}">@all</a>)
+ text = User.reference_prefix + 'all'
+ %(<a href="#{url}" class="#{link_class}">#{text}</a>)
end
def link_to_namespace(namespace)
@@ -86,7 +84,8 @@ module Gitlab
url = urls.group_url(group, only_path: context[:only_path])
- %(<a href="#{url}" class="#{link_class}">@#{group}</a>)
+ text = Group.reference_prefix + group
+ %(<a href="#{url}" class="#{link_class}">#{text}</a>)
end
def link_to_user(user, namespace)
@@ -94,7 +93,8 @@ module Gitlab
url = urls.user_url(user, only_path: context[:only_path])
- %(<a href="#{url}" class="#{link_class}">@#{user}</a>)
+ text = User.reference_prefix + user
+ %(<a href="#{url}" class="#{link_class}">#{text}</a>)
end
def user_can_reference_group?(group)
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index e35f848fa6e..e836b05ff25 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
+ %i(user label issue merge_request snippet commit commit_range).each do |type|
+ define_method("#{type}s") do
+ references[type]
+ end
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
+ private
- def snippets
- result = pipeline_result(:snippet)
- result.uniq
+ def markdown
+ @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML, GitlabMarkdownHelper::MARKDOWN_OPTIONS)
end
- def commits
- result = pipeline_result(:commit)
- result.uniq
- end
+ def references
+ @references ||= Hash.new do |references, type|
+ type = type.to_sym
+ return references[type] if references.has_key?(type)
- def commit_ranges
- result = pipeline_result(:commit_range)
- result.uniq
+ references[type] = pipeline_result(type).uniq
+ end
end
- private
-
# Instantiate and call HTML::Pipeline with a single reference filter type,
# returning the result
#
@@ -65,11 +48,12 @@ 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)
- result = pipeline.call(@_text)
+ result = pipeline.call(@text)
result[:references][filter_type]
end
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 0d37b9dea85..00000000000
--- 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/edit_file_action.rb b/lib/gitlab/satellite/files/edit_file_action.rb
deleted file mode 100644
index 3cb9c0b5ecb..00000000000
--- 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
deleted file mode 100644
index 6446b14568a..00000000000
--- a/lib/gitlab/satellite/files/file_action.rb
+++ /dev/null
@@ -1,25 +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
-
- 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 724dfa0d042..00000000000
--- 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
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
index 7dcecc2ecf6..2f7aff03c2a 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/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index b066a1a6935..946902e2f6d 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 9951bacedf5..cf7f4198cbf 100755
--- a/lib/support/init.d/gitlab.default.example
+++ b/lib/support/init.d/gitlab.default.example
@@ -29,3 +29,8 @@ 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 are using
+# shell other than "bash"
+# The default is "/bin/bash"
+shell_path="/bin/bash"
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 1a6303b6c82..75bd41f2838 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}
@@ -14,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
@@ -38,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? ... "
@@ -298,58 +328,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
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index e835d6cb9b7..afdaba11cb0 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
diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake
index 14a130be2ca..c95b6540ebc 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?
diff --git a/lib/tasks/jasmine.rake b/lib/tasks/jasmine.rake
index 9e2cceffa19..ac307a9e929 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
diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
index f05d1f5fbe1..65415f21e55 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(32).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 26e8a795fa4..9373a7af024 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 }
@@ -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(32)
end
end
@@ -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 25862614d28..00906e8087a 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/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb
index 133beba7b98..16d1ca55f8d 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/features/groups_spec.rb b/spec/features/groups_spec.rb
new file mode 100644
index 00000000000..edc1c63a0aa
--- /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 8f3dfc8d5a9..902968cebcb 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`
@@ -66,6 +68,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
@@ -145,7 +151,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
@@ -233,6 +239,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
@@ -344,13 +362,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 +394,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 +407,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/features/projects_spec.rb b/spec/features/projects_spec.rb
index cae11be7cdd..f8eea70ec4a 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,32 +1,57 @@
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
- expect { remove_project }.to change {Project.count}.by(-1)
+ 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) }
- it 'should delete the project from disk' do
- expect(GitlabShellWorker).to(
- receive(:perform_async).with(:remove_repository,
- /#{@project.path_with_namespace}/)
- ).twice
+ before do
+ login_with(user)
+ project.team << [user, :master]
+ visit edit_namespace_project_path(project.namespace, project)
+ end
- remove_project
+ it 'should remove project' do
+ expect { remove_project }.to change {Project.count}.by(-1)
end
end
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
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 69bac387d20..db20b23f87d 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 8536377a7f0..bc385fd0d69 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
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index 64817ec6700..02ab46c905a 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>span tag</span>
-<a href="#" rel="nofollow">This is a link with a defined rel attribute, which should be removed</a>
+<a href="#" rel="bookmark">This is a link with a defined rel attribute, which should be removed</a>
<a href="javascript:alert('Hi')">This is a link trying to be sneaky. It gets its link removed entirely.</a>
@@ -127,61 +127,68 @@ But it shouldn't autolink text inside certain tags:
- <a>http://about.gitlab.com/</a>
- <kbd>http://about.gitlab.com/</kbd>
-### Reference Filters (e.g., #<%= issue.iid %>)
+### ExternalLinkFilter
-References should be parseable even inside _!<%= merge_request.iid %>_ emphasis.
+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.
#### 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
diff --git a/spec/helpers/blob_helper_spec.rb b/spec/helpers/blob_helper_spec.rb
new file mode 100644
index 00000000000..e49e4e6d5d8
--- /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('<span id="LC1" class="line">:type &quot;assem&quot;))</span>')
+ end
+
+ it 'should highlight single block' do
+ expected = %Q[<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>
+<span id="LC2" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>]
+
+ 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 = [
+ '<span id="LC1" class="line"><span class="p">(</span><span class="nb">make-pathname</span> <span class="ss">:defaults</span> <span class="nv">name</span></span>',
+ '<span id="LC1" class="line"><span class="ss">:type</span> <span class="s">&quot;assem&quot;</span><span class="p">))</span></span>'
+ ]
+
+ result = split_content.map{ |content| highlight(blob_name, content, nowrap: true, continue: true) }
+ expect(result).to eq(expected)
+ end
+ end
+end
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 0d0418f84a7..bbb434638ce 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,10 +90,16 @@ describe GitlabMarkdownHelper do
end
it "escapes HTML passed in as the body" do
- actual = "This is a <h1>test</h1> - see ##{issues[0].iid}"
+ actual = "This is a <h1>test</h1> - see #{issues[0].to_reference}"
expect(link_to_gfm(actual, commit_path)).
to match('&lt;h1&gt;test&lt;/h1&gt;')
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(<a href="/foo">#{issues[0].to_reference}</a>)
+ end
end
describe '#render_wiki_content' do
diff --git a/spec/helpers/labels_helper_spec.rb b/spec/helpers/labels_helper_spec.rb
index 0b7e3b1d11f..0c8d06b7059 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
diff --git a/spec/javascripts/extensions/array_spec.js.coffee b/spec/javascripts/extensions/array_spec.js.coffee
new file mode 100644
index 00000000000..4ceac619422
--- /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)
diff --git a/spec/javascripts/extensions/jquery_spec.js.coffee b/spec/javascripts/extensions/jquery_spec.js.coffee
new file mode 100644
index 00000000000..b10e16b7d01
--- /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 '<input type="text" />'
+
+ 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 '<input type="text" disabled="disabled" class="disabled" />'
+
+ 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/fixtures/issuable.html.haml b/spec/javascripts/fixtures/issuable.html.haml
new file mode 100644
index 00000000000..42ab4aa68b1
--- /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 00000000000..0aecc7334fd
--- /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 00000000000..db5abe0cae3
--- /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 00000000000..c4329b8f94a
--- /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/fixtures/zen_mode.html.haml b/spec/javascripts/fixtures/zen_mode.html.haml
new file mode 100644
index 00000000000..e867e4de2b9
--- /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/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee
index 13b25862f57..268e4c68c31 100644
--- a/spec/javascripts/issue_spec.js.coffee
+++ b/spec/javascripts/issue_spec.js.coffee
@@ -1,34 +1,20 @@
-#= 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', ->
+ 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 3ebc4a4eed5..a4735af0343 100644
--- a/spec/javascripts/merge_request_spec.js.coffee
+++ b/spec/javascripts/merge_request_spec.js.coffee
@@ -1,34 +1,23 @@
-#= 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))
+ fixture.load('merge_requests_show.html')
+ @merge = new MergeRequest({})
- # This form is used to get the `update` URL. Not ideal.
- $container.append(affix(selectors.form))
+ it 'modifies the Markdown field', ->
+ spyOn(jQuery, 'ajax').and.stub()
- @merge = new MergeRequest({})
+ $('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)
diff --git a/spec/javascripts/notes_spec.js.coffee b/spec/javascripts/notes_spec.js.coffee
index de2e8e7f6c8..050b6e362c6 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 57dcc2161d3..a01ad7140dd 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/spec/javascripts/spec_helper.coffee b/spec/javascripts/spec_helper.coffee
new file mode 100644
index 00000000000..47b41dd2c81
--- /dev/null
+++ b/spec/javascripts/spec_helper.coffee
@@ -0,0 +1,46 @@
+# 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
+
+# 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
+# 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/javascripts/stat_graph_contributors_util_spec.js b/spec/javascripts/stat_graph_contributors_util_spec.js
index ee90892eb48..dbafe782b77 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")
})
})
diff --git a/spec/javascripts/support/jasmine.yml b/spec/javascripts/support/jasmine.yml
deleted file mode 100644
index 168c9618643..00000000000
--- 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 4d73aec5a31..00000000000
--- 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
-#
diff --git a/spec/javascripts/zen_mode_spec.js.coffee b/spec/javascripts/zen_mode_spec.js.coffee
new file mode 100644
index 00000000000..1f4ea58ad48
--- /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)
diff --git a/spec/lib/gitlab/backend/grack_auth_spec.rb b/spec/lib/gitlab/backend/grack_auth_spec.rb
index d0aad54f677..42c9946d2a9 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 2ac496fd669..00000000000
--- 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
diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb
index cb7b0fbb890..5d7ff4f6122 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/markdown/autolink_filter_spec.rb b/spec/lib/gitlab/markdown/autolink_filter_spec.rb
index 0bbdc11a979..a14cb2da089 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 7274cb309a0..e8391cc7aca 100644
--- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
@@ -2,40 +2,42 @@ require 'spec_helper'
module Gitlab::Markdown
describe CommitRangeReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
let(:project) { create(:project) }
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('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|
it "ignores valid references contained inside '#{elem}' element" do
- exp = act = "<#{elem}>Commit Range #{commit1.id}..#{commit2.id}</#{elem}>"
+ exp = act = "<#{elem}>Commit Range #{range.to_reference}</#{elem}>"
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
@@ -51,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(/\(<a.+>#{exp}<\/a>\.\)/)
end
@@ -65,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
@@ -95,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! }
@@ -106,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(/\(<a.+>#{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 cc32a4fcf03..a10d43c9a02 100644
--- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
@@ -2,14 +2,13 @@ require 'spec_helper'
module Gitlab::Markdown
describe CommitReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
let(:project) { create(:project) }
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(/\(<a.+>#{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/emoji_filter_spec.rb b/spec/lib/gitlab/markdown/emoji_filter_spec.rb
index 18d55c4818f..11efd9bb4cd 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 b19bc125b92..f16095bc2b2 100644
--- a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
@@ -2,26 +2,25 @@ require 'spec_helper'
module Gitlab::Markdown
describe ExternalIssueReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
def helper
IssuesHelper
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}</#{elem}>"
+ exp = act = "<#{elem}>Issue #{reference}</#{elem}>"
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}</#{elem}>"
- 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/external_link_filter_spec.rb b/spec/lib/gitlab/markdown/external_link_filter_spec.rb
new file mode 100644
index 00000000000..a040b34577b
--- /dev/null
+++ b/spec/lib/gitlab/markdown/external_link_filter_spec.rb
@@ -0,0 +1,31 @@
+require 'spec_helper'
+
+module Gitlab::Markdown
+ describe ExternalLinkFilter do
+ include FilterSpecHelper
+
+ it 'ignores elements without an href attribute' do
+ exp = act = %q(<a id="ignored">Ignore Me</a>)
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'ignores non-HTTP(S) links' do
+ exp = act = %q(<a href="irc://irc.freenode.net/gitlab">IRC</a>)
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'skips internal links' do
+ internal = Gitlab.config.gitlab.url
+ exp = act = %Q(<a href="#{internal}/sign_in">Login</a>)
+ expect(filter(act).to_html).to eq exp
+ end
+
+ it 'adds rel="nofollow" to external links' do
+ act = %q(<a href="https://google.com/">Google</a>)
+ doc = filter(act)
+
+ expect(doc.at_css('a')).to have_attribute('rel')
+ expect(doc.at_css('a')['rel']).to eq 'nofollow'
+ end
+ end
+end
diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
index 08382b3e7e8..fa43d33794d 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
@@ -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}</#{elem}>"
+ exp = act = "<#{elem}>Issue #{issue.to_reference}</#{elem}>"
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 9f898837466..cf3337b1ba1 100644
--- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
@@ -3,15 +3,14 @@ require 'html/pipeline'
module Gitlab::Markdown
describe LabelReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
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,60 +93,41 @@ 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 '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.name}'" }
-
- 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(\(<a.+><span.+>#{label.name}</span></a>\.\)))
- end
-
- it 'ignores invalid label names' do
- exp = act = "Label ~'#{label.name.reverse}'"
+ it 'links to a valid reference' do
+ doc = filter("See #{reference}")
- 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.name}") }
-
- 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(\(<a.+><span.+>#{label.name}</span></a>\.\)))
- end
+ it 'links with adjacent text' do
+ doc = filter("Label (#{reference}.)")
+ expect(doc.to_html).to match(%r(\(<a.+><span.+>#{label.name}</span></a>\.\)))
+ end
- it 'ignores invalid label names' do
- exp = act = %(Label ~"#{label.name.reverse}")
+ 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
+
+ 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
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 d6e745114f2..5945302a2da 100644
--- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
@@ -2,25 +2,24 @@ 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) }
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}</#{elem}>"
+ exp = act = "<#{elem}>Merge #{merge.to_reference}</#{elem}>"
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/sanitization_filter_spec.rb b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb
index 4a1aa766149..e50c82d0b3c 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
@@ -42,6 +40,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{<pre class="code highlight white c"><code><span class="k">def</span></code></pre>}
expect(filter(act).to_html).to eq exp
@@ -87,5 +92,27 @@ 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('<h1>Description</h1>', 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</#{elem}>"
+ 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</#{elem}>"
+ expect(filter(act, pipeline: :description).to_html).to eq exp
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
index a4b331157af..38619a3c07f 100644
--- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
@@ -2,15 +2,14 @@ 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) }
- 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/table_of_contents_filter_spec.rb b/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb
index f383a5850d5..ddf583a72c1 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)
"<h#{level}>#{text}</h#{level}>\n"
diff --git a/spec/lib/gitlab/markdown/task_list_filter_spec.rb b/spec/lib/gitlab/markdown/task_list_filter_spec.rb
index 2a1e1cc5127..94f39cc966e 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 = %(<ul><li>Item</li></ul>)
diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
index 922502ada33..08e6941028f 100644
--- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
@@ -2,67 +2,65 @@ require 'spec_helper'
module Gitlab::Markdown
describe UserReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
- 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}</#{elem}>"
+ exp = act = "<#{elem}>Hey #{reference}</#{elem}>"
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(/\(<a.+>@#{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(/\(<a.+>#{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/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index 9801dc16554..f921dd9cc09 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -16,11 +16,35 @@ 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
+ ```
+
+ Quote:
+
+ > @offteam
+ })
+ expect(subject.users).to eq([])
+ end
+
it 'accesses valid issue objects' 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 +106,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/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index 37607b55eb1..c40ae7b5703 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/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index 31ee3e99cad..e7fb43ff335 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' }
@@ -21,6 +27,23 @@ describe CommitRange do
end
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 '#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}"
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index ad2ac143d97..27eb02a870b 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 00000000000..7744610db78
--- /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 9428224a64f..80638fc8db2 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/hooks/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb
index 4e0d50d7f3f..dae7e399cfb 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 d9714596f5d..fb5111dd9f5 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 e4b6b886565..edb21fc2e47 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 9f5ef3eff70..4c3f0cbcbbf 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/models/issue_spec.rb b/spec/models/issue_spec.rb
index 20d823b40e5..614b648bb52 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)
@@ -45,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/label_spec.rb b/spec/models/label_spec.rb
index 8644ac46605..6518213d71c 100644
--- a/spec/models/label_spec.rb
+++ b/spec/models/label_spec.rb
@@ -14,30 +14,63 @@ 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(build(:label, color: '#AABBCC')).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(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(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
+ 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
+
+ context 'using name' do
+ it 'returns a String reference to the object' do
+ expect(label.to_reference(:name)).to eq %(~"#{label.name}")
+ end
- expect(build(:label, title: 'GITLAB')).to be_valid
- expect(build(:label, title: 'gitlab')).to be_valid
+ 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
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 97b8abc49dd..0465aa34843 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -24,22 +24,45 @@
require 'spec_helper'
describe MergeRequest do
- describe "Validation" do
- it { is_expected.to validate_presence_of(:target_branch) }
- it { is_expected.to validate_presence_of(:source_branch) }
+ 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 "Mass assignment" do
+ 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 "Respond to" do
+ describe 'validation' do
+ it { is_expected.to validate_presence_of(:target_branch) }
+ it { is_expected.to validate_presence_of(:source_branch) }
+ end
+
+ 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?) }
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
@@ -57,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)
@@ -108,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/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index 45171e1bf64..eb73aa763fc 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
diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb
index e5bf9125313..ebd8b545aa7 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
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index bbaf54488be..e88615e1a2e 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)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 37e21a90818..48568e2a3ff 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 e37dcc75230..c81dd36ef4b 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -18,23 +18,47 @@
require 'spec_helper'
describe Snippet do
- describe "Associations" do
- it { is_expected.to belong_to(:author).class_name('User') }
- it { is_expected.to have_many(:notes).dependent(:destroy) }
+ 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 "Mass assignment" 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 "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
+ 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 0dddcd5bda2..be0b70395d6 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) }
@@ -79,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) }
@@ -175,6 +182,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)
@@ -233,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
@@ -255,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
@@ -557,7 +574,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) }
@@ -583,4 +599,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
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index bab8888a631..15f547e128d 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')
@@ -63,9 +59,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
@@ -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)
@@ -97,35 +89,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
@@ -138,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)
@@ -153,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
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index dcd50f73326..0ed5883914b 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
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index 6ddaaa0a6dd..21787fdd895 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
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index aada7febf6c..dbfd72e5f19 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 }
@@ -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
@@ -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
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 042352311da..0040718d9be 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
@@ -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',
diff --git a/spec/services/destroy_group_service_spec.rb b/spec/services/destroy_group_service_spec.rb
new file mode 100644
index 00000000000..24e439503e7
--- /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
diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb
index 6fc69e93628..a91be3b4472 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 |note|
+ note && note.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 916b01e1c45..c75173c1452 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
{
@@ -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).
@@ -51,15 +53,39 @@ describe MergeRequests::UpdateService do
expect(email.subject).to include(merge_request.title)
end
+ def find_note(starting_with)
+ @merge_request.notes.find do |note|
+ note && note.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
+
+ 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/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 1a02299bf19..0dc3b412783 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
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
new file mode 100644
index 00000000000..cdf576cc0c1
--- /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
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 4e4cb6d19ed..700286b585a 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -207,6 +207,41 @@ 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 '.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) }
@@ -238,13 +273,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 +289,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 +305,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
diff --git a/spec/support/filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
new file mode 100644
index 00000000000..755964e9a3d
--- /dev/null
+++ b/spec/support/filter_spec_helper.rb
@@ -0,0 +1,77 @@
+# Helper methods for Gitlab::Markdown filter specs
+#
+# Must be included into specs manually
+module FilterSpecHelper
+ extend ActiveSupport::Concern
+
+ # 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
+ #
+ # Commit SHAs get reversed, IDs get incremented by 1, all other Strings get
+ # their word characters reversed.
+ #
+ # 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.gsub(/\w+\z/) { |v| v.reverse }
+ end
+ 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
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index 53fb6545553..d29c8a55c82 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,26 @@ 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
+ new_text = <<-MSG.strip_heredoc
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
diff --git a/spec/support/reference_filter_spec_helper.rb b/spec/support/reference_filter_spec_helper.rb
deleted file mode 100644
index 06c39e1ada5..00000000000
--- a/spec/support/reference_filter_spec_helper.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-# Common methods and setup for Gitlab::Markdown reference filter specs
-#
-# Must be included into specs manually
-module ReferenceFilterSpecHelper
- extend ActiveSupport::Concern
-
- # Shortcut to Rails' auto-generated routes helpers, to avoid including the
- # module
- def urls
- Rails.application.routes.url_helpers
- 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
-
- def allow_cross_reference!
- allow_any_instance_of(described_class).
- to receive(:user_can_reference_project?).and_return(true)
- end
-
- def disallow_cross_reference!
- allow_any_instance_of(described_class).
- to receive(:user_can_reference_project?).and_return(false)
- end
-end
diff --git a/spec/teaspoon_env.rb b/spec/teaspoon_env.rb
new file mode 100644
index 00000000000..58f45ff8610
--- /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
diff --git a/vendor/assets/javascripts/jasmine-fixture.js b/vendor/assets/javascripts/jasmine-fixture.js
deleted file mode 100755
index 9980aec6ddb..00000000000
--- 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 $('<div id="jasmine_content"></div>').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);