summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--CHANGELOG34
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile13
-rw-r--r--Gemfile.lock27
-rw-r--r--LICENSE2
-rw-r--r--app/assets/images/auth_buttons/azure_64.pngbin0 -> 986 bytes
-rw-r--r--app/assets/javascripts/application.js.coffee7
-rw-r--r--app/assets/javascripts/branch-graph.js.coffee2
-rw-r--r--app/assets/javascripts/calendar.js.coffee5
-rw-r--r--app/assets/javascripts/commits.js.coffee66
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee4
-rw-r--r--app/assets/javascripts/dropzone_input.js.coffee14
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.coffee25
-rw-r--r--app/assets/javascripts/issues.js.coffee11
-rw-r--r--app/assets/javascripts/logo.js.coffee43
-rw-r--r--app/assets/javascripts/merge_requests.js.coffee4
-rw-r--r--app/assets/javascripts/project_find_file.js.coffee125
-rw-r--r--app/assets/javascripts/shortcuts_find_file.js.coffee19
-rw-r--r--app/assets/javascripts/shortcuts_tree.coffee4
-rw-r--r--app/assets/stylesheets/framework/blocks.scss9
-rw-r--r--app/assets/stylesheets/framework/calendar.scss42
-rw-r--r--app/assets/stylesheets/framework/sidebar.scss2
-rw-r--r--app/assets/stylesheets/framework/typography.scss6
-rw-r--r--app/assets/stylesheets/pages/commits.scss60
-rw-r--r--app/assets/stylesheets/pages/events.scss1
-rw-r--r--app/assets/stylesheets/pages/issuable.scss10
-rw-r--r--app/assets/stylesheets/pages/projects.scss13
-rw-r--r--app/assets/stylesheets/pages/tree.scss8
-rw-r--r--app/controllers/abuse_reports_controller.rb11
-rw-r--r--app/controllers/admin/application_settings_controller.rb2
-rw-r--r--app/controllers/admin/builds_controller.rb6
-rw-r--r--app/controllers/explore/groups_controller.rb2
-rw-r--r--app/controllers/projects/branches_controller.rb5
-rw-r--r--app/controllers/projects/builds_controller.rb6
-rw-r--r--app/controllers/projects/commits_controller.rb10
-rw-r--r--app/controllers/projects/find_file_controller.rb26
-rw-r--r--app/controllers/projects/merge_requests_controller.rb2
-rw-r--r--app/controllers/projects/refs_controller.rb2
-rw-r--r--app/controllers/projects_controller.rb11
-rw-r--r--app/controllers/users_controller.rb2
-rw-r--r--app/finders/groups_finder.rb44
-rw-r--r--app/finders/joined_groups_finder.rb49
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/helpers/auth_helper.rb2
-rw-r--r--app/helpers/issues_helper.rb4
-rw-r--r--app/helpers/page_layout_helper.rb25
-rw-r--r--app/helpers/search_helper.rb2
-rw-r--r--app/helpers/sorting_helper.rb4
-rw-r--r--app/mailers/abuse_report_mailer.rb10
-rw-r--r--app/mailers/emails/notes.rb2
-rw-r--r--app/models/ability.rb2
-rw-r--r--app/models/abuse_report.rb6
-rw-r--r--app/models/application_setting.rb15
-rw-r--r--app/models/ci/build.rb9
-rw-r--r--app/models/ci/runner_project.rb11
-rw-r--r--app/models/ci/trigger.rb13
-rw-r--r--app/models/ci/variable.rb3
-rw-r--r--app/models/commit_status.rb55
-rw-r--r--app/models/concerns/mentionable.rb7
-rw-r--r--app/models/generic_commit_status.rb1
-rw-r--r--app/models/global_milestone.rb4
-rw-r--r--app/models/group.rb9
-rw-r--r--app/models/hooks/project_hook.rb1
-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.rb3
-rw-r--r--app/models/merge_request.rb60
-rw-r--r--app/models/milestone.rb26
-rw-r--r--app/models/namespace.rb1
-rw-r--r--app/models/project.rb26
-rw-r--r--app/models/project_services/asana_service.rb84
-rw-r--r--app/models/project_services/assembla_service.rb1
-rw-r--r--app/models/project_services/bamboo_service.rb1
-rw-r--r--app/models/project_services/buildkite_service.rb1
-rw-r--r--app/models/project_services/builds_email_service.rb7
-rw-r--r--app/models/project_services/campfire_service.rb1
-rw-r--r--app/models/project_services/ci_service.rb1
-rw-r--r--app/models/project_services/custom_issue_tracker_service.rb1
-rw-r--r--app/models/project_services/drone_ci_service.rb1
-rw-r--r--app/models/project_services/emails_on_push_service.rb1
-rw-r--r--app/models/project_services/external_wiki_service.rb1
-rw-r--r--app/models/project_services/flowdock_service.rb1
-rw-r--r--app/models/project_services/gemnasium_service.rb1
-rw-r--r--app/models/project_services/gitlab_ci_service.rb1
-rw-r--r--app/models/project_services/gitlab_issue_tracker_service.rb1
-rw-r--r--app/models/project_services/hipchat_service.rb1
-rw-r--r--app/models/project_services/irker_service.rb1
-rw-r--r--app/models/project_services/issue_tracker_service.rb1
-rw-r--r--app/models/project_services/jira_service.rb11
-rw-r--r--app/models/project_services/pivotaltracker_service.rb1
-rw-r--r--app/models/project_services/pushover_service.rb1
-rw-r--r--app/models/project_services/redmine_service.rb1
-rw-r--r--app/models/project_services/slack_service.rb1
-rw-r--r--app/models/project_services/teamcity_service.rb1
-rw-r--r--app/models/repository.rb49
-rw-r--r--app/models/service.rb1
-rw-r--r--app/models/tree.rb14
-rw-r--r--app/models/user.rb118
-rw-r--r--app/services/create_branch_service.rb1
-rw-r--r--app/services/delete_branch_service.rb1
-rw-r--r--app/services/notification_service.rb1
-rw-r--r--app/services/projects/download_service.rb8
-rw-r--r--app/services/projects/housekeeping_service.rb20
-rw-r--r--app/services/projects/transfer_service.rb3
-rw-r--r--app/services/projects/upload_service.rb8
-rw-r--r--app/services/system_hooks_service.rb11
-rw-r--r--app/services/system_note_service.rb6
-rw-r--r--app/uploaders/file_uploader.rb15
-rw-r--r--app/validators/namespace_validator.rb1
-rw-r--r--app/views/abuse_report_mailer/notify.html.haml2
-rw-r--r--app/views/abuse_reports/new.html.haml4
-rw-r--r--app/views/admin/abuse_reports/_abuse_report.html.haml23
-rw-r--r--app/views/admin/abuse_reports/index.html.haml3
-rw-r--r--app/views/admin/application_settings/_form.html.haml10
-rw-r--r--app/views/admin/builds/index.html.haml14
-rw-r--r--app/views/admin/dashboard/index.html.haml22
-rw-r--r--app/views/admin/groups/index.html.haml2
-rw-r--r--app/views/admin/groups/show.html.haml2
-rw-r--r--app/views/admin/projects/show.html.haml2
-rw-r--r--app/views/admin/users/_profile.html.haml2
-rw-r--r--app/views/admin/users/index.html.haml14
-rw-r--r--app/views/admin/users/show.html.haml8
-rw-r--r--app/views/dashboard/issues.atom.builder2
-rw-r--r--app/views/dashboard/projects/index.atom.builder2
-rw-r--r--app/views/events/_event.html.haml2
-rw-r--r--app/views/events/_event_last_push.html.haml2
-rw-r--r--app/views/groups/edit.html.haml9
-rw-r--r--app/views/groups/issues.atom.builder2
-rw-r--r--app/views/groups/show.atom.builder2
-rw-r--r--app/views/groups/show.html.haml13
-rw-r--r--app/views/help/_shortcuts.html.haml26
-rw-r--r--app/views/layouts/_head.html.haml14
-rw-r--r--app/views/layouts/group.html.haml7
-rw-r--r--app/views/layouts/nav/_admin.html.haml6
-rw-r--r--app/views/layouts/nav/_dashboard.html.haml4
-rw-r--r--app/views/layouts/nav/_group.html.haml4
-rw-r--r--app/views/layouts/nav/_profile.html.haml4
-rw-r--r--app/views/layouts/nav/_project.html.haml9
-rw-r--r--app/views/layouts/project.html.haml7
-rw-r--r--app/views/notify/repository_push_email.html.haml2
-rw-r--r--app/views/notify/repository_push_email.text.haml2
-rw-r--r--app/views/profiles/keys/_key_details.html.haml2
-rw-r--r--app/views/projects/_find_file_link.html.haml3
-rw-r--r--app/views/projects/_home_panel.html.haml21
-rw-r--r--app/views/projects/branches/_branch.html.haml15
-rw-r--r--app/views/projects/builds/index.html.haml12
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml5
-rw-r--r--app/views/projects/commit/show.html.haml4
-rw-r--r--app/views/projects/commits/_commit.html.haml2
-rw-r--r--app/views/projects/commits/_commits.html.haml2
-rw-r--r--app/views/projects/commits/show.atom.builder4
-rw-r--r--app/views/projects/commits/show.html.haml26
-rw-r--r--app/views/projects/edit.html.haml13
-rw-r--r--app/views/projects/find_file/show.html.haml27
-rw-r--r--app/views/projects/hooks/index.html.haml4
-rw-r--r--app/views/projects/issues/_issues.html.haml2
-rw-r--r--app/views/projects/issues/index.atom.builder2
-rw-r--r--app/views/projects/merge_requests/_merge_requests.html.haml2
-rw-r--r--app/views/projects/merge_requests/_show.html.haml2
-rw-r--r--app/views/projects/merge_requests/index.html.haml5
-rw-r--r--app/views/projects/show.atom.builder2
-rw-r--r--app/views/projects/show.html.haml11
-rw-r--r--app/views/projects/tree/show.html.haml8
-rw-r--r--app/views/shared/_logo.svg14
-rw-r--r--app/views/shared/_service_settings.html.haml4
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml19
-rw-r--r--app/views/shared/projects/_project.html.haml2
-rw-r--r--app/views/users/show.atom.builder2
-rw-r--r--app/views/users/show.html.haml2
-rw-r--r--config/initializers/date_time_formats.rb9
-rw-r--r--config/initializers/metrics.rb1
-rw-r--r--config/routes.rb25
-rw-r--r--db/migrate/20151228111122_remove_public_from_namespace.rb6
-rw-r--r--db/migrate/20160106164438_remove_influxdb_credentials.rb6
-rw-r--r--db/schema.rb510
-rw-r--r--doc/README.md5
-rw-r--r--doc/administration/environment_variables.md (renamed from doc/administration/enviroment_variables.md)7
-rw-r--r--doc/api/projects.md28
-rw-r--r--doc/api/tags.md20
-rw-r--r--doc/api/users.md7
-rw-r--r--doc/ci/README.md1
-rw-r--r--doc/ci/enable_or_disable_ci.md70
-rw-r--r--doc/ci/img/features_settings.pngbin0 -> 18691 bytes
-rw-r--r--doc/ci/runners/README.md5
-rw-r--r--doc/install/installation.md4
-rw-r--r--doc/integration/README.md29
-rw-r--r--doc/integration/azure.md83
-rw-r--r--doc/integration/external-issue-tracker.md48
-rw-r--r--doc/integration/img/jira_issue_reference.png (renamed from doc/integration/jira_issue_reference.png)bin39942 -> 39942 bytes
-rw-r--r--doc/integration/img/jira_merge_request_close.pngbin0 -> 111150 bytes
-rw-r--r--doc/integration/img/jira_project_name.png (renamed from doc/integration/jira_project_name.png)bin60598 -> 60598 bytes
-rw-r--r--doc/integration/img/jira_service.png (renamed from doc/integration/jira_service.png)bin59082 -> 59082 bytes
-rw-r--r--doc/integration/img/jira_service_close_issue.png (renamed from doc/integration/jira_service_close_issue.png)bin88433 -> 88433 bytes
-rw-r--r--doc/integration/img/jira_service_page.pngbin0 -> 35496 bytes
-rw-r--r--doc/integration/img/jira_workflow_screenshot.png (renamed from doc/integration/jira_workflow_screenshot.png)bin121534 -> 121534 bytes
-rw-r--r--doc/integration/jira.md138
-rw-r--r--doc/integration/jira_service_page.pngbin162449 -> 0 bytes
-rw-r--r--doc/integration/omniauth.md1
-rw-r--r--doc/integration/redmine_configuration.pngbin118752 -> 0 bytes
-rw-r--r--doc/integration/redmine_service_template.pngbin198077 -> 0 bytes
-rw-r--r--doc/project_services/img/redmine_configuration.pngbin0 -> 21061 bytes
-rw-r--r--doc/project_services/img/services_templates_redmine_example.pngbin0 -> 17351 bytes
-rw-r--r--doc/project_services/project_services.md49
-rw-r--r--doc/project_services/redmine.md21
-rw-r--r--doc/project_services/services_templates.md25
-rw-r--r--doc/raketasks/backup_restore.md43
-rw-r--r--doc/ssh/README.md11
-rw-r--r--doc/system_hooks/system_hooks.md50
-rw-r--r--doc/workflow/add-user/add-user.md90
-rw-r--r--doc/workflow/add-user/images/add-members.pngbin2361 -> 0 bytes
-rw-r--r--doc/workflow/add-user/images/new-member.pngbin12038 -> 0 bytes
-rw-r--r--doc/workflow/add-user/images/select-project.pngbin4042 -> 0 bytes
-rw-r--r--doc/workflow/add-user/img/add_new_user_to_project_settings.pngbin0 -> 22822 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_email_accept.pngbin0 -> 10833 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_email_ready.pngbin0 -> 16177 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_email_search.pngbin0 -> 15889 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_give_permissions.pngbin0 -> 22089 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_import_members_from_another_project.pngbin0 -> 18897 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_imported_members.pngbin0 -> 23897 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_list_members.pngbin0 -> 15732 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_members_menu.png (renamed from doc/workflow/add-user/images/members.png)bin8295 -> 8295 bytes
-rw-r--r--doc/workflow/add-user/img/add_user_search_people.pngbin0 -> 13518 bytes
-rw-r--r--doc/workflow/importing/import_projects_from_github.md4
-rw-r--r--doc/workflow/shortcuts.pngbin78736 -> 48782 bytes
-rw-r--r--features/explore/groups.feature15
-rw-r--r--features/project/commits/commits.feature5
-rw-r--r--features/project/find_file.feature42
-rw-r--r--features/project/fork.feature11
-rw-r--r--features/steps/group/milestones.rb2
-rw-r--r--features/steps/project/commits/commits.rb9
-rw-r--r--features/steps/project/fork.rb19
-rw-r--r--features/steps/project/project_find_file.rb73
-rw-r--r--features/steps/shared/paths.rb4
-rw-r--r--lib/api/merge_requests.rb2
-rw-r--r--lib/api/projects.rb14
-rw-r--r--lib/api/tags.rb21
-rw-r--r--lib/api/users.rb2
-rw-r--r--lib/banzai/filter/abstract_reference_filter.rb50
-rw-r--r--lib/banzai/filter/milestone_reference_filter.rb22
-rw-r--r--lib/banzai/filter/redactor_filter.rb2
-rw-r--r--lib/banzai/filter/reference_filter.rb4
-rw-r--r--lib/banzai/filter/reference_gatherer_filter.rb2
-rw-r--r--lib/banzai/filter/relative_link_filter.rb2
-rw-r--r--lib/banzai/filter/task_list_filter.rb13
-rw-r--r--lib/banzai/pipeline/gfm_pipeline.rb1
-rw-r--r--lib/banzai/pipeline/single_line_pipeline.rb16
-rw-r--r--lib/banzai/querying.rb18
-rw-r--r--lib/banzai/renderer.rb4
-rw-r--r--lib/gitlab/backend/shell.rb12
-rw-r--r--lib/gitlab/build_data_builder.rb1
-rw-r--r--lib/gitlab/contributions_calendar.rb4
-rw-r--r--lib/gitlab/current_settings.rb3
-rw-r--r--lib/gitlab/email/receiver.rb7
-rw-r--r--lib/gitlab/fogbugz_import/importer.rb4
-rw-r--r--lib/gitlab/github_import/base_formatter.rb21
-rw-r--r--lib/gitlab/github_import/comment_formatter.rb45
-rw-r--r--lib/gitlab/github_import/importer.rb64
-rw-r--r--lib/gitlab/github_import/issue_formatter.rb66
-rw-r--r--lib/gitlab/github_import/pull_request_formatter.rb101
-rw-r--r--lib/gitlab/metrics.rb36
-rw-r--r--lib/gitlab/metrics/instrumentation.rb2
-rw-r--r--lib/gitlab/metrics/metric.rb7
-rw-r--r--lib/gitlab/metrics/obfuscated_sql.rb47
-rw-r--r--lib/gitlab/metrics/rack_middleware.rb4
-rw-r--r--lib/gitlab/metrics/sampler.rb19
-rw-r--r--lib/gitlab/metrics/subscribers/action_view.rb1
-rw-r--r--lib/gitlab/metrics/subscribers/active_record.rb30
-rw-r--r--lib/gitlab/metrics/transaction.rb33
-rw-r--r--lib/gitlab/reference_extractor.rb2
-rw-r--r--lib/tasks/gitlab/check.rake2
-rw-r--r--lib/version_check.rb2
-rw-r--r--spec/controllers/abuse_reports_controller_spec.rb80
-rw-r--r--spec/controllers/projects/find_file_controller_spec.rb66
-rw-r--r--spec/factories/merge_requests.rb41
-rw-r--r--spec/factories/projects.rb7
-rw-r--r--spec/features/admin/admin_builds_spec.rb119
-rw-r--r--spec/features/builds_spec.rb23
-rw-r--r--spec/features/markdown_spec.rb1
-rw-r--r--spec/features/projects_spec.rb6
-rw-r--r--spec/finders/groups_finder_spec.rb48
-rw-r--r--spec/finders/joined_groups_finder_spec.rb49
-rw-r--r--spec/fixtures/markdown.md.erb7
-rw-r--r--spec/helpers/application_helper_spec.rb8
-rw-r--r--spec/helpers/page_layout_helper_spec.rb64
-rw-r--r--spec/helpers/search_helper_spec.rb2
-rw-r--r--spec/javascripts/issue_spec.js.coffee32
-rw-r--r--spec/lib/banzai/filter/milestone_reference_filter_spec.rb75
-rw-r--r--spec/lib/banzai/filter/relative_link_filter_spec.rb8
-rw-r--r--spec/lib/banzai/filter/task_list_filter_spec.rb6
-rw-r--r--spec/lib/banzai/querying_spec.rb13
-rw-r--r--spec/lib/gitlab/build_data_builder_spec.rb1
-rw-r--r--spec/lib/gitlab/email/receiver_spec.rb11
-rw-r--r--spec/lib/gitlab/github_import/comment_formatter_spec.rb80
-rw-r--r--spec/lib/gitlab/github_import/issue_formatter_spec.rb139
-rw-r--r--spec/lib/gitlab/github_import/pull_request_formatter_spec.rb184
-rw-r--r--spec/lib/gitlab/metrics/instrumentation_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/metric_spec.rb3
-rw-r--r--spec/lib/gitlab/metrics/obfuscated_sql_spec.rb93
-rw-r--r--spec/lib/gitlab/metrics/rack_middleware_spec.rb6
-rw-r--r--spec/lib/gitlab/metrics/sampler_spec.rb38
-rw-r--r--spec/lib/gitlab/metrics/subscribers/action_view_spec.rb3
-rw-r--r--spec/lib/gitlab/metrics/subscribers/active_record_spec.rb33
-rw-r--r--spec/lib/gitlab/metrics/transaction_spec.rb29
-rw-r--r--spec/lib/gitlab/metrics_spec.rb18
-rw-r--r--spec/mailers/abuse_report_mailer_spec.rb38
-rw-r--r--spec/models/abuse_report_spec.rb17
-rw-r--r--spec/models/application_setting_spec.rb65
-rw-r--r--spec/models/ci/build_spec.rb22
-rw-r--r--spec/models/ci/commit_spec.rb2
-rw-r--r--spec/models/ci/runner_project_spec.rb11
-rw-r--r--spec/models/ci/trigger_spec.rb13
-rw-r--r--spec/models/ci/variable_spec.rb3
-rw-r--r--spec/models/commit_status_spec.rb1
-rw-r--r--spec/models/external_wiki_service_spec.rb1
-rw-r--r--spec/models/generic_commit_status_spec.rb1
-rw-r--r--spec/models/group_spec.rb28
-rw-r--r--spec/models/hooks/web_hook_spec.rb12
-rw-r--r--spec/models/merge_request_spec.rb41
-rw-r--r--spec/models/namespace_spec.rb1
-rw-r--r--spec/models/note_spec.rb15
-rw-r--r--spec/models/project_services/asana_service_spec.rb77
-rw-r--r--spec/models/project_services/builds_email_service_spec.rb23
-rw-r--r--spec/models/project_spec.rb7
-rw-r--r--spec/models/service_spec.rb1
-rw-r--r--spec/models/user_spec.rb115
-rw-r--r--spec/requests/api/projects_spec.rb23
-rw-r--r--spec/requests/api/tags_spec.rb21
-rw-r--r--spec/routing/project_routing_spec.rb13
-rw-r--r--spec/services/notification_service_spec.rb15
-rw-r--r--spec/services/projects/download_service_spec.rb24
-rw-r--r--spec/services/system_hooks_service_spec.rb47
-rw-r--r--spec/services/system_note_service_spec.rb2
-rw-r--r--spec/support/markdown_feature.rb8
-rw-r--r--spec/support/matchers/markdown_matchers.rb9
-rw-r--r--vendor/assets/javascripts/fuzzaldrin-plus.min.js1
-rw-r--r--vendor/assets/javascripts/jquery.blockUI.js590
-rw-r--r--vendor/assets/javascripts/jquery.history.js1
338 files changed, 4068 insertions, 2507 deletions
diff --git a/.gitignore b/.gitignore
index f5b6427ca03..91ea81bfc4e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,6 +26,7 @@ config/initializers/smtp_settings.rb
config/resque.yml
config/unicorn.rb
config/secrets.yml
+config/sidekiq.yml
coverage/*
db/*.sqlite3
db/*.sqlite3-journal
diff --git a/CHANGELOG b/CHANGELOG
index ea403642e32..efb261f6573 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,23 +1,52 @@
Please view this file on the master branch, on stable branches it's out of date.
v 8.4.0 (unreleased)
+ - Add housekeeping function to project settings page
+ - The default GitLab logo now acts as a loading indicator
+ - Accept 2xx status codes for successful Web hook triggers (Stan Hu)
+ - Fix missing date of month in network graph when commits span a month (Stan Hu)
+ - Expire view caches when application settings change (e.g. Gravatar disabled) (Stan Hu)
+ - Don't notify users twice if they are both project watchers and subscribers (Stan Hu)
- Implement new UI for group page
- Implement search inside emoji picker
- Add API support for looking up a user by username (Stan Hu)
- Add project permissions to all project API endpoints (Stan Hu)
+ - Link to milestone in "Milestone changed" system note
- Only allow group/project members to mention `@all`
- Expose Git's version in the admin area (Trey Davis)
- Add "Frequently used" category to emoji picker
- Add CAS support (tduehr)
- Add link to merge request on build detail page
+ - Fix: Problem with projects ending with .keys (Jose Corcuera)
- Revert back upvote and downvote button to the issue and MR pages
- Swap position of Assignee and Author selector on Issuables (Zeger-Jan van de Weg)
+ - Add system hook messages for project rename and transfer (Steve Norman)
+ - Fix version check image in Safari
+ - Show 'All' tab by default in the builds page
+ - Add Open Graph and Twitter Card data to all pages
+ - Fix API project lookups when querying with a namespace with dots (Stan Hu)
+ - Update version check images to use SVG
+ - Validate README format before displaying
+ - Enable Microsoft Azure OAuth2 support (Janis Meybohm)
+ - Properly set task-list class on single item task lists
+ - Add file finder feature in tree view (Kyungchul Shin)
+ - Ajax filter by message for commits page
+ - API: Add support for deleting a tag via the API (Robert Schilling)
v 8.3.3 (unreleased)
+ - Preserve CE behavior with JIRA integration by only calling API if URL is set
+ - Fix duplicated branch creation/deletion events when using Web UI (Stan Hu)
+ - Get "Merge when build succeeds" to work when commits were pushed to MR target branch while builds were running
+ - Suppress e-mails on failed builds if allow_failure is set (Stan Hu)
- Fix project transfer e-mail sending incorrect paths in e-mail notification (Stan Hu)
- Enable "Add key" button when user fills in a proper key (Stan Hu)
+ - Fix error in processing reply-by-email messages (Jason Lee)
+ - Fix Error 500 when visiting build page of project with nil runners_token (Stan Hu)
v 8.3.2
+ - Change single user API endpoint to return more detailed data (Michael Potthoff)
+
+v 8.3.2 (unreleased)
- Disable --follow in `git log` to avoid loading duplicate commit data in infinite scroll (Stan Hu)
- Add support for Google reCAPTCHA in user registration
@@ -27,6 +56,7 @@ v 8.3.1
- Fix LDAP identity and user retrieval when special characters are used
- Move Sidekiq-cron configuration to gitlab.yml
- Enable forcing Two-Factor authentication sitewide, with optional grace period
+ - Import GitHub Pull Requests into GitLab
v 8.3.0
- Bump rack-attack to 4.3.1 for security fix (Stan Hu)
@@ -90,6 +120,8 @@ v 8.3.0
- Do not show build status unless builds are enabled and `.gitlab-ci.yml` is present
- Persist runners registration token in database
- Fix online editor should not remove newlines at the end of the file
+ - Expose Git's version in the admin area
+ - Show "New Merge Request" buttons on canonical repos when you have a fork (Josh Frye)
v 8.2.3
- Fix application settings cache not expiring after changes (Stan Hu)
@@ -148,6 +180,8 @@ v 8.2.0
- Allow to define cache in `.gitlab-ci.yml`
- Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu)
- Remove deprecated CI events from project settings page
+ - Use issue editor as cross reference comment author when issue is edited with a new mention.
+ - Add graphs of commits ahead and behind default branch (Jeff Stubler)
- Improve personal snippet access workflow (Douglas Alexandre)
- [API] Add ability to fetch the commit ID of the last commit that actually touched a file
- Fix omniauth documentation setting for omnibus configuration (Jon Cairns)
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index d48d3702aed..a04abec9149 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-2.6.9
+2.6.10
diff --git a/Gemfile b/Gemfile
index 2a1c4f7d73a..aa912867dda 100644
--- a/Gemfile
+++ b/Gemfile
@@ -33,6 +33,7 @@ gem 'omniauth-saml', '~> 1.4.0'
gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd'
+gem 'omniauth-azure-oauth2'
gem 'rack-oauth2', '~> 1.2.1'
# reCAPTCHA protection
@@ -48,7 +49,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 7.2.20'
+gem "gitlab_git", '~> 7.2.22'
# LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes
@@ -66,10 +67,6 @@ gem 'grape', '~> 0.13.0'
gem 'grape-entity', '~> 0.4.2'
gem 'rack-cors', '~> 0.4.0', require: 'rack/cors'
-# Format dates and times
-# based on human-friendly examples
-gem "stamp", '~> 0.6.0'
-
# Pagination
gem "kaminari", "~> 0.16.3"
@@ -169,10 +166,10 @@ gem 'asana', '~> 0.4.0'
gem 'ruby-fogbugz', '~> 0.2.1'
# d3
-gem 'd3_rails', '~> 3.5.5'
+gem 'd3_rails', '~> 3.5.0'
#cal-heatmap
-gem "cal-heatmap-rails", "~> 0.0.1"
+gem 'cal-heatmap-rails', '~> 3.5.0'
# underscore-rails
gem "underscore-rails", "~> 1.8.0"
@@ -200,7 +197,7 @@ gem 'turbolinks', '~> 2.5.0'
gem 'jquery-turbolinks', '~> 2.1.0'
gem 'addressable', '~> 2.3.8'
-gem 'bootstrap-sass', '~> 3.0'
+gem 'bootstrap-sass', '~> 3.3.0'
gem 'font-awesome-rails', '~> 4.2'
gem 'gitlab_emoji', '~> 0.2.0'
gem 'gon', '~> 6.0.1'
diff --git a/Gemfile.lock b/Gemfile.lock
index 9769ae80a7d..96bf2bd1a2e 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -66,7 +66,7 @@ GEM
attr_encrypted (1.3.4)
encryptor (>= 1.3.0)
attr_required (1.0.0)
- autoprefixer-rails (6.1.2)
+ autoprefixer-rails (6.2.3)
execjs
json
awesome_print (1.2.0)
@@ -82,9 +82,9 @@ GEM
erubis (>= 2.6.6)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
- bootstrap-sass (3.3.5)
- autoprefixer-rails (>= 5.0.0.1)
- sass (>= 3.2.19)
+ bootstrap-sass (3.3.6)
+ autoprefixer-rails (>= 5.2.1)
+ sass (>= 3.3.4)
brakeman (3.1.4)
erubis (~> 2.6)
fastercsv (~> 1.5)
@@ -106,7 +106,7 @@ GEM
bundler (~> 1.2)
thor (~> 0.18)
byebug (8.2.1)
- cal-heatmap-rails (0.0.1)
+ cal-heatmap-rails (3.5.1)
capybara (2.4.4)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
@@ -443,6 +443,10 @@ GEM
omniauth (1.2.2)
hashie (>= 1.2, < 4)
rack (~> 1.0)
+ omniauth-azure-oauth2 (0.0.6)
+ jwt (~> 1.0)
+ omniauth (~> 1.0)
+ omniauth-oauth2 (~> 1.1)
omniauth-bitbucket (0.0.2)
multi_json (~> 1.7)
omniauth (~> 1.1)
@@ -730,7 +734,6 @@ GEM
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
- stamp (0.6.0)
state_machines (0.4.0)
state_machines-activemodel (0.3.0)
activemodel (~> 4.1)
@@ -843,13 +846,13 @@ DEPENDENCIES
benchmark-ips
better_errors (~> 1.0.1)
binding_of_caller (~> 0.7.2)
- bootstrap-sass (~> 3.0)
+ bootstrap-sass (~> 3.3.0)
brakeman (~> 3.1.0)
browser (~> 1.0.0)
bullet
bundler-audit
byebug
- cal-heatmap-rails (~> 0.0.1)
+ cal-heatmap-rails (~> 3.5.0)
capybara (~> 2.4.0)
capybara-screenshot (~> 1.0.0)
carrierwave (~> 0.9.0)
@@ -859,7 +862,7 @@ DEPENDENCIES
connection_pool (~> 2.0)
coveralls (~> 0.8.2)
creole (~> 0.5.0)
- d3_rails (~> 3.5.5)
+ d3_rails (~> 3.5.0)
database_cleaner (~> 1.4.0)
default_value_for (~> 3.0.0)
devise (~> 3.5.3)
@@ -883,7 +886,7 @@ DEPENDENCIES
github-markup (~> 1.3.1)
gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_emoji (~> 0.2.0)
- gitlab_git (~> 7.2.20)
+ gitlab_git (~> 7.2.22)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0)
@@ -916,6 +919,7 @@ DEPENDENCIES
oauth2 (~> 1.0.0)
octokit (~> 3.7.0)
omniauth (~> 1.2.2)
+ omniauth-azure-oauth2
omniauth-bitbucket (~> 0.0.2)
omniauth-cas3 (~> 1.1.2)
omniauth-facebook (~> 3.0.0)
@@ -973,7 +977,6 @@ DEPENDENCIES
spring-commands-spinach (~> 1.0.0)
spring-commands-teaspoon (~> 0.0.2)
sprockets (~> 2.12.3)
- stamp (~> 0.6.0)
state_machines-activerecord (~> 0.3.0)
task_list (~> 1.0.2)
teaspoon (~> 1.0.0)
@@ -994,4 +997,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.10.6
+ 1.11.2
diff --git a/LICENSE b/LICENSE
index d8cb29f3638..1dc1bdb7411 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright (c) 2011-2015 GitLab B.V.
+Copyright (c) 2011-2016 GitLab B.V.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/app/assets/images/auth_buttons/azure_64.png b/app/assets/images/auth_buttons/azure_64.png
new file mode 100644
index 00000000000..a82c751e001
--- /dev/null
+++ b/app/assets/images/auth_buttons/azure_64.png
Binary files differ
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index affab5bb030..c095e5ae2b1 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -10,12 +10,12 @@
#= require jquery.cookie
#= require jquery.endless-scroll
#= require jquery.highlight
-#= require jquery.history
#= require jquery.waitforimages
#= require jquery.atwho
#= require jquery.scrollTo
-#= require jquery.blockUI
#= require jquery.turbolinks
+#= require d3
+#= require cal-heatmap
#= require turbolinks
#= require autosave
#= require bootstrap
@@ -27,7 +27,6 @@
#= require branch-graph
#= require ace/ace
#= require ace/ext-searchbox
-#= require d3
#= require underscore
#= require nprogress
#= require nprogress-turbolinks
@@ -39,9 +38,9 @@
#= require shortcuts_dashboard_navigation
#= require shortcuts_issuable
#= require shortcuts_network
-#= require cal-heatmap
#= require jquery.nicescroll.min
#= require_tree .
+#= require fuzzaldrin-plus.min
window.slugify = (text) ->
text.replace(/[^-a-zA-Z0-9]+/g, '_').toLowerCase()
diff --git a/app/assets/javascripts/branch-graph.js.coffee b/app/assets/javascripts/branch-graph.js.coffee
index 917228bd276..f2fd2a775a4 100644
--- a/app/assets/javascripts/branch-graph.js.coffee
+++ b/app/assets/javascripts/branch-graph.js.coffee
@@ -66,7 +66,7 @@ class @BranchGraph
r.rect(40, 0, 30, @barHeight).attr fill: "#444"
for day, mm in @days
- if cuday isnt day[0]
+ if cuday isnt day[0] || cumonth isnt day[1]
# Dates
r.text(55, @offsetY + @unitTime * mm, day[0])
.attr(
diff --git a/app/assets/javascripts/calendar.js.coffee b/app/assets/javascripts/calendar.js.coffee
index 97621236924..d80e0e716ce 100644
--- a/app/assets/javascripts/calendar.js.coffee
+++ b/app/assets/javascripts/calendar.js.coffee
@@ -1,9 +1,4 @@
class @Calendar
- options =
- month: "short"
- day: "numeric"
- year: "numeric"
-
constructor: (timestamps, starting_year, starting_month, calendar_activities_path) ->
cal = new CalHeatMap()
cal.init
diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee
index c183e78e513..ffd3627b1b0 100644
--- a/app/assets/javascripts/commits.js.coffee
+++ b/app/assets/javascripts/commits.js.coffee
@@ -1,15 +1,5 @@
class @CommitsList
- @data =
- ref: null
- limit: 0
- offset: 0
- @disable = false
-
- @showProgress: ->
- $('.loading').show()
-
- @hideProgress: ->
- $('.loading').hide()
+ @timer = null
@init: (ref, limit) ->
$("body").on "click", ".day-commits-table li.commit", (event) ->
@@ -18,38 +8,32 @@ class @CommitsList
e.stopPropagation()
return false
- @data.ref = ref
- @data.limit = limit
- @data.offset = limit
+ Pager.init limit, false
+
+ @content = $("#commits-list")
+ @searchField = $("#commits-search")
+ @initSearch()
- this.initLoadMore()
- this.showProgress()
+ @initSearch: ->
+ @timer = null
+ @searchField.keyup =>
+ clearTimeout(@timer)
+ @timer = setTimeout(@filterResults, 500)
+
+ @filterResults: =>
+ form = $(".commits-search-form")
+ search = @searchField.val()
+ commitsUrl = form.attr("action") + '?' + form.serialize()
+ @content.fadeTo('fast', 0.5)
- @getOld: ->
- this.showProgress()
$.ajax
type: "GET"
- url: location.href
- data: @data
- complete: this.hideProgress
- success: (data) ->
- CommitsList.append(data.count, data.html)
+ url: form.attr("action")
+ data: form.serialize()
+ complete: =>
+ @content.fadeTo('fast', 1.0)
+ success: (data) =>
+ @content.html(data.html)
+ # Change url so if user reload a page - search results are saved
+ history.replaceState {page: commitsUrl}, document.title, commitsUrl
dataType: "json"
-
- @append: (count, html) ->
- $("#commits-list").append(html)
- if count > 0
- @data.offset += count
- else
- @disable = true
-
- @initLoadMore: ->
- $(document).unbind('scroll')
- $(document).endlessScroll
- bottomPixels: 400
- fireDelay: 1000
- fireOnce: true
- ceaseFire: =>
- @disable
- callback: =>
- this.getOld()
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 69e061ce6e9..58d6b9d4060 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -87,7 +87,9 @@ class Dispatcher
new GroupAvatar()
when 'projects:tree:show'
new TreeView()
- shortcut_handler = new ShortcutsNavigation()
+ shortcut_handler = new ShortcutsTree()
+ when 'projects:find_file:show'
+ shortcut_handler = true
when 'projects:blob:show'
new LineHighlighter()
shortcut_handler = new ShortcutsNavigation()
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index 30a35a04339..c714c0fa939 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -66,7 +66,7 @@ class @DropzoneInput
success: (header, response) ->
child = $(dropzone[0]).children("textarea")
- $(child).val $(child).val() + formatLink(response.link) + "\n"
+ $(child).val $(child).val() + response.link.markdown + "\n"
return
error: (temp, errorMessage) ->
@@ -99,11 +99,6 @@ class @DropzoneInput
child = $(dropzone[0]).children("textarea")
- formatLink = (link) ->
- text = "[#{link.alt}](#{link.url})"
- text = "!#{text}" if link.is_image
- text
-
handlePaste = (event) ->
pasteEvent = event.originalEvent
if pasteEvent.clipboardData and pasteEvent.clipboardData.items
@@ -162,7 +157,7 @@ class @DropzoneInput
closeAlertMessage()
success: (e, textStatus, response) ->
- insertToTextArea(filename, formatLink(response.responseJSON.link))
+ insertToTextArea(filename, response.responseJSON.link.markdown)
error: (response) ->
showError(response.responseJSON.message)
@@ -202,8 +197,3 @@ class @DropzoneInput
e.preventDefault()
$(@).closest('.gfm-form').find('.div-dropzone').click()
return
-
- formatLink: (link) ->
- text = "[#{link.alt}](#{link.url})"
- text = "!#{text}" if link.is_image
- text
diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
index 7967892f856..fa90fb65f42 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.coffee
+++ b/app/assets/javascripts/gfm_auto_complete.js.coffee
@@ -34,7 +34,7 @@ GitLab.GfmAutoComplete =
searchKey: 'search'
callbacks:
beforeSave: (members) ->
- $.map members, (m) ->
+ $.map members, (m) ->
title = m.name
title += " (#{m.count})" if m.count
@@ -50,7 +50,7 @@ GitLab.GfmAutoComplete =
insertTpl: '${atwho-at}${id}'
callbacks:
beforeSave: (issues) ->
- $.map issues, (i) ->
+ $.map issues, (i) ->
id: i.iid
title: sanitize(i.title)
search: "#{i.iid} #{i.title}"
@@ -63,18 +63,17 @@ GitLab.GfmAutoComplete =
insertTpl: '${atwho-at}${id}'
callbacks:
beforeSave: (merges) ->
- $.map merges, (m) ->
+ $.map merges, (m) ->
id: m.iid
title: sanitize(m.title)
search: "#{m.iid} #{m.title}"
- input.one 'focus', =>
- $.getJSON(@dataSource).done (data) ->
- # load members
- input.atwho 'load', '@', data.members
- # load issues
- input.atwho 'load', 'issues', data.issues
- # load merge requests
- input.atwho 'load', 'mergerequests', data.mergerequests
- # load emojis
- input.atwho 'load', ':', data.emojis
+ $.getJSON(@dataSource).done (data) ->
+ # load members
+ input.atwho 'load', '@', data.members
+ # load issues
+ input.atwho 'load', 'issues', data.issues
+ # load merge requests
+ input.atwho 'load', 'mergerequests', data.mergerequests
+ # load emojis
+ input.atwho 'load', ':', data.emojis
diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee
index ac9e022e727..a0acf3028bf 100644
--- a/app/assets/javascripts/issues.js.coffee
+++ b/app/assets/javascripts/issues.js.coffee
@@ -15,13 +15,6 @@
$(this).html totalIssues + 1
else
$(this).html totalIssues - 1
- $("body").on "click", ".issues-other-filters .dropdown-menu a", ->
- $('.issues-list').block(
- message: null,
- overlayCSS:
- backgroundColor: '#DDD'
- opacity: .4
- )
reload: ->
Issues.initSelects()
@@ -54,7 +47,7 @@
form = $("#issue_search_form")
search = $("#issue_search").val()
$('.issues-holder').css("opacity", '0.5')
- issues_url = form.attr('action') + '? '+ form.serialize()
+ issues_url = form.attr('action') + '?' + form.serialize()
$.ajax
type: "GET"
@@ -65,7 +58,7 @@
success: (data) ->
$('.issues-holder').html(data.html)
# Change url so if user reload a page - search results are saved
- History.replaceState {page: issues_url}, document.title, issues_url
+ history.replaceState {page: issues_url}, document.title, issues_url
Issues.reload()
dataType: "json"
diff --git a/app/assets/javascripts/logo.js.coffee b/app/assets/javascripts/logo.js.coffee
new file mode 100644
index 00000000000..e864a674cdd
--- /dev/null
+++ b/app/assets/javascripts/logo.js.coffee
@@ -0,0 +1,43 @@
+NProgress.configure(showSpinner: false)
+
+defaultClass = 'tanuki-shape'
+pieces = [
+ 'path#tanuki-right-cheek',
+ 'path#tanuki-right-eye, path#tanuki-right-ear',
+ 'path#tanuki-nose',
+ 'path#tanuki-left-eye, path#tanuki-left-ear',
+ 'path#tanuki-left-cheek',
+]
+pieceIndex = 0
+firstPiece = pieces[0]
+
+currentTimer = null
+delay = 150
+
+clearHighlights = ->
+ $(".#{defaultClass}.highlight").attr('class', defaultClass)
+
+start = ->
+ clearHighlights()
+ pieceIndex = 0
+ pieces.reverse() unless pieces[0] == firstPiece
+ currentTimer = setInterval(work, delay)
+
+stop = ->
+ clearInterval(currentTimer)
+ clearHighlights()
+
+work = ->
+ clearHighlights()
+ $(pieces[pieceIndex]).attr('class', "#{defaultClass} highlight")
+
+ # If we hit the last piece, reset the index and then reverse the array to
+ # get a nice back-and-forth sweeping look
+ if pieceIndex == pieces.length - 1
+ pieceIndex = 0
+ pieces.reverse()
+ else
+ pieceIndex++
+
+$(document).on('page:fetch', start)
+$(document).on('page:change', stop)
diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee
index 83434c1b9ba..b3c73ffce5d 100644
--- a/app/assets/javascripts/merge_requests.js.coffee
+++ b/app/assets/javascripts/merge_requests.js.coffee
@@ -16,7 +16,7 @@
form = $("#issue_search_form")
search = $("#issue_search").val()
$('.merge-requests-holder').css("opacity", '0.5')
- issues_url = form.attr('action') + '? '+ form.serialize()
+ issues_url = form.attr('action') + '?' + form.serialize()
$.ajax
type: "GET"
@@ -27,7 +27,7 @@
success: (data) ->
$('.merge-requests-holder').html(data.html)
# Change url so if user reload a page - search results are saved
- History.replaceState {page: issues_url}, document.title, issues_url
+ history.replaceState {page: issues_url}, document.title, issues_url
MergeRequests.reload()
dataType: "json"
diff --git a/app/assets/javascripts/project_find_file.js.coffee b/app/assets/javascripts/project_find_file.js.coffee
new file mode 100644
index 00000000000..0dd32352c34
--- /dev/null
+++ b/app/assets/javascripts/project_find_file.js.coffee
@@ -0,0 +1,125 @@
+class @ProjectFindFile
+ constructor: (@element, @options)->
+ @filePaths = {}
+ @inputElement = @element.find(".file-finder-input")
+
+ # init event
+ @initEvent()
+
+ # focus text input box
+ @inputElement.focus()
+
+ # load file list
+ @load(@options.url)
+
+ # init event
+ initEvent: ->
+ @inputElement.off "keyup"
+ @inputElement.on "keyup", (event) =>
+ target = $(event.target)
+ value = target.val()
+ oldValue = target.data("oldValue") ? ""
+
+ if value != oldValue
+ target.data("oldValue", value)
+ @findFile()
+ @element.find("tr.tree-item").eq(0).addClass("selected").focus()
+
+ @element.find(".tree-content-holder .tree-table").on "click", (event) ->
+ if (event.target.nodeName != "A")
+ path = @element.find(".tree-item-file-name a", this).attr("href")
+ location.href = path if path
+
+ # find file
+ findFile: ->
+ searchText = @inputElement.val()
+ result = if searchText.length > 0 then fuzzaldrinPlus.filter(@filePaths, searchText) else @filePaths
+ @renderList result, searchText
+
+ # files pathes load
+ load: (url) ->
+ $.ajax
+ url: url
+ method: "get"
+ dataType: "json"
+ success: (data) =>
+ @element.find(".loading").hide()
+ @filePaths = data
+ @findFile()
+ @element.find(".files-slider tr.tree-item").eq(0).addClass("selected").focus()
+
+ # render result
+ renderList: (filePaths, searchText) ->
+ @element.find(".tree-table > tbody").empty()
+
+ for filePath, i in filePaths
+ break if i == 20
+
+ if searchText
+ matches = fuzzaldrinPlus.match(filePath, searchText)
+
+ blobItemUrl = "#{@options.blobUrlTemplate}/#{filePath}"
+
+ html = @makeHtml filePath, matches, blobItemUrl
+ @element.find(".tree-table > tbody").append(html)
+
+ # highlight text(awefwbwgtc -> <b>a</b>wefw<b>b</b>wgt<b>c</b> )
+ highlighter = (element, text, matches) ->
+ lastIndex = 0
+ highlightText = ""
+ matchedChars = []
+
+ for matchIndex in matches
+ unmatched = text.substring(lastIndex, matchIndex)
+
+ if unmatched
+ element.append(matchedChars.join("").bold()) if matchedChars.length
+ matchedChars = []
+ element.append(document.createTextNode(unmatched))
+
+ matchedChars.push(text[matchIndex])
+ lastIndex = matchIndex + 1
+
+ element.append(matchedChars.join("").bold()) if matchedChars.length
+ element.append(document.createTextNode(text.substring(lastIndex)))
+
+ # make tbody row html
+ makeHtml: (filePath, matches, blobItemUrl) ->
+ $tr = $("<tr class='tree-item'><td class='tree-item-file-name'><i class='fa fa-file-text-o fa-fw'></i><span class='str-truncated'><a></a></span></td></tr>")
+ if matches
+ $tr.find("a").replaceWith(highlighter($tr.find("a"), filePath, matches).attr("href", blobItemUrl))
+ else
+ $tr.find("a").attr("href", blobItemUrl).text(filePath)
+
+ return $tr
+
+ selectRow: (type) ->
+ rows = @element.find(".files-slider tr.tree-item")
+ selectedRow = @element.find(".files-slider tr.tree-item.selected")
+
+ if rows && rows.length > 0
+ if selectedRow && selectedRow.length > 0
+ if type == "UP"
+ next = selectedRow.prev()
+ else if type == "DOWN"
+ next = selectedRow.next()
+
+ if next.length > 0
+ selectedRow.removeClass "selected"
+ selectedRow = next
+ else
+ selectedRow = rows.eq(0)
+ selectedRow.addClass("selected").focus()
+
+ selectRowUp: =>
+ @selectRow "UP"
+
+ selectRowDown: =>
+ @selectRow "DOWN"
+
+ goToTree: =>
+ location.href = @options.treeUrl
+
+ goToBlob: =>
+ path = @element.find(".tree-item.selected .tree-item-file-name a").attr("href")
+ location.href = path if path
diff --git a/app/assets/javascripts/shortcuts_find_file.js.coffee b/app/assets/javascripts/shortcuts_find_file.js.coffee
new file mode 100644
index 00000000000..311e80bae19
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_find_file.js.coffee
@@ -0,0 +1,19 @@
+#= require shortcuts_navigation
+
+class @ShortcutsFindFile extends ShortcutsNavigation
+ constructor: (@projectFindFile) ->
+ super()
+ _oldStopCallback = Mousetrap.stopCallback
+ # override to fire shortcuts action when focus in textbox
+ Mousetrap.stopCallback = (event, element, combo) =>
+ if element == @projectFindFile.inputElement[0] and (combo == 'up' or combo == 'down' or combo == 'esc' or combo == 'enter')
+ # when press up/down key in textbox, cusor prevent to move to home/end
+ event.preventDefault()
+ return false
+
+ return _oldStopCallback(event, element, combo)
+
+ Mousetrap.bind('up', @projectFindFile.selectRowUp)
+ Mousetrap.bind('down', @projectFindFile.selectRowDown)
+ Mousetrap.bind('esc', @projectFindFile.goToTree)
+ Mousetrap.bind('enter', @projectFindFile.goToBlob)
diff --git a/app/assets/javascripts/shortcuts_tree.coffee b/app/assets/javascripts/shortcuts_tree.coffee
new file mode 100644
index 00000000000..ba0839c9fc0
--- /dev/null
+++ b/app/assets/javascripts/shortcuts_tree.coffee
@@ -0,0 +1,4 @@
+class @ShortcutsTree extends ShortcutsNavigation
+ constructor: ->
+ super()
+ Mousetrap.bind('t', -> ShortcutsTree.findAndFollowLink('.shortcuts-find-file'))
diff --git a/app/assets/stylesheets/framework/blocks.scss b/app/assets/stylesheets/framework/blocks.scss
index 206d39cc9b3..fa0e70847f3 100644
--- a/app/assets/stylesheets/framework/blocks.scss
+++ b/app/assets/stylesheets/framework/blocks.scss
@@ -72,6 +72,15 @@
> p:last-child {
margin-bottom: 0;
}
+
+ .block-controls {
+ float: right;
+
+ .control {
+ float: left;
+ margin-left: 10px;
+ }
+ }
}
.cover-block {
diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss
index a36fefe22c5..580012abd77 100644
--- a/app/assets/stylesheets/framework/calendar.scss
+++ b/app/assets/stylesheets/framework/calendar.scss
@@ -19,38 +19,33 @@
}
}
}
+
/**
* This overwrites the default values of the cal-heatmap gem
*/
.calendar {
.qi {
- background-color: #999;
fill: #fff;
}
.q1 {
- background-color: #dae289;
- fill: #ededed;
+ fill: #ededed !important;
}
.q2 {
- background-color: #cedb9c;
- fill: #ACD5F2;
+ fill: #ACD5F2 !important;
}
.q3 {
- background-color: #b5cf6b;
- fill: #7FA8D1;
+ fill: #7FA8D1 !important;
}
.q4 {
- background-color: #637939;
- fill: #49729B;
+ fill: #49729B !important;
}
.q5 {
- background-color: #3b6427;
- fill: #254E77;
+ fill: #254E77 !important;
}
.domain-background {
@@ -59,32 +54,7 @@
}
.ch-tooltip {
- position: absolute;
- display: none;
- margin-top: 22px;
- margin-left: 1px;
- font-size: 13px;
padding: 3px;
font-weight: 550;
- background-color: #222;
- span {
- position: absolute;
- width: 200px;
- text-align: center;
- visibility: hidden;
- border-radius: 10px;
- &:after {
- content: '';
- position: absolute;
- top: 100%;
- left: 50%;
- margin-left: -8px;
- width: 0;
- height: 0;
- border-top: 8px solid #000000;
- border-right: 8px solid transparent;
- border-left: 8px solid transparent;
- }
- }
}
}
diff --git a/app/assets/stylesheets/framework/sidebar.scss b/app/assets/stylesheets/framework/sidebar.scss
index 458af76cb75..83243dd2457 100644
--- a/app/assets/stylesheets/framework/sidebar.scss
+++ b/app/assets/stylesheets/framework/sidebar.scss
@@ -105,7 +105,7 @@
.tanuki-shape {
transition: all 0.8s;
- &:hover {
+ &:hover, &.highlight {
fill: rgb(255, 255, 255);
transition: all 0.1s;
}
diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss
index c3e4ad0ad00..714369d9f15 100644
--- a/app/assets/stylesheets/framework/typography.scss
+++ b/app/assets/stylesheets/framework/typography.scss
@@ -54,17 +54,17 @@
h3 {
margin: 24px 0 12px 0;
- font-size: 1.25em;
+ font-size: 1.1em;
}
h4 {
margin: 24px 0 12px 0;
- font-size: 1.1em;
+ font-size: 0.98em;
}
h5 {
margin: 24px 0 12px 0;
- font-size: 1em;
+ font-size: 0.95em;
}
h6 {
diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss
index c9dfcff6290..800df95cff3 100644
--- a/app/assets/stylesheets/pages/commits.scss
+++ b/app/assets/stylesheets/pages/commits.scss
@@ -28,10 +28,6 @@
}
}
-.commits-feed-holder {
- float: right;
-}
-
li.commit {
list-style: none;
@@ -122,3 +118,59 @@ li.commit {
color: $gl-gray;
}
}
+
+.divergence-graph {
+ padding: 12px 12px 0 0;
+ float: right;
+
+ .graph-side {
+ position: relative;
+ width: 80px;
+ height: 22px;
+ padding: 5px 0 13px;
+ float: left;
+
+ .bar {
+ position: absolute;
+ height: 4px;
+ background-color: #ccc;
+ }
+
+ .bar-behind {
+ right: 0;
+ border-radius: 3px 0 0 3px;
+ }
+
+ .bar-ahead {
+ left: 0;
+ border-radius: 0 3px 3px 0;
+ }
+
+ .count {
+ padding-top: 6px;
+ padding-bottom: 0px;
+ font-size: 12px;
+ color: #333;
+ display: block;
+ }
+
+ .count-behind {
+ padding-right: 4px;
+ text-align: right;
+ }
+
+ .count-ahead {
+ padding-left: 4px;
+ text-align: left;
+ }
+ }
+
+ .graph-separator {
+ position: relative;
+ width: 1px;
+ height: 18px;
+ margin: 5px 0 0;
+ float: left;
+ background-color: #ccc;
+ }
+}
diff --git a/app/assets/stylesheets/pages/events.scss b/app/assets/stylesheets/pages/events.scss
index 282aaf2219b..984b4b91216 100644
--- a/app/assets/stylesheets/pages/events.scss
+++ b/app/assets/stylesheets/pages/events.scss
@@ -138,6 +138,7 @@
*/
.event-last-push {
overflow: auto;
+ width: 100%;
.event-last-push-text {
@include str-truncated(100%);
padding: 5px 0;
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 9da273a0b6b..d4b44004f4f 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -94,8 +94,16 @@
}
.cross-project-reference {
- font-weight: bold;
color: $gl-link-color;
+
+ span {
+ white-space: nowrap;
+ width: 85%;
+ overflow: hidden;
+ position: relative;
+ display: inline-block;
+ text-overflow: ellipsis;
+ }
button {
float: right;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index cff3edb7ed2..d32509b7d49 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -26,6 +26,13 @@
}
.project-home-panel {
+
+ .cover-controls {
+ .project-settings-dropdown {
+ margin-left: 10px;
+ }
+ }
+
.project-identicon-holder {
margin-bottom: 16px;
@@ -404,10 +411,15 @@ ul.nav.nav-projects-tabs {
}
}
+.last-push-widget {
+ margin-top: -1px;
+}
+
.top-area {
border-bottom: 1px solid #EEE;
margin: 0 -16px;
padding: 0 $gl-padding;
+ height: 42px;
ul.left-top-menu {
display: inline-block;
@@ -514,6 +526,7 @@ pre.light-well {
.projects-search-form {
margin: -$gl-padding;
padding: $gl-padding;
+ padding-bottom: 0;
margin-bottom: 0px;
input {
diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss
index d4ab6967ccd..97505edeabf 100644
--- a/app/assets/stylesheets/pages/tree.scss
+++ b/app/assets/stylesheets/pages/tree.scss
@@ -1,5 +1,13 @@
.tree-holder {
+ .file-finder {
+ width: 50%;
+ .file-finder-input {
+ width: 95%;
+ display: inline-block;
+ }
+ }
+
.tree-table {
margin-bottom: 0;
diff --git a/app/controllers/abuse_reports_controller.rb b/app/controllers/abuse_reports_controller.rb
index 20bc5173f1d..38814459f66 100644
--- a/app/controllers/abuse_reports_controller.rb
+++ b/app/controllers/abuse_reports_controller.rb
@@ -9,12 +9,10 @@ class AbuseReportsController < ApplicationController
@abuse_report.reporter = current_user
if @abuse_report.save
- if current_application_settings.admin_notification_email.present?
- AbuseReportMailer.notify(@abuse_report.id).deliver_later
- end
+ @abuse_report.notify
message = "Thank you for your report. A GitLab administrator will look into it shortly."
- redirect_to root_path, notice: message
+ redirect_to @abuse_report.user, notice: message
else
render :new
end
@@ -23,6 +21,9 @@ class AbuseReportsController < ApplicationController
private
def report_params
- params.require(:abuse_report).permit(:user_id, :message)
+ params.require(:abuse_report).permit(%i(
+ message
+ user_id
+ ))
end
end
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 10e736fd362..44d06b6a647 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -70,8 +70,6 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:metrics_enabled,
:metrics_host,
:metrics_port,
- :metrics_username,
- :metrics_password,
:metrics_pool_size,
:metrics_timeout,
:metrics_method_call_threshold,
diff --git a/app/controllers/admin/builds_controller.rb b/app/controllers/admin/builds_controller.rb
index 83d9684c706..0db91eaaf2e 100644
--- a/app/controllers/admin/builds_controller.rb
+++ b/app/controllers/admin/builds_controller.rb
@@ -5,12 +5,12 @@ class Admin::BuildsController < Admin::ApplicationController
@builds = @all_builds.order('created_at DESC')
@builds =
case @scope
- when 'all'
- @builds
+ when 'running'
+ @builds.running_or_pending.reverse_order
when 'finished'
@builds.finished
else
- @builds.running_or_pending.reverse_order
+ @builds
end
@builds = @builds.page(params[:page]).per(30)
end
diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb
index 9575a87ee41..a9bf4321f73 100644
--- a/app/controllers/explore/groups_controller.rb
+++ b/app/controllers/explore/groups_controller.rb
@@ -1,6 +1,6 @@
class Explore::GroupsController < Explore::ApplicationController
def index
- @groups = GroupsFinder.new.execute(current_user)
+ @groups = Group.order_id_desc
@groups = @groups.search(params[:search]) if params[:search].present?
@groups = @groups.sort(@sort = params[:sort])
@groups = @groups.page(params[:page]).per(PER_PAGE)
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 3c2849a7601..4db3b3bf23d 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -9,6 +9,11 @@ class Projects::BranchesController < Projects::ApplicationController
@sort = params[:sort] || 'name'
@branches = @repository.branches_sorted_by(@sort)
@branches = Kaminari.paginate_array(@branches).page(params[:page]).per(PER_PAGE)
+
+ @max_commits = @branches.reduce(0) do |memo, branch|
+ diverging_commit_counts = repository.diverging_commit_counts(branch)
+ [memo, diverging_commit_counts[:behind], diverging_commit_counts[:ahead]].max
+ end
end
def recent
diff --git a/app/controllers/projects/builds_controller.rb b/app/controllers/projects/builds_controller.rb
index 26ba12520c7..39d3ba26ba2 100644
--- a/app/controllers/projects/builds_controller.rb
+++ b/app/controllers/projects/builds_controller.rb
@@ -12,12 +12,12 @@ class Projects::BuildsController < Projects::ApplicationController
@builds = @all_builds.order('created_at DESC')
@builds =
case @scope
- when 'all'
- @builds
+ when 'running'
+ @builds.running_or_pending.reverse_order
when 'finished'
@builds.finished
else
- @builds.running_or_pending.reverse_order
+ @builds
end
@builds = @builds.page(params[:page]).per(30)
end
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index 04a88990bf4..bf5b54c8cb7 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -8,10 +8,16 @@ class Projects::CommitsController < Projects::ApplicationController
before_action :authorize_download_code!
def show
- @repo = @project.repository
@limit, @offset = (params[:limit] || 40).to_i, (params[:offset] || 0).to_i
+ search = params[:search]
+
+ @commits =
+ if search.present?
+ @repository.find_commits_by_message(search, @ref, @path, @limit, @offset).compact
+ else
+ @repository.commits(@ref, @path, @limit, @offset)
+ end
- @commits = @repo.commits(@ref, @path, @limit, @offset)
@note_counts = project.notes.where(commit_id: @commits.map(&:id)).
group(:commit_id).count
diff --git a/app/controllers/projects/find_file_controller.rb b/app/controllers/projects/find_file_controller.rb
new file mode 100644
index 00000000000..54a0c447aee
--- /dev/null
+++ b/app/controllers/projects/find_file_controller.rb
@@ -0,0 +1,26 @@
+# Controller for viewing a repository's file structure
+class Projects::FindFileController < Projects::ApplicationController
+ include ExtractsPath
+ include ActionView::Helpers::SanitizeHelper
+ include TreeHelper
+
+ before_action :require_non_empty_project
+ before_action :assign_ref_vars
+ before_action :authorize_download_code!
+
+ def show
+ return render_404 unless @repository.commit(@ref)
+
+ respond_to do |format|
+ format.html
+ end
+ end
+
+ def list
+ file_paths = @repo.ls_files(@ref)
+
+ respond_to do |format|
+ format.json { render json: file_paths }
+ end
+ end
+end
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index ab5c953189c..de948d271c8 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -153,7 +153,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def merge_check
- @merge_request.check_if_can_be_merged if @merge_request.unchecked?
+ @merge_request.check_if_can_be_merged
render partial: "projects/merge_requests/widget/show.html.haml", layout: false
end
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index c4e18c17077..a8f091819ca 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -20,6 +20,8 @@ class Projects::RefsController < Projects::ApplicationController
namespace_project_network_path(@project.namespace, @project, @id, @options)
when "graphs"
namespace_project_graph_path(@project.namespace, @project, @id)
+ when "find_file"
+ namespace_project_find_file_path(@project.namespace, @project, @id)
when "graphs_commits"
commits_namespace_project_graph_path(@project.namespace, @project, @id)
else
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index 3004722bce0..935f7d75c6a 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -8,7 +8,7 @@ class ProjectsController < ApplicationController
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
# Authorize
- before_action :authorize_admin_project!, only: [:edit, :update]
+ before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping]
before_action :event_filter, only: [:show, :activity]
layout :determine_layout
@@ -166,6 +166,15 @@ class ProjectsController < ApplicationController
end
end
+ def housekeeping
+ ::Projects::HousekeepingService.new(@project).execute
+
+ respond_to do |format|
+ flash[:notice] = "Housekeeping successfully started."
+ format.html { redirect_to project_path(@project) }
+ end
+ end
+
def toggle_star
current_user.toggle_star(@project)
@project.reload
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 30cb869eb2a..280228dbcc0 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -7,7 +7,7 @@ class UsersController < ApplicationController
@projects = PersonalProjectsFinder.new(@user).execute(current_user)
- @groups = JoinedGroupsFinder.new(@user).execute(current_user)
+ @groups = @user.groups.order_id_desc
respond_to do |format|
format.html
diff --git a/app/finders/groups_finder.rb b/app/finders/groups_finder.rb
deleted file mode 100644
index 91cb0f228f0..00000000000
--- a/app/finders/groups_finder.rb
+++ /dev/null
@@ -1,44 +0,0 @@
-class GroupsFinder
- # Finds the groups available to the given user.
- #
- # current_user - The user to find the groups for.
- #
- # Returns an ActiveRecord::Relation.
- def execute(current_user = nil)
- if current_user
- relation = groups_visible_to_user(current_user)
- else
- relation = public_groups
- end
-
- relation.order_id_desc
- end
-
- private
-
- # This method returns the groups "current_user" can see.
- def groups_visible_to_user(current_user)
- base = groups_for_projects(public_and_internal_projects)
-
- union = Gitlab::SQL::Union.
- new([base.select(:id), current_user.authorized_groups.select(:id)])
-
- Group.where("namespaces.id IN (#{union.to_sql})")
- end
-
- def public_groups
- groups_for_projects(public_projects)
- end
-
- def groups_for_projects(projects)
- Group.public_and_given_groups(projects.select(:namespace_id))
- end
-
- def public_projects
- Project.unscoped.public_only
- end
-
- def public_and_internal_projects
- Project.unscoped.public_and_internal_only
- end
-end
diff --git a/app/finders/joined_groups_finder.rb b/app/finders/joined_groups_finder.rb
deleted file mode 100644
index e7523136fea..00000000000
--- a/app/finders/joined_groups_finder.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-# Class for finding the groups a user is a member of.
-class JoinedGroupsFinder
- def initialize(user = nil)
- @user = user
- end
-
- # Finds the groups of the source user, optionally limited to those visible to
- # the current user.
- #
- # current_user - If given the groups of "@user" will only include the groups
- # "current_user" can also see.
- #
- # Returns an ActiveRecord::Relation.
- def execute(current_user = nil)
- if current_user
- relation = groups_visible_to_user(current_user)
- else
- relation = public_groups
- end
-
- relation.order_id_desc
- end
-
- private
-
- # Returns the groups the user in "current_user" can see.
- #
- # This list includes all public/internal projects as well as the projects of
- # "@user" that "current_user" also has access to.
- def groups_visible_to_user(current_user)
- base = @user.authorized_groups.visible_to_user(current_user)
- extra = public_and_internal_groups
- union = Gitlab::SQL::Union.new([base.select(:id), extra.select(:id)])
-
- Group.where("namespaces.id IN (#{union.to_sql})")
- end
-
- def public_groups
- groups_for_projects(@user.authorized_projects.public_only)
- end
-
- def public_and_internal_groups
- groups_for_projects(@user.authorized_projects.public_and_internal_only)
- end
-
- def groups_for_projects(projects)
- @user.groups.public_and_given_groups(projects.select(:namespace_id))
- end
-end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index f7f7a1a02d3..2b9bad9c9ea 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -206,7 +206,7 @@ module ApplicationHelper
element = content_tag :time, time.to_s,
class: "#{html_class} js-timeago js-timeago-pending",
datetime: time.getutc.iso8601,
- title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
+ title: time.in_time_zone.to_s(:medium),
data: { toggle: 'tooltip', placement: placement, container: 'body' }
unless skip_js
diff --git a/app/helpers/auth_helper.rb b/app/helpers/auth_helper.rb
index 0cfc0565e84..de669e529a7 100644
--- a/app/helpers/auth_helper.rb
+++ b/app/helpers/auth_helper.rb
@@ -1,5 +1,5 @@
module AuthHelper
- PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook).freeze
+ PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2 facebook azure_oauth2).freeze
FORM_BASED_PROVIDERS = [/\Aldap/, 'crowd'].freeze
def ldap_enabled?
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 80e2741b09a..a7080ddfefb 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -80,7 +80,7 @@ module IssuesHelper
xml.link href: namespace_project_issue_url(issue.project.namespace,
issue.project, issue)
xml.title truncate(issue.title, length: 80)
- xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
+ xml.updated issue.created_at.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(issue.author_email))
xml.author do |author|
xml.name issue.author_name
@@ -99,7 +99,7 @@ module IssuesHelper
end
def emoji_icon(name, unicode = nil, aliases = [])
- unicode ||= Emoji.emoji_filename(name)
+ unicode ||= Emoji.emoji_filename(name) rescue ""
content_tag :div, "",
class: "icon emoji-icon emoji-#{unicode}",
diff --git a/app/helpers/page_layout_helper.rb b/app/helpers/page_layout_helper.rb
index 791cb9e50bd..82f805fa444 100644
--- a/app/helpers/page_layout_helper.rb
+++ b/app/helpers/page_layout_helper.rb
@@ -27,35 +27,20 @@ module PageLayoutHelper
#
# Returns an HTML-safe String.
def page_description(description = nil)
- @page_description ||= page_description_default
-
if description.present?
@page_description = description.squish
- else
+ elsif @page_description.present?
sanitize(@page_description, tags: []).truncate_words(30)
end
end
- # Default value for page_description when one hasn't been defined manually by
- # a view
- def page_description_default
- if @project
- @project.description || brand_title
- else
- brand_title
- end
- end
-
def page_image
default = image_url('gitlab_logo.png')
- if @project
- @project.avatar_url || default
- elsif @user
- avatar_icon(@user)
- else
- default
- end
+ subject = @project || @user || @group
+
+ image = subject.avatar_url if subject.present?
+ image || default
end
# Define or get attributes to be used as Twitter card metadata
diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb
index a6ee6880247..d4f78258626 100644
--- a/app/helpers/search_helper.rb
+++ b/app/helpers/search_helper.rb
@@ -70,7 +70,7 @@ module SearchHelper
# Autocomplete results for the current user's groups
def groups_autocomplete(term, limit = 5)
- GroupsFinder.new.execute(current_user).search(term).limit(limit).map do |group|
+ Group.search(term).limit(limit).map do |group|
{
label: "group: #{search_result_sanitize(group.name)}",
url: group_path(group)
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
index bb12d43f397..99d7df64a83 100644
--- a/app/helpers/sorting_helper.rb
+++ b/app/helpers/sorting_helper.rb
@@ -19,7 +19,7 @@ module SortingHelper
end
def sort_title_recently_updated
- 'Recently updated'
+ 'Last updated'
end
def sort_title_oldest_created
@@ -27,7 +27,7 @@ module SortingHelper
end
def sort_title_recently_created
- 'Recently created'
+ 'Last created'
end
def sort_title_milestone_soon
diff --git a/app/mailers/abuse_report_mailer.rb b/app/mailers/abuse_report_mailer.rb
index f0c41f69a5c..d0ce827a595 100644
--- a/app/mailers/abuse_report_mailer.rb
+++ b/app/mailers/abuse_report_mailer.rb
@@ -2,11 +2,19 @@ class AbuseReportMailer < BaseMailer
include Gitlab::CurrentSettings
def notify(abuse_report_id)
+ return unless deliverable?
+
@abuse_report = AbuseReport.find(abuse_report_id)
mail(
- to: current_application_settings.admin_notification_email,
+ to: current_application_settings.admin_notification_email,
subject: "#{@abuse_report.user.name} (#{@abuse_report.user.username}) was reported for abuse"
)
end
+
+ private
+
+ def deliverable?
+ current_application_settings.admin_notification_email.present?
+ end
end
diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb
index 65f37e92677..e1382d2da12 100644
--- a/app/mailers/emails/notes.rb
+++ b/app/mailers/emails/notes.rb
@@ -48,7 +48,7 @@ module Emails
yield
- SentNotification.record(@note, recipient_id, reply_key)
+ SentNotification.record_note(@note, recipient_id, reply_key)
end
end
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 1b3ee757040..5a1a67db8e1 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -69,7 +69,7 @@ class Ability
subject.group
end
- if group && group.public_profile?
+ if group && group.projects.public_only.any?
[:read_group]
else
[]
diff --git a/app/models/abuse_report.rb b/app/models/abuse_report.rb
index 89b3116b9f2..55864236b2f 100644
--- a/app/models/abuse_report.rb
+++ b/app/models/abuse_report.rb
@@ -18,4 +18,10 @@ class AbuseReport < ActiveRecord::Base
validates :user, presence: true
validates :message, presence: true
validates :user_id, uniqueness: true
+
+ def notify
+ return unless self.persisted?
+
+ AbuseReportMailer.notify(self.id).deliver_later
+ end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index be69d317d73..6c6c2468374 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -27,9 +27,20 @@
# admin_notification_email :string(255)
# shared_runners_enabled :boolean default(TRUE), not null
# max_artifacts_size :integer default(100), not null
-# runners_registration_token :string(255)
-# require_two_factor_authentication :boolean default(TRUE)
+# runners_registration_token :string
+# require_two_factor_authentication :boolean default(FALSE)
# two_factor_grace_period :integer default(48)
+# metrics_enabled :boolean default(FALSE)
+# metrics_host :string default("localhost")
+# metrics_username :string
+# metrics_password :string
+# metrics_pool_size :integer default(16)
+# metrics_timeout :integer default(10)
+# metrics_method_call_threshold :integer default(10)
+# recaptcha_enabled :boolean default(FALSE)
+# recaptcha_site_key :string
+# recaptcha_private_key :string
+# metrics_port :integer default(8089)
#
class ApplicationSetting < ActiveRecord::Base
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index 3e67b2771c1..a4779d06de8 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -29,6 +29,7 @@
# target_url :string(255)
# description :string(255)
# artifacts_file :text
+# gl_project_id :integer
#
module Ci
@@ -54,6 +55,8 @@ module Ci
# To prevent db load megabytes of data from trace
default_scope -> { select(Ci::Build.columns_without_lazy) }
+ before_destroy { project }
+
class << self
def columns_without_lazy
(column_names - LAZY_ATTRIBUTES).map do |column_name|
@@ -145,10 +148,6 @@ module Ci
end
end
- def project
- commit.project
- end
-
def project_id
commit.project.id
end
@@ -207,7 +206,7 @@ module Ci
def trace
trace = raw_trace
- if project && trace.present?
+ if project && trace.present? && project.runners_token.present?
trace.gsub(project.runners_token, 'xxxxxx')
else
trace
diff --git a/app/models/ci/runner_project.rb b/app/models/ci/runner_project.rb
index 93d9be144e8..7b16f207a26 100644
--- a/app/models/ci/runner_project.rb
+++ b/app/models/ci/runner_project.rb
@@ -2,11 +2,12 @@
#
# Table name: ci_runner_projects
#
-# id :integer not null, primary key
-# runner_id :integer not null
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
+# id :integer not null, primary key
+# runner_id :integer not null
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# gl_project_id :integer
#
module Ci
diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb
index 23516709a41..bb98cd5c7da 100644
--- a/app/models/ci/trigger.rb
+++ b/app/models/ci/trigger.rb
@@ -2,12 +2,13 @@
#
# Table name: ci_triggers
#
-# id :integer not null, primary key
-# token :string(255)
-# project_id :integer not null
-# deleted_at :datetime
-# created_at :datetime
-# updated_at :datetime
+# id :integer not null, primary key
+# token :string(255)
+# project_id :integer
+# deleted_at :datetime
+# created_at :datetime
+# updated_at :datetime
+# gl_project_id :integer
#
module Ci
diff --git a/app/models/ci/variable.rb b/app/models/ci/variable.rb
index 56759d3e50f..7f6f497f325 100644
--- a/app/models/ci/variable.rb
+++ b/app/models/ci/variable.rb
@@ -3,12 +3,13 @@
# Table name: ci_variables
#
# id :integer not null, primary key
-# project_id :integer not null
+# project_id :integer
# key :string(255)
# value :text
# encrypted_value :text
# encrypted_value_salt :string(255)
# encrypted_value_iv :string(255)
+# gl_project_id :integer
#
module Ci
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index 21c5c87bc3d..ff479493474 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -1,30 +1,35 @@
# == Schema Information
#
-# project_id integer
-# status string
-# finished_at datetime
-# trace text
-# created_at datetime
-# updated_at datetime
-# started_at datetime
-# runner_id integer
-# coverage float
-# commit_id integer
-# commands text
-# job_id integer
-# name string
-# deploy boolean default: false
-# options text
-# allow_failure boolean default: false, null: false
-# stage string
-# trigger_request_id integer
-# stage_idx integer
-# tag boolean
-# ref string
-# user_id integer
-# type string
-# target_url string
-# description string
+# Table name: ci_builds
+#
+# id :integer not null, primary key
+# project_id :integer
+# status :string(255)
+# finished_at :datetime
+# trace :text
+# created_at :datetime
+# updated_at :datetime
+# started_at :datetime
+# runner_id :integer
+# coverage :float
+# commit_id :integer
+# commands :text
+# job_id :integer
+# name :string(255)
+# deploy :boolean default(FALSE)
+# options :text
+# allow_failure :boolean default(FALSE), not null
+# stage :string(255)
+# trigger_request_id :integer
+# stage_idx :integer
+# tag :boolean
+# ref :string(255)
+# user_id :integer
+# type :string(255)
+# target_url :string(255)
+# description :string(255)
+# artifacts_file :text
+# gl_project_id :integer
#
class CommitStatus < ActiveRecord::Base
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 6316ee208b5..98f71ae8cb0 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -51,8 +51,11 @@ module Mentionable
else
self.class.mentionable_attrs.each do |attr, options|
text = send(attr)
- options[:cache_key] = [self, attr] if options.delete(:cache) && self.persisted?
- ext.analyze(text, options)
+
+ context = options.dup
+ context[:cache_key] = [self, attr] if context.delete(:cache) && self.persisted?
+
+ ext.analyze(text, context)
end
end
diff --git a/app/models/generic_commit_status.rb b/app/models/generic_commit_status.rb
index 12c934e2494..97f4f03a9a5 100644
--- a/app/models/generic_commit_status.rb
+++ b/app/models/generic_commit_status.rb
@@ -29,6 +29,7 @@
# target_url :string(255)
# description :string(255)
# artifacts_file :text
+# gl_project_id :integer
#
class GenericCommitStatus < CommitStatus
diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb
index af1d7562ebe..7ee276255a0 100644
--- a/app/models/global_milestone.rb
+++ b/app/models/global_milestone.rb
@@ -121,9 +121,9 @@ class GlobalMilestone
def expires_at
if due_date
if due_date.past?
- "expired at #{due_date.stamp("Aug 21, 2011")}"
+ "expired on #{due_date.to_s(:medium)}"
else
- "expires at #{due_date.stamp("Aug 21, 2011")}"
+ "expires on #{due_date.to_s(:medium)}"
end
end
end
diff --git a/app/models/group.rb b/app/models/group.rb
index 1b5b875a19e..5a31b46920c 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -11,7 +11,6 @@
# type :string(255)
# description :string(255) default(""), not null
# avatar :string(255)
-# public :boolean default(FALSE)
#
require 'carrierwave/orm/activerecord'
@@ -50,10 +49,6 @@ class Group < Namespace
User.reference_pattern
end
- def public_and_given_groups(ids)
- where('public IS TRUE OR namespaces.id IN (?)', ids)
- end
-
def visible_to_user(user)
where(id: user.authorized_groups.select(:id).reorder(nil))
end
@@ -125,10 +120,6 @@ class Group < Namespace
end
end
- def public_profile?
- self.public || projects.public_only.any?
- end
-
def post_create_hook
Gitlab::AppLogger.info("Group \"#{name}\" was created")
diff --git a/app/models/hooks/project_hook.rb b/app/models/hooks/project_hook.rb
index 22638057773..fa18ba5dbbe 100644
--- a/app/models/hooks/project_hook.rb
+++ b/app/models/hooks/project_hook.rb
@@ -15,6 +15,7 @@
# tag_push_events :boolean default(FALSE)
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
+# build_events :boolean default(FALSE), not null
#
class ProjectHook < WebHook
diff --git a/app/models/hooks/service_hook.rb b/app/models/hooks/service_hook.rb
index 09bb3ee52a2..b333a337347 100644
--- a/app/models/hooks/service_hook.rb
+++ b/app/models/hooks/service_hook.rb
@@ -15,6 +15,7 @@
# tag_push_events :boolean default(FALSE)
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
+# build_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 2f63c59b07e..d81512fae5d 100644
--- a/app/models/hooks/system_hook.rb
+++ b/app/models/hooks/system_hook.rb
@@ -15,6 +15,7 @@
# tag_push_events :boolean default(FALSE)
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
+# build_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 40eb0e20b4b..d0aadfc330a 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -15,6 +15,7 @@
# tag_push_events :boolean default(FALSE)
# note_events :boolean default(FALSE), not null
# enable_ssl_verification :boolean default(TRUE)
+# build_events :boolean default(FALSE), not null
#
class WebHook < ActiveRecord::Base
@@ -60,7 +61,7 @@ class WebHook < ActiveRecord::Base
basic_auth: auth)
end
- [response.code == 200, ActionView::Base.full_sanitizer.sanitize(response.to_s)]
+ [(response.code >= 200 && response.code < 300), ActionView::Base.full_sanitizer.sanitize(response.to_s)]
rescue SocketError, OpenSSL::SSL::SSLError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
logger.error("WebHook Error => #{e}")
[false, e.to_s]
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index ac25d38eb63..5723ff36e02 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -2,28 +2,28 @@
#
# Table name: merge_requests
#
-# id :integer not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
-# source_project_id :integer not null
-# author_id :integer
-# assignee_id :integer
-# title :string(255)
-# created_at :datetime
-# updated_at :datetime
-# milestone_id :integer
-# state :string(255)
-# merge_status :string(255)
-# target_project_id :integer not null
-# iid :integer
-# description :text
-# position :integer default(0)
-# locked_at :datetime
-# updated_by_id :integer
-# merge_error :string(255)
-# merge_params :text (serialized to hash)
-# merge_when_build_succeeds :boolean default(false), not null
-# merge_user_id :integer
+# id :integer not null, primary key
+# target_branch :string(255) not null
+# source_branch :string(255) not null
+# source_project_id :integer not null
+# author_id :integer
+# assignee_id :integer
+# title :string(255)
+# created_at :datetime
+# updated_at :datetime
+# milestone_id :integer
+# state :string(255)
+# merge_status :string(255)
+# target_project_id :integer not null
+# iid :integer
+# description :text
+# position :integer default(0)
+# locked_at :datetime
+# updated_by_id :integer
+# merge_error :string(255)
+# merge_params :text
+# merge_when_build_succeeds :boolean default(FALSE), not null
+# merge_user_id :integer
#
require Rails.root.join("app/models/commit")
@@ -229,6 +229,8 @@ class MergeRequest < ActiveRecord::Base
end
def check_if_can_be_merged
+ return unless unchecked?
+
can_be_merged =
project.repository.can_be_merged?(source_sha, target_branch)
@@ -252,7 +254,11 @@ class MergeRequest < ActiveRecord::Base
end
def mergeable?
- open? && !work_in_progress? && can_be_merged?
+ return false unless open? && !work_in_progress?
+
+ check_if_can_be_merged
+
+ can_be_merged?
end
def gitlab_merge_status
@@ -452,6 +458,10 @@ class MergeRequest < ActiveRecord::Base
!source_branch_exists? || !target_branch_exists?
end
+ def broken?
+ self.commits.blank? || branch_missing? || cannot_be_merged?
+ end
+
def can_be_merged_by?(user)
::Gitlab::GitAccess.new(user, project).can_push_to_branch?(target_branch)
end
@@ -507,8 +517,4 @@ class MergeRequest < ActiveRecord::Base
def ci_commit
@ci_commit ||= source_project.ci_commit(last_commit.id) if last_commit && source_project
end
-
- def broken?
- self.commits.blank? || branch_missing? || cannot_be_merged?
- end
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index d8c7536cd31..c9a0ad8b9b6 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -22,6 +22,7 @@ class Milestone < ActiveRecord::Base
include InternalId
include Sortable
+ include Referable
include StripAttribute
belongs_to :project
@@ -61,6 +62,27 @@ class Milestone < ActiveRecord::Base
end
end
+ def self.reference_pattern
+ nil
+ end
+
+ def self.link_reference_pattern
+ super("milestones", /(?<milestone>\d+)/)
+ end
+
+ def to_reference(from_project = nil)
+ escaped_title = self.title.gsub("]", "\\]")
+
+ h = Gitlab::Application.routes.url_helpers
+ url = h.namespace_project_milestone_url(self.project.namespace, self.project, self)
+
+ "[#{escaped_title}](#{url})"
+ end
+
+ def reference_link_text(from_project = nil)
+ self.title
+ end
+
def expired?
if due_date
due_date.past?
@@ -90,9 +112,9 @@ class Milestone < ActiveRecord::Base
def expires_at
if due_date
if due_date.past?
- "expired at #{due_date.stamp("Aug 21, 2011")}"
+ "expired on #{due_date.to_s(:medium)}"
else
- "expires at #{due_date.stamp("Aug 21, 2011")}"
+ "expires on #{due_date.to_s(:medium)}"
end
end
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index adafabbec07..bdb33f37495 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -11,7 +11,6 @@
# type :string(255)
# description :string(255) default(""), not null
# avatar :string(255)
-# public :boolean default(FALSE)
#
class Namespace < ActiveRecord::Base
diff --git a/app/models/project.rb b/app/models/project.rb
index 017471995ec..31990485f7d 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -29,6 +29,13 @@
# import_source :string(255)
# commit_count :integer default(0)
# import_error :text
+# ci_id :integer
+# builds_enabled :boolean default(TRUE), not null
+# shared_runners_enabled :boolean default(TRUE), not null
+# runners_token :string
+# build_coverage_regex :string
+# build_allow_git_fetch :boolean default(TRUE), not null
+# build_timeout :integer default(3600), not null
#
require 'carrierwave/orm/activerecord'
@@ -43,6 +50,7 @@ class Project < ActiveRecord::Base
include Sortable
include AfterCommitQueue
include CaseSensitivity
+ include TokenAuthenticatable
extend Gitlab::ConfigHelper
@@ -81,6 +89,7 @@ class Project < ActiveRecord::Base
acts_as_taggable_on :tags
attr_accessor :new_default_branch
+ attr_accessor :old_path_with_namespace
# Relations
belongs_to :creator, foreign_key: 'creator_id', class_name: 'User'
@@ -185,10 +194,8 @@ class Project < ActiveRecord::Base
if: ->(project) { project.avatar.present? && project.avatar_changed? }
validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
- before_validation :set_runners_token_token
- def set_runners_token_token
- self.runners_token = SecureRandom.hex(15) if self.runners_token.blank?
- end
+ add_authentication_token_field :runners_token
+ before_save :ensure_runners_token
mount_uploader :avatar, AvatarUploader
@@ -701,6 +708,11 @@ class Project < ActiveRecord::Base
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
send_move_instructions(old_path_with_namespace)
reset_events_cache
+
+ @old_path_with_namespace = old_path_with_namespace
+
+ SystemHooksService.new.execute_hooks_for(self, :rename)
+
@repository = nil
rescue
# Returning false does not rollback after_* transaction but gives
@@ -769,6 +781,8 @@ class Project < ActiveRecord::Base
end
def change_head(branch)
+ # Cached divergent commit counts are based on repository head
+ repository.expire_branch_cache
gitlab_shell.update_repository_head(self.path_with_namespace, branch)
reload_default_branch
end
@@ -885,4 +899,8 @@ class Project < ActiveRecord::Base
return true unless forked?
Gitlab::VisibilityLevel.allowed_fork_levels(forked_from_project.visibility_level).include?(level.to_i)
end
+
+ def runners_token
+ ensure_runners_token!
+ end
end
diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb
index e6e16058d41..792ad804575 100644
--- a/app/models/project_services/asana_service.rb
+++ b/app/models/project_services/asana_service.rb
@@ -16,7 +16,9 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
+
require 'asana'
class AsanaService < Service
@@ -40,8 +42,8 @@ get the commit comment added to it.
You can also close a task with a message containing: `fix #123456`.
-You can find your Api Keys here:
-http://developer.asana.com/documentation/#api_keys'
+You can create a Personal Access Token here:
+http://app.asana.com/-/account_api'
end
def to_param
@@ -53,14 +55,12 @@ http://developer.asana.com/documentation/#api_keys'
{
type: 'text',
name: 'api_key',
- placeholder: 'User API token. User must have access to task,
-all comments will be attributed to this user.'
+ placeholder: 'User Personal Access Token. User must have access to task, all comments will be attributed to this user.'
},
{
type: 'text',
name: 'restrict_to_branch',
- placeholder: 'Comma-separated list of branches which will be
-automatically inspected. Leave blank to include all branches.'
+ placeholder: 'Comma-separated list of branches which will be automatically inspected. Leave blank to include all branches.'
}
]
end
@@ -69,58 +69,58 @@ automatically inspected. Leave blank to include all branches.'
%w(push)
end
+ def client
+ @_client ||= begin
+ Asana::Client.new do |c|
+ c.authentication :access_token, api_key
+ end
+ end
+ end
+
def execute(data)
return unless supported_events.include?(data[:object_kind])
- Asana.configure do |client|
- client.api_key = api_key
- end
-
- user = data[:user_name]
+ # check the branch restriction is poplulated and branch is not included
branch = Gitlab::Git.ref_name(data[:ref])
-
branch_restriction = restrict_to_branch.to_s
-
- # check the branch restriction is poplulated and branch is not included
if branch_restriction.length > 0 && branch_restriction.index(branch).nil?
return
end
+ user = data[:user_name]
project_name = project.name_with_namespace
- push_msg = user + ' pushed to branch ' + branch + ' of ' + project_name
data[:commits].each do |commit|
- check_commit(' ( ' + commit[:url] + ' ): ' + commit[:message], push_msg)
+ push_msg = "#{user} pushed to branch #{branch} of #{project_name} ( #{commit[:url]} ):"
+ check_commit(commit[:message], push_msg)
end
end
def check_commit(message, push_msg)
- task_list = []
- close_list = []
-
- message.split("\n").each do |line|
- # look for a task ID or a full Asana url
- task_list.concat(line.scan(/#(\d+)/))
- task_list.concat(line.scan(/https:\/\/app\.asana\.com\/\d+\/\d+\/(\d+)/))
- # look for a word starting with 'fix' followed by a task ID
- close_list.concat(line.scan(/(fix\w*)\W*#(\d+)/i))
- end
-
- # post commit to every taskid found
- task_list.each do |taskid|
- task = Asana::Task.find(taskid[0])
-
- if task
- task.create_story(text: push_msg + ' ' + message)
- end
- end
-
- # close all tasks that had 'fix(ed/es/ing) #:id' in them
- close_list.each do |taskid|
- task = Asana::Task.find(taskid.last)
-
- if task
- task.modify(completed: true)
+ # matches either:
+ # - #1234
+ # - https://app.asana.com/0/0/1234
+ # optionally preceded with:
+ # - fix/ed/es/ing
+ # - close/s/d
+ # - closing
+ issue_finder = /(fix\w*|clos[ei]\w*+)?\W*(?:https:\/\/app\.asana\.com\/\d+\/\d+\/(\d+)|#(\d+))/i
+
+ message.scan(issue_finder).each do |tuple|
+ # tuple will be
+ # [ 'fix', 'id_from_url', 'id_from_pound' ]
+ taskid = tuple[2] || tuple[1]
+
+ begin
+ task = Asana::Task.find_by_id(client, taskid)
+ task.add_comment(text: "#{push_msg} #{message}")
+
+ if tuple[0]
+ task.update(completed: true)
+ end
+ rescue => e
+ Rails.logger.error(e.message)
+ next
end
end
end
diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb
index fb7e0c0fb0d..29d841faed8 100644
--- a/app/models/project_services/assembla_service.rb
+++ b/app/models/project_services/assembla_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class AssemblaService < Service
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
index aa8746beb80..9e7f642180e 100644
--- a/app/models/project_services/bamboo_service.rb
+++ b/app/models/project_services/bamboo_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class BambooService < CiService
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 199ee3a9d0d..3efbfd2eec3 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
require "addressable/uri"
diff --git a/app/models/project_services/builds_email_service.rb b/app/models/project_services/builds_email_service.rb
index 8247c79fc33..f6313255cbb 100644
--- a/app/models/project_services/builds_email_service.rb
+++ b/app/models/project_services/builds_email_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class BuildsEmailService < Service
@@ -72,12 +73,16 @@ class BuildsEmailService < Service
when 'success'
!notify_only_broken_builds?
when 'failed'
- true
+ !allow_failure?(data)
else
false
end
end
+ def allow_failure?(data)
+ data[:build_allow_failure] == true
+ end
+
def all_recipients(data)
all_recipients = recipients.split(',')
diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb
index e591afdda64..6e8f0842524 100644
--- a/app/models/project_services/campfire_service.rb
+++ b/app/models/project_services/campfire_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class CampfireService < Service
diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb
index 88186113c68..c3f70d1f972 100644
--- a/app/models/project_services/ci_service.rb
+++ b/app/models/project_services/ci_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
# Base class for CI services
diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb
index 7c2027c18e6..88a3e9218cb 100644
--- a/app/models/project_services/custom_issue_tracker_service.rb
+++ b/app/models/project_services/custom_issue_tracker_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class CustomIssueTrackerService < IssueTrackerService
diff --git a/app/models/project_services/drone_ci_service.rb b/app/models/project_services/drone_ci_service.rb
index 08e5ccb3855..b4724bb647e 100644
--- a/app/models/project_services/drone_ci_service.rb
+++ b/app/models/project_services/drone_ci_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class DroneCiService < CiService
diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb
index 8f5d8b086eb..b831577cd97 100644
--- a/app/models/project_services/emails_on_push_service.rb
+++ b/app/models/project_services/emails_on_push_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class EmailsOnPushService < Service
diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb
index 74c57949b4d..b402b68665a 100644
--- a/app/models/project_services/external_wiki_service.rb
+++ b/app/models/project_services/external_wiki_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class ExternalWikiService < Service
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index 15c7c907f7e..8605ce66e48 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
require "flowdock-git-hook"
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index 202fee042e3..61babe9cfe5 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
require "gemnasium/gitlab_service"
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index b64d97ce75d..33f0d7ea01a 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
# TODO(ayufan): The GitLabCiService is deprecated and the type should be removed when the database entries are removed
diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb
index 9558292fea3..7aa04309f54 100644
--- a/app/models/project_services/gitlab_issue_tracker_service.rb
+++ b/app/models/project_services/gitlab_issue_tracker_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class GitlabIssueTrackerService < IssueTrackerService
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 1e1686a11c6..32a81808930 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class HipchatService < Service
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index d24aa317cf3..bd9b580038f 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
require 'uri'
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index 936e574cccd..ed201979d39 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class IssueTrackerService < Service
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index e216f406e1c..f6571fc063e 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class JiraService < IssueTrackerService
@@ -39,15 +40,10 @@ class JiraService < IssueTrackerService
end
def help
- line1 = 'Setting `project_url`, `issues_url` and `new_issue_url` will '\
+ 'Setting `project_url`, `issues_url` and `new_issue_url` will '\
'allow a user to easily navigate to the Jira issue tracker. See the '\
'[integration doc](http://doc.gitlab.com/ce/integration/external-issue-tracker.html) '\
'for details.'
-
- line2 = 'Support for referencing commits and automatic closing of Jira issues directly '\
- 'from GitLab is [available in GitLab EE.](http://doc.gitlab.com/ee/integration/jira.html)'
-
- [line1, line2].join("\n\n")
end
def title
@@ -120,6 +116,7 @@ class JiraService < IssueTrackerService
end
def test_settings
+ return unless api_url.present?
result = JiraService.get(
jira_api_test_url,
headers: {
@@ -217,6 +214,7 @@ class JiraService < IssueTrackerService
end
def send_message(url, message)
+ return unless api_url.present?
result = JiraService.post(
url,
body: message,
@@ -242,6 +240,7 @@ class JiraService < IssueTrackerService
end
def existing_comment?(issue_name, new_comment)
+ return unless api_url.present?
result = JiraService.get(
comment_url(issue_name),
headers: {
diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb
index ade9ee97873..c9a890c7e3f 100644
--- a/app/models/project_services/pivotaltracker_service.rb
+++ b/app/models/project_services/pivotaltracker_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class PivotaltrackerService < Service
diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb
index 53edf522e9a..3d7e8bbee61 100644
--- a/app/models/project_services/pushover_service.rb
+++ b/app/models/project_services/pushover_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class PushoverService < Service
diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb
index dd9ba97ee1f..de974354c77 100644
--- a/app/models/project_services/redmine_service.rb
+++ b/app/models/project_services/redmine_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class RedmineService < IssueTrackerService
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index 375b4534d07..d89cf6d17b2 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class SlackService < Service
diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb
index a63700693d7..b8e9416131a 100644
--- a/app/models/project_services/teamcity_service.rb
+++ b/app/models/project_services/teamcity_service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
class TeamcityService < CiService
diff --git a/app/models/repository.rb b/app/models/repository.rb
index a9bf4eb4033..d9ff71c01ed 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -92,9 +92,12 @@ class Repository
commits
end
- def find_commits_by_message(query)
+ def find_commits_by_message(query, ref = nil, path = nil, limit = 1000, offset = 0)
+ ref ||= root_ref
+
# Limited to 1000 commits for now, could be parameterized?
- args = %W(#{Gitlab.config.git.bin_path} log --pretty=%H --max-count 1000 --grep=#{query})
+ args = %W(#{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset} --max-count #{limit} --grep=#{query})
+ args = args.concat(%W(-- #{path})) if path.present?
git_log_results = Gitlab::Popen.popen(args, path_to_repo).first.lines.map(&:chomp)
commits = git_log_results.map { |c| commit(c) }
@@ -176,17 +179,41 @@ class Repository
cache.fetch(:size) { raw_repository.size }
end
+ def diverging_commit_counts(branch)
+ root_ref_hash = raw_repository.rev_parse_target(root_ref).oid
+ cache.fetch(:"diverging_commit_counts_#{branch.name}") do
+ # Rugged seems to throw a `ReferenceError` when given branch_names rather
+ # than SHA-1 hashes
+ number_commits_behind = commits_between(branch.target, root_ref_hash).size
+ number_commits_ahead = commits_between(root_ref_hash, branch.target).size
+
+ { behind: number_commits_behind, ahead: number_commits_ahead }
+ end
+ end
+
def cache_keys
%i(size branch_names tag_names commit_count
readme version contribution_guide changelog license)
end
+ def branch_cache_keys
+ branches.map do |branch|
+ :"diverging_commit_counts_#{branch.name}"
+ end
+ end
+
def build_cache
cache_keys.each do |key|
unless cache.exist?(key)
send(key)
end
end
+
+ branches.each do |branch|
+ unless cache.exist?(:"diverging_commit_counts_#{branch.name}")
+ send(:diverging_commit_counts, branch)
+ end
+ end
end
def expire_tags_cache
@@ -203,6 +230,14 @@ class Repository
cache_keys.each do |key|
cache.expire(key)
end
+
+ expire_branch_cache
+ end
+
+ def expire_branch_cache
+ branches.each do |branch|
+ cache.expire(:"diverging_commit_counts_#{branch.name}")
+ end
end
def rebuild_cache
@@ -210,6 +245,11 @@ class Repository
cache.expire(key)
send(key)
end
+
+ branches.each do |branch|
+ cache.expire(:"diverging_commit_counts_#{branch.name}")
+ diverging_commit_counts(branch)
+ end
end
def lookup_cache
@@ -644,6 +684,11 @@ class Repository
end
end
+ def ls_files(ref)
+ actual_ref = ref || root_ref
+ raw_repository.ls_files(actual_ref)
+ end
+
private
def cache
diff --git a/app/models/service.rb b/app/models/service.rb
index d3bf7f0ebd1..24f4bf7646e 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
# To add new service you should build a class inherited from Service
diff --git a/app/models/tree.rb b/app/models/tree.rb
index 93b3246a668..e0e04d8859f 100644
--- a/app/models/tree.rb
+++ b/app/models/tree.rb
@@ -17,18 +17,16 @@ class Tree
def readme
return @readme if defined?(@readme)
- available_readmes = blobs.select(&:readme?)
+ # Take the first previewable readme, or return nil if none is available or
+ # we can't preview any of them
+ readme_tree = blobs.find do |blob|
+ blob.readme? && (previewable?(blob.name) || plain?(blob.name))
+ end
- if available_readmes.count == 0
+ if readme_tree.nil?
return @readme = nil
end
- # Take the first previewable readme, or the first available readme, if we
- # can't preview any of them
- readme_tree = available_readmes.find do |readme|
- previewable?(readme.name)
- end || available_readmes.first
-
readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name)
git_repo = repository.raw_repository
diff --git a/app/models/user.rb b/app/models/user.rb
index df87f3b79bd..46b36c605b0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -2,62 +2,63 @@
#
# Table name: users
#
-# id :integer not null, primary key
-# email :string(255) default(""), not null
-# encrypted_password :string(255) default(""), not null
-# reset_password_token :string(255)
-# reset_password_sent_at :datetime
-# remember_created_at :datetime
-# sign_in_count :integer default(0)
-# current_sign_in_at :datetime
-# last_sign_in_at :datetime
-# current_sign_in_ip :string(255)
-# last_sign_in_ip :string(255)
-# created_at :datetime
-# updated_at :datetime
-# name :string(255)
-# admin :boolean default(FALSE), not null
-# projects_limit :integer default(10)
-# skype :string(255) default(""), not null
-# linkedin :string(255) default(""), not null
-# twitter :string(255) default(""), not null
-# authentication_token :string(255)
-# theme_id :integer default(1), not null
-# bio :string(255)
-# failed_attempts :integer default(0)
-# locked_at :datetime
-# unlock_token :string(255)
-# username :string(255)
-# can_create_group :boolean default(TRUE), not null
-# can_create_team :boolean default(TRUE), not null
-# state :string(255)
-# color_scheme_id :integer default(1), not null
-# notification_level :integer default(1), not null
-# password_expires_at :datetime
-# created_by_id :integer
-# last_credential_check_at :datetime
-# avatar :string(255)
-# confirmation_token :string(255)
-# confirmed_at :datetime
-# confirmation_sent_at :datetime
-# unconfirmed_email :string(255)
-# hide_no_ssh_key :boolean default(FALSE)
-# website_url :string(255) default(""), not null
-# notification_email :string(255)
-# hide_no_password :boolean default(FALSE)
-# password_automatically_set :boolean default(FALSE)
-# location :string(255)
-# encrypted_otp_secret :string(255)
-# encrypted_otp_secret_iv :string(255)
-# encrypted_otp_secret_salt :string(255)
-# otp_required_for_login :boolean default(FALSE), not null
-# otp_backup_codes :text
-# public_email :string(255) default(""), not null
-# dashboard :integer default(0)
-# project_view :integer default(0)
-# consumed_timestep :integer
-# layout :integer default(0)
-# hide_project_limit :boolean default(FALSE)
+# id :integer not null, primary key
+# email :string(255) default(""), not null
+# encrypted_password :string(255) default(""), not null
+# reset_password_token :string(255)
+# reset_password_sent_at :datetime
+# remember_created_at :datetime
+# sign_in_count :integer default(0)
+# current_sign_in_at :datetime
+# last_sign_in_at :datetime
+# current_sign_in_ip :string(255)
+# last_sign_in_ip :string(255)
+# created_at :datetime
+# updated_at :datetime
+# name :string(255)
+# admin :boolean default(FALSE), not null
+# projects_limit :integer default(10)
+# skype :string(255) default(""), not null
+# linkedin :string(255) default(""), not null
+# twitter :string(255) default(""), not null
+# authentication_token :string(255)
+# theme_id :integer default(1), not null
+# bio :string(255)
+# failed_attempts :integer default(0)
+# locked_at :datetime
+# username :string(255)
+# can_create_group :boolean default(TRUE), not null
+# can_create_team :boolean default(TRUE), not null
+# state :string(255)
+# color_scheme_id :integer default(1), not null
+# notification_level :integer default(1), not null
+# password_expires_at :datetime
+# created_by_id :integer
+# last_credential_check_at :datetime
+# avatar :string(255)
+# confirmation_token :string(255)
+# confirmed_at :datetime
+# confirmation_sent_at :datetime
+# unconfirmed_email :string(255)
+# hide_no_ssh_key :boolean default(FALSE)
+# website_url :string(255) default(""), not null
+# notification_email :string(255)
+# hide_no_password :boolean default(FALSE)
+# password_automatically_set :boolean default(FALSE)
+# location :string(255)
+# encrypted_otp_secret :string(255)
+# encrypted_otp_secret_iv :string(255)
+# encrypted_otp_secret_salt :string(255)
+# otp_required_for_login :boolean default(FALSE), not null
+# otp_backup_codes :text
+# public_email :string(255) default(""), not null
+# dashboard :integer default(0)
+# project_view :integer default(0)
+# consumed_timestep :integer
+# layout :integer default(0)
+# hide_project_limit :boolean default(FALSE)
+# unlock_token :string
+# otp_grace_period_started_at :datetime
#
require 'carrierwave/orm/activerecord'
@@ -352,10 +353,13 @@ class User < ActiveRecord::Base
end
def namespace_uniq
+ # Return early if username already failed the first uniqueness validation
+ return if self.errors[:username].include?('has already been taken')
+
namespace_name = self.username
existing_namespace = Namespace.by_path(namespace_name)
if existing_namespace && existing_namespace != self.namespace
- self.errors.add :username, "already exists"
+ self.errors.add(:username, 'has already been taken')
end
end
diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb
index f139872c728..c0e08a151f2 100644
--- a/app/services/create_branch_service.rb
+++ b/app/services/create_branch_service.rb
@@ -31,7 +31,6 @@ class CreateBranchService < BaseService
if new_branch
push_data = build_push_data(project, current_user, new_branch)
- EventCreateService.new.push(project, current_user, push_data)
project.execute_hooks(push_data.dup, :push_hooks)
project.execute_services(push_data.dup, :push_hooks)
diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb
index 22bf9dd935e..004b3ce7286 100644
--- a/app/services/delete_branch_service.rb
+++ b/app/services/delete_branch_service.rb
@@ -27,7 +27,6 @@ class DeleteBranchService < BaseService
if repository.rm_branch(current_user, branch_name)
push_data = build_push_data(branch)
- EventCreateService.new.push(project, current_user, push_data)
project.execute_hooks(push_data.dup, :push_hooks)
project.execute_services(push_data.dup, :push_hooks)
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index bdf7b3ad2bb..e4edc55bf69 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -413,6 +413,7 @@ class NotificationService
recipients = reject_unsubscribed_users(recipients, target)
recipients.delete(current_user)
+ recipients = recipients.uniq
recipients
end
diff --git a/app/services/projects/download_service.rb b/app/services/projects/download_service.rb
index 99f22293d0d..6386f57fb0d 100644
--- a/app/services/projects/download_service.rb
+++ b/app/services/projects/download_service.rb
@@ -16,13 +16,7 @@ module Projects
uploader.download!(@url)
uploader.store!
- filename = uploader.image? ? uploader.file.basename : uploader.file.filename
-
- {
- 'alt' => filename,
- 'url' => uploader.secure_url,
- 'is_image' => uploader.image?
- }
+ uploader.to_h
end
private
diff --git a/app/services/projects/housekeeping_service.rb b/app/services/projects/housekeeping_service.rb
new file mode 100644
index 00000000000..0db85ac2142
--- /dev/null
+++ b/app/services/projects/housekeeping_service.rb
@@ -0,0 +1,20 @@
+# Projects::HousekeepingService class
+#
+# Used for git housekeeping
+#
+# Ex.
+# Projects::HousekeepingService.new(project).execute
+#
+module Projects
+ class HousekeepingService < BaseService
+ include Gitlab::ShellAdapter
+
+ def initialize(project)
+ @project = project
+ end
+
+ def execute
+ GitlabShellWorker.perform_async(:gc, @project.path_with_namespace)
+ end
+ end
+end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 64ea6dd42eb..2e734654466 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -55,6 +55,9 @@ module Projects
# Move uploads
Gitlab::UploadsTransfer.new.move_project(project.path, old_namespace.path, new_namespace.path)
+ project.old_path_with_namespace = old_path
+
+ SystemHooksService.new.execute_hooks_for(project, :transfer)
true
end
end
diff --git a/app/services/projects/upload_service.rb b/app/services/projects/upload_service.rb
index 279550d6f4a..012e82a7704 100644
--- a/app/services/projects/upload_service.rb
+++ b/app/services/projects/upload_service.rb
@@ -10,13 +10,7 @@ module Projects
uploader = FileUploader.new(@project)
uploader.store!(@file)
- filename = uploader.image? ? uploader.file.basename : uploader.file.filename
-
- {
- alt: filename,
- url: uploader.secure_url,
- is_image: uploader.image?
- }
+ uploader.to_h
end
private
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index 8b5143e1eb7..6dc854ec33d 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -18,7 +18,8 @@ class SystemHooksService
def build_event_data(model, event)
data = {
event_name: build_event_name(model, event),
- created_at: model.created_at.xmlschema
+ created_at: model.created_at.xmlschema,
+ updated_at: model.updated_at.xmlschema
}
case model
@@ -34,6 +35,14 @@ class SystemHooksService
end
when Project
data.merge!(project_data(model))
+
+ if event == :rename || event == :transfer
+ data.merge!({
+ old_path_with_namespace: model.old_path_with_namespace
+ })
+ end
+
+ data
when User
data.merge!({
name: model.name,
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 98a71cbf1ad..1083bcec054 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -41,7 +41,7 @@ class SystemNoteService
#
# Returns the created Note object
def self.change_assignee(noteable, project, author, assignee)
- body = assignee.nil? ? 'Assignee removed' : "Reassigned to @#{assignee.username}"
+ body = assignee.nil? ? 'Assignee removed' : "Reassigned to #{assignee.to_reference}"
create_note(noteable: noteable, project: project, author: author, note: body)
end
@@ -66,7 +66,7 @@ class SystemNoteService
def self.change_label(noteable, project, author, added_labels, removed_labels)
labels_count = added_labels.count + removed_labels.count
- references = ->(label) { "~#{label.id}" }
+ references = ->(label) { label.to_reference(:id) }
added_labels = added_labels.map(&references).join(' ')
removed_labels = removed_labels.map(&references).join(' ')
@@ -103,7 +103,7 @@ class SystemNoteService
# Returns the created Note object
def self.change_milestone(noteable, project, author, milestone)
body = 'Milestone '
- body += milestone.nil? ? 'removed' : "changed to #{milestone.title}"
+ body += milestone.nil? ? 'removed' : "changed to #{milestone.to_reference(project)}"
create_note(noteable: noteable, project: project, author: author, note: body)
end
diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb
index ac920119a85..86d24469e05 100644
--- a/app/uploaders/file_uploader.rb
+++ b/app/uploaders/file_uploader.rb
@@ -30,4 +30,19 @@ class FileUploader < CarrierWave::Uploader::Base
def secure_url
File.join("/uploads", @secret, file.filename)
end
+
+ def to_h
+ filename = image? ? self.file.basename : self.file.filename
+ escaped_filename = filename.gsub("]", "\\]")
+
+ markdown = "[#{escaped_filename}](#{self.secure_url})"
+ markdown.prepend("!") if image?
+
+ {
+ alt: filename,
+ url: self.secure_url,
+ is_image: image?,
+ markdown: markdown
+ }
+ end
end
diff --git a/app/validators/namespace_validator.rb b/app/validators/namespace_validator.rb
index 10e35ce665a..7a35958cc5f 100644
--- a/app/validators/namespace_validator.rb
+++ b/app/validators/namespace_validator.rb
@@ -17,6 +17,7 @@ class NamespaceValidator < ActiveModel::EachValidator
hooks
issues
merge_requests
+ new
notes
profile
projects
diff --git a/app/views/abuse_report_mailer/notify.html.haml b/app/views/abuse_report_mailer/notify.html.haml
index 619533e09a7..2741eb44357 100644
--- a/app/views/abuse_report_mailer/notify.html.haml
+++ b/app/views/abuse_report_mailer/notify.html.haml
@@ -8,4 +8,4 @@
= @abuse_report.message
%p
- = link_to "View details", abuse_reports_url
+ = link_to "View details", admin_abuse_reports_url
diff --git a/app/views/abuse_reports/new.html.haml b/app/views/abuse_reports/new.html.haml
index cffd7684008..3e5cdd2ce4a 100644
--- a/app/views/abuse_reports/new.html.haml
+++ b/app/views/abuse_reports/new.html.haml
@@ -2,7 +2,7 @@
%h3.page-title Report abuse
%p Please use this form to report users who create spam issues, comments or behave inappropriately.
%hr
-= form_for @abuse_report, html: { class: 'form-horizontal'} do |f|
+= form_for @abuse_report, html: { class: 'form-horizontal js-requires-input'} do |f|
= f.hidden_field :user_id
- if @abuse_report.errors.any?
.alert.alert-danger
@@ -16,7 +16,7 @@
.form-group
= f.label :message, class: 'control-label'
.col-sm-10
- = f.text_area :message, class: "form-control", rows: 2, required: true
+ = f.text_area :message, class: "form-control js-quick-submit", rows: 2, required: true
.help-block
Explain the problem with this user. If appropriate, provide a link to the relevant issue or comment.
diff --git a/app/views/admin/abuse_reports/_abuse_report.html.haml b/app/views/admin/abuse_reports/_abuse_report.html.haml
index d3afc658cd6..2ab01704b77 100644
--- a/app/views/admin/abuse_reports/_abuse_report.html.haml
+++ b/app/views/admin/abuse_reports/_abuse_report.html.haml
@@ -2,25 +2,30 @@
- user = abuse_report.user
%tr
%td
- - if reporter
- = link_to reporter.name, reporter
+ - if user
+ = link_to user.name, [:admin, user]
+ .light.small
+ Joined #{time_ago_with_tooltip(user.created_at)}
- else
(removed)
%td
- = abuse_report.created_at.to_s(:short)
- %td
- = abuse_report.message
- %td
- - if user
- = link_to user.name, user
+ - if reporter
+ = link_to reporter.name, [:admin, reporter]
- else
(removed)
+ .light.small
+ = time_ago_with_tooltip(abuse_report.created_at)
+ %td
+ = markdown(abuse_report.message.squish!, pipeline: :single_line)
%td
- if user
= link_to 'Remove user & report', admin_abuse_report_path(abuse_report, remove_user: true),
data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, remote: true, method: :delete, class: "btn btn-xs btn-remove js-remove-tr"
%td
- - if user
+ - if user && !user.blocked?
= link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs"
+ - else
+ .btn.btn-xs.disabled
+ Already Blocked
= link_to 'Remove report', [:admin, abuse_report], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr"
diff --git a/app/views/admin/abuse_reports/index.html.haml b/app/views/admin/abuse_reports/index.html.haml
index 40a5fe4628b..bc4a9cedb2c 100644
--- a/app/views/admin/abuse_reports/index.html.haml
+++ b/app/views/admin/abuse_reports/index.html.haml
@@ -6,10 +6,9 @@
%table.table
%thead
%tr
+ %th User
%th Reported by
- %th Reported at
%th Message
- %th User
%th Primary action
%th
= render @abuse_reports
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 214e0209bb7..81337432ab7 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -149,7 +149,7 @@
.checkbox
= f.label :shared_runners_enabled do
= f.check_box :shared_runners_enabled
- Enable shared runners for a new projects
+ Enable shared runners for new projects
.form-group
= f.label :max_artifacts_size, 'Maximum artifacts size (MB)', class: 'control-label col-sm-2'
@@ -180,14 +180,6 @@
sending messages to this port, without it metrics data will not be
saved.
.form-group
- = f.label :metrics_username, 'InfluxDB username', class: 'control-label col-sm-2'
- .col-sm-10
- = f.text_field :metrics_username, class: 'form-control'
- .form-group
- = f.label :metrics_password, 'InfluxDB password', class: 'control-label col-sm-2'
- .col-sm-10
- = f.text_field :metrics_password, class: 'form-control'
- .form-group
= f.label :metrics_pool_size, 'Connection pool size', class: 'control-label col-sm-2'
.col-sm-10
= f.number_field :metrics_pool_size, class: 'form-control'
diff --git a/app/views/admin/builds/index.html.haml b/app/views/admin/builds/index.html.haml
index 55da06a7fe9..ddd4e1481eb 100644
--- a/app/views/admin/builds/index.html.haml
+++ b/app/views/admin/builds/index.html.haml
@@ -7,18 +7,18 @@
%ul.center-top-menu
%li{class: ('active' if @scope.nil?)}
= link_to admin_builds_path do
+ All
+ %span.badge.js-totalbuilds-count= @all_builds.count(:id)
+
+ %li{class: ('active' if @scope == 'running')}
+ = link_to admin_builds_path(scope: :running) do
Running
- %span.badge.js-running-count= @all_builds.running_or_pending.count(:id)
+ %span.badge.js-running-count= number_with_delimiter(@all_builds.running_or_pending.count(:id))
%li{class: ('active' if @scope == 'finished')}
= link_to admin_builds_path(scope: :finished) do
Finished
- %span.badge.js-running-count= @all_builds.finished.count(:id)
-
- %li{class: ('active' if @scope == 'all')}
- = link_to admin_builds_path(scope: :all) do
- All
- %span.badge.js-totalbuilds-count= @all_builds.count(:id)
+ %span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id))
.gray-content-block
#{(@scope || 'running').capitalize} builds
diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml
index 531247e9148..cc389c3ae08 100644
--- a/app/views/admin/dashboard/index.html.haml
+++ b/app/views/admin/dashboard/index.html.haml
@@ -6,35 +6,35 @@
%p
Forks
%span.light.pull-right
- = ForkedProjectLink.count
+ = number_with_delimiter(ForkedProjectLink.count)
%p
Issues
%span.light.pull-right
- = Issue.count
+ = number_with_delimiter(Issue.count)
%p
Merge Requests
%span.light.pull-right
- = MergeRequest.count
+ = number_with_delimiter(MergeRequest.count)
%p
Notes
%span.light.pull-right
- = Note.count
+ = number_with_delimiter(Note.count)
%p
Snippets
%span.light.pull-right
- = Snippet.count
+ = number_with_delimiter(Snippet.count)
%p
SSH Keys
%span.light.pull-right
- = Key.count
+ = number_with_delimiter(Key.count)
%p
Milestones
%span.light.pull-right
- = Milestone.count
+ = number_with_delimiter(Milestone.count)
%p
Active Users
%span.light.pull-right
- = User.active.count
+ = number_with_delimiter(User.active.count)
.col-md-4
%h4
Features
@@ -99,7 +99,7 @@
%h4 Projects
.data
= link_to admin_namespaces_projects_path do
- %h1= Project.count
+ %h1= number_with_delimiter(Project.count)
%hr
= link_to('New Project', new_project_path, class: "btn btn-new")
.col-sm-4
@@ -107,7 +107,7 @@
%h4 Users
.data
= link_to admin_users_path do
- %h1= User.count
+ %h1= number_with_delimiter(User.count)
%hr
= link_to 'New User', new_admin_user_path, class: "btn btn-new"
.col-sm-4
@@ -115,7 +115,7 @@
%h4 Groups
.data
= link_to admin_groups_path do
- %h1= Group.count
+ %h1= number_with_delimiter(Group.count)
%hr
= link_to 'New Group', new_admin_group_path, class: "btn btn-new"
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 5ce7cdf2f8d..3940210e19b 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -1,6 +1,6 @@
- page_title "Groups"
%h3.page-title
- Groups (#{@groups.total_count})
+ Groups (#{number_with_delimiter(@groups.total_count)})
= link_to 'New Group', new_admin_group_path, class: "btn btn-new pull-right"
%p.light
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index 296497a4cd4..f7fd156b84a 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -30,7 +30,7 @@
%li
%span.light Created on:
%strong
- = @group.created_at.stamp("March 1, 1999")
+ = @group.created_at.to_s(:medium)
.panel.panel-default
.panel-heading
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 5260eadf95b..0c986af4a95 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -38,7 +38,7 @@
%li
%span.light Created on:
%strong
- = @project.created_at.stamp("March 1, 1999")
+ = @project.created_at.to_s(:medium)
%li
%span.light http:
diff --git a/app/views/admin/users/_profile.html.haml b/app/views/admin/users/_profile.html.haml
index 7d11edc79e2..6bc217f84cc 100644
--- a/app/views/admin/users/_profile.html.haml
+++ b/app/views/admin/users/_profile.html.haml
@@ -4,7 +4,7 @@
%ul.well-list
%li
%span.light Member since
- %strong= user.created_at.stamp("Aug 21, 2011")
+ %strong= user.created_at.to_s(:medium)
- unless user.public_email.blank?
%li
%span.light E-mail:
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index bc08458312c..a92c9c152b9 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -8,27 +8,27 @@
%li{class: "#{'active' unless params[:filter]}"}
= link_to admin_users_path do
Active
- %small.pull-right= User.active.count
+ %small.pull-right= number_with_delimiter(User.active.count)
%li{class: "#{'active' if params[:filter] == "admins"}"}
= link_to admin_users_path(filter: "admins") do
Admins
- %small.pull-right= User.admins.count
+ %small.pull-right= number_with_delimiter(User.admins.count)
%li.filter-two-factor-enabled{class: "#{'active' if params[:filter] == 'two_factor_enabled'}"}
= link_to admin_users_path(filter: 'two_factor_enabled') do
2FA Enabled
- %small.pull-right= User.with_two_factor.count
+ %small.pull-right= number_with_delimiter(User.with_two_factor.count)
%li.filter-two-factor-disabled{class: "#{'active' if params[:filter] == 'two_factor_disabled'}"}
= link_to admin_users_path(filter: 'two_factor_disabled') do
2FA Disabled
- %small.pull-right= User.without_two_factor.count
+ %small.pull-right= number_with_delimiter(User.without_two_factor.count)
%li{class: "#{'active' if params[:filter] == "blocked"}"}
= link_to admin_users_path(filter: "blocked") do
Blocked
- %small.pull-right= User.blocked.count
+ %small.pull-right= number_with_delimiter(User.blocked.count)
%li{class: "#{'active' if params[:filter] == "wop"}"}
= link_to admin_users_path(filter: "wop") do
Without projects
- %small.pull-right= User.without_projects.count
+ %small.pull-right= number_with_delimiter(User.without_projects.count)
%hr
= form_tag admin_users_path, method: :get, class: 'form-inline' do
.form-group
@@ -42,7 +42,7 @@
%section.col-md-9
.panel.panel-default
.panel-heading
- Users (#{@users.total_count})
+ Users (#{number_with_delimiter(@users.total_count)})
.panel-head-actions
.dropdown.inline
%a.dropdown-toggle.btn.btn-sm{href: '#', "data-toggle" => "dropdown"}
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 0848504b7a6..2c2450d4117 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -58,12 +58,12 @@
%li
%span.light Member since:
%strong
- = @user.created_at.stamp("Nov 12, 2031")
+ = @user.created_at.to_s(:medium)
- if @user.confirmed_at
%li
%span.light Confirmed at:
%strong
- = @user.confirmed_at.stamp("Nov 12, 2031")
+ = @user.confirmed_at.to_s(:medium)
- else
%li
%span.light Confirmed:
@@ -74,7 +74,7 @@
%span.light Current sign-in at:
%strong
- if @user.current_sign_in_at
- = @user.current_sign_in_at.stamp("Nov 12, 2031")
+ = @user.current_sign_in_at.to_s(:medium)
- else
never
@@ -82,7 +82,7 @@
%span.light Last sign-in at:
%strong
- if @user.last_sign_in_at
- = @user.last_sign_in_at.stamp("Nov 12, 2031")
+ = @user.last_sign_in_at.to_s(:medium)
- else
never
diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder
index 07bda1c77f8..0d7b1b30dc3 100644
--- a/app/views/dashboard/issues.atom.builder
+++ b/app/views/dashboard/issues.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: issues_dashboard_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html"
xml.id issues_dashboard_url
- xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
+ xml.updated @issues.first.created_at.xmlschema if @issues.any?
@issues.each do |issue|
issue_to_atom(xml, issue)
diff --git a/app/views/dashboard/projects/index.atom.builder b/app/views/dashboard/projects/index.atom.builder
index c8c219f4cca..2e2712c5146 100644
--- a/app/views/dashboard/projects/index.atom.builder
+++ b/app/views/dashboard/projects/index.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: dashboard_projects_url(format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: dashboard_projects_url, rel: "alternate", type: "text/html"
xml.id dashboard_projects_url
- xml.updated @events.latest_update_time.strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
+ xml.updated @events.latest_update_time.xmlschema if @events.any?
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 9aacc79d686..46432a92348 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -3,7 +3,7 @@
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
- = cache [event, "v2.1"] do
+ = cache [event, current_application_settings, "v2.1"] do
= image_tag avatar_icon(event.author_email, 46), class: "avatar s46", alt:''
- if event.created_project?
= render "events/event/created_project", event: event
diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml
index ffc37ad6178..abea86b026a 100644
--- a/app/views/events/_event_last_push.html.haml
+++ b/app/views/events/_event_last_push.html.haml
@@ -1,5 +1,5 @@
- if show_last_push_widget?(event)
- .gray-content-block.clear-block
+ .gray-content-block.clear-block.last-push-widget
.event-last-push
.event-last-push-text
%span You pushed to
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 1dea77c2e96..7e3e2e28bc9 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -24,15 +24,6 @@
%hr
= link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar"
- .form-group
- %hr
- = f.label :public, class: 'control-label' do
- Public
- .col-sm-10
- .checkbox
- = f.check_box :public
- %span.descr Make this group public (even if there are no public projects inside this group)
-
.form-actions
= f.submit 'Save group', class: "btn btn-save"
diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder
index 66fe7e25871..486d1d8587a 100644
--- a/app/views/groups/issues.atom.builder
+++ b/app/views/groups/issues.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: issues_dashboard_url(format: :atom, private_token: @user.private_token), rel: "self", type: "application/atom+xml"
xml.link href: issues_dashboard_url, rel: "alternate", type: "text/html"
xml.id issues_dashboard_url
- xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
+ xml.updated @issues.first.created_at.xmlschema if @issues.any?
@issues.each do |issue|
issue_to_atom(xml, issue)
diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder
index 7ea574434c3..5cc0f5e1d2e 100644
--- a/app/views/groups/show.atom.builder
+++ b/app/views/groups/show.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: group_url(@group, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: group_url(@group), rel: "alternate", type: "text/html"
xml.id group_url(@group)
- xml.updated @events.latest_update_time.strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
+ xml.updated @events.latest_update_time.xmlschema if @events.any?
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index c2c7c581b3e..48a544fc834 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -6,6 +6,12 @@
= auto_discovery_link_tag(:atom, group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} activity")
.cover-block
+ .cover-controls
+ - if @group && can?(current_user, :admin_group, @group)
+ = link_to icon('pencil'), edit_group_path(@group), class: 'btn'
+ - if current_user
+ = link_to icon('rss'), group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn'
+
.avatar-holder
= link_to group_icon(@group), target: '_blank' do
= image_tag group_icon(@group), class: "avatar group-avatar s90"
@@ -34,9 +40,6 @@
.gray-content-block.activity-filter-block
- if current_user
= render "events/event_last_push", event: @last_push
- .pull-right
- = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'btn rss-btn' do
- %i.fa.fa-rss
= render 'shared/event_filter'
@@ -47,5 +50,5 @@
= render "projects", projects: @projects
- else
- %p
- This group does not have public projects
+ %p.center-top-menu.no-top
+ No projects to show
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index e8e331dd109..9ee6f07b26b 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -40,6 +40,32 @@
%td.shortcut
.key enter
%td Open Selection
+ %tr
+ %td.shortcut
+ .key t
+ %td Go to finding file
+ %tbody
+ %tr
+ %th
+ %th Finding Project File
+ %tr
+ %td.shortcut
+ .key
+ %i.fa.fa-arrow-up
+ %td Move selection up
+ %tr
+ %td.shortcut
+ .key
+ %i.fa.fa-arrow-down
+ %td Move selection down
+ %tr
+ %td.shortcut
+ .key enter
+ %td Open Selection
+ %tr
+ %td.shortcut
+ .key esc
+ %td Go back
.col-lg-4
%table.shortcut-mappings
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index 2e0bd2007a3..38ca4f91c4d 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -1,13 +1,13 @@
+- page_description brand_title unless page_description
+
+- site_name = "GitLab"
%head{prefix: "og: http://ogp.me/ns#"}
%meta{charset: "utf-8"}
%meta{'http-equiv' => 'X-UA-Compatible', content: 'IE=edge'}
- %meta{name: 'referrer', content: 'origin-when-cross-origin'}
-
- %meta{name: "description", content: page_description}
-# Open Graph - http://ogp.me/
%meta{property: 'og:type', content: "object"}
- %meta{property: 'og:site_name', content: "GitLab"}
+ %meta{property: 'og:site_name', content: site_name}
%meta{property: 'og:title', content: page_title}
%meta{property: 'og:description', content: page_description}
%meta{property: 'og:image', content: page_image}
@@ -20,8 +20,8 @@
%meta{property: 'twitter:image', content: page_image}
= page_card_meta_tags
- - page_title "GitLab"
- %title= page_title
+ %title= page_title(site_name)
+ %meta{name: "description", content: page_description}
= favicon_link_tag 'favicon.ico'
@@ -34,6 +34,8 @@
= include_gon
+ - unless browser.safari?
+ %meta{name: 'referrer', content: 'origin-when-cross-origin'}
%meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'}
%meta{name: 'theme-color', content: '#474D57'}
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index 31888c5580e..2e483b7148d 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -1,5 +1,6 @@
-- page_title @group.name
-- header_title group_title(@group) unless header_title
-- sidebar "group" unless sidebar
+- page_title @group.name
+- page_description @group.description unless page_description
+- header_title group_title(@group) unless header_title
+- sidebar "group" unless sidebar
= render template: "layouts/application"
diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml
index c60ac5eefac..cffdb52cc23 100644
--- a/app/views/layouts/nav/_admin.html.haml
+++ b/app/views/layouts/nav/_admin.html.haml
@@ -29,13 +29,13 @@
= icon('cog fw')
%span
Runners
- %span.count= Ci::Runner.count(:all)
+ %span.count= number_with_delimiter(Ci::Runner.count(:all))
= nav_link path: 'builds#index' do
= link_to admin_builds_path do
= icon('link fw')
%span
Builds
- %span.count= Ci::Build.count(:all)
+ %span.count= number_with_delimiter(Ci::Build.count(:all))
= nav_link(controller: :logs) do
= link_to admin_logs_path, title: 'Logs' do
= icon('file-text fw')
@@ -80,7 +80,7 @@
= icon('exclamation-circle fw')
%span
Abuse Reports
- %span.count= AbuseReport.count(:all)
+ %span.count= number_with_delimiter(AbuseReport.count(:all))
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings' do
diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml
index da698831300..106abd24a56 100644
--- a/app/views/layouts/nav/_dashboard.html.haml
+++ b/app/views/layouts/nav/_dashboard.html.haml
@@ -24,13 +24,13 @@
= icon('exclamation-circle fw')
%span
Issues
- %span.count= current_user.assigned_issues.opened.count
+ %span.count= number_with_delimiter(current_user.assigned_issues.opened.count)
= nav_link(path: 'dashboard#merge_requests') do
= link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do
= icon('tasks fw')
%span
Merge Requests
- %span.count= current_user.assigned_merge_requests.opened.count
+ %span.count= number_with_delimiter(current_user.assigned_merge_requests.opened.count)
= nav_link(controller: :snippets) do
= link_to dashboard_snippets_path, title: 'Snippets' do
= icon('clipboard fw')
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 68da8d5de2a..e5e2a59eaed 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -25,14 +25,14 @@
%span
Issues
- if current_user
- %span.count= Issue.opened.of_group(@group).count
+ %span.count= number_with_delimiter(Issue.opened.of_group(@group).count)
= nav_link(path: 'groups#merge_requests') do
= link_to merge_requests_group_path(@group), title: 'Merge Requests' do
= icon('tasks fw')
%span
Merge Requests
- if current_user
- %span.count= MergeRequest.opened.of_group(@group).count
+ %span.count= number_with_delimiter(MergeRequest.opened.of_group(@group).count)
= nav_link(controller: [:group_members]) do
= link_to group_group_members_path(@group), title: 'Members' do
= icon('users fw')
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 64b30783c05..f3ded04419b 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -27,7 +27,7 @@
= icon('envelope-o fw')
%span
Emails
- %span.count= current_user.emails.count + 1
+ %span.count= number_with_delimiter(current_user.emails.count + 1)
- unless current_user.ldap_user?
= nav_link(controller: :passwords) do
= link_to edit_profile_password_path, title: 'Password' do
@@ -45,7 +45,7 @@
= icon('key fw')
%span
SSH Keys
- %span.count= current_user.keys.count
+ %span.count= number_with_delimiter(current_user.keys.count)
= nav_link(controller: :preferences) do
= link_to profile_preferences_path, title: 'Preferences' do
-# TODO (rspeicher): Better icon?
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index c0d62028639..270ccfd387f 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -25,7 +25,7 @@
%span
Activity
- if project_nav_tab? :files
- = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
+ = nav_link(controller: %w(tree blob blame edit_tree new_tree find_file)) do
= link_to project_files_path(@project), title: 'Files', class: 'shortcuts-tree' do
= icon('files-o fw')
%span
@@ -44,7 +44,7 @@
= icon('cubes fw')
%span
Builds
- %span.count.builds_counter= @project.builds.running_or_pending.count(:all)
+ %span.count.builds_counter= number_with_delimiter(@project.builds.running_or_pending.count(:all))
- if project_nav_tab? :graphs
= nav_link(controller: %w(graphs)) do
@@ -67,7 +67,7 @@
%span
Issues
- if @project.default_issues_tracker?
- %span.count.issue_counter= @project.issues.opened.count
+ %span.count.issue_counter= number_with_delimiter(@project.issues.opened.count)
- if project_nav_tab? :merge_requests
= nav_link(controller: :merge_requests) do
@@ -75,7 +75,7 @@
= icon('tasks fw')
%span
Merge Requests
- %span.count.merge_counter= @project.merge_requests.opened.count
+ %span.count.merge_counter= number_with_delimiter(@project.merge_requests.opened.count)
- if project_nav_tab? :settings
= nav_link(controller: [:project_members, :teams]) do
@@ -117,4 +117,3 @@
%li.hidden
= link_to namespace_project_network_path(@project.namespace, @project, current_ref), title: 'Network', class: 'shortcuts-network' do
Network
-
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index abf73bcc709..ab527e8e438 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -1,6 +1,7 @@
-- page_title @project.name_with_namespace
-- header_title project_title(@project) unless header_title
-- sidebar "project" unless sidebar
+- page_title @project.name_with_namespace
+- page_description @project.description unless page_description
+- header_title project_title(@project) unless header_title
+- sidebar "project" unless sidebar
- content_for :scripts_body_top do
- project = @target_project || @project
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index 4361f67a74d..3dd2595f1ad 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -17,7 +17,7 @@
%strong #{link_to(commit.short_id, namespace_project_commit_url(@message.project_namespace, @message.project, commit))}
%div
%span by #{commit.author_name}
- %i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
+ %i at #{commit.committed_date.to_s(:iso8601)}
%pre.commit-message
= commit.safe_message
diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml
index aa0e263b6df..53869e36b28 100644
--- a/app/views/notify/repository_push_email.text.haml
+++ b/app/views/notify/repository_push_email.text.haml
@@ -8,7 +8,7 @@
\
= @message.reverse_compare? ? "Deleted commits:" : "Commits:"
- @message.commits.each do |commit|
- #{commit.short_id} by #{commit.author_name} at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")}
+ #{commit.short_id} by #{commit.author_name} at #{commit.committed_date.to_s(:iso8601)}
#{commit.safe_message}
\- - - - -
\
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
index 0ca8bd95157..3bd1f1af162 100644
--- a/app/views/profiles/keys/_key_details.html.haml
+++ b/app/views/profiles/keys/_key_details.html.haml
@@ -10,7 +10,7 @@
%strong= @key.title
%li
%span.light Created on:
- %strong= @key.created_at.stamp("Aug 21, 2011")
+ %strong= @key.created_at.to_s(:medium)
.col-md-8
%p
diff --git a/app/views/projects/_find_file_link.html.haml b/app/views/projects/_find_file_link.html.haml
new file mode 100644
index 00000000000..08e2fc48be7
--- /dev/null
+++ b/app/views/projects/_find_file_link.html.haml
@@ -0,0 +1,3 @@
+= link_to namespace_project_find_file_path(@project.namespace, @project, @ref), class: 'btn btn-grouped shortcuts-find-file', rel: 'nofollow' do
+ = icon('search')
+ %span Find File
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index e92115b9b98..0f61e623396 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -18,13 +18,26 @@
= visibility_level_label(@project.visibility_level)
.cover-controls
- - if can?(current_user, :admin_project, @project)
- = link_to edit_project_path(@project), class: 'btn btn-gray' do
- = icon('pencil')
- if current_user
- &nbsp;
= link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), class: 'btn btn-gray' do
= icon('rss')
+ - access = user_max_access_in_project(current_user.id, @project)
+ - can_edit = can?(current_user, :admin_project, @project)
+ - if access || can_edit
+ %span.dropdown.project-settings-dropdown
+ %a.dropdown-new.btn.btn-gray#project-settings-button{href: '#', 'data-toggle' => 'dropdown'}
+ = icon('cog')
+ = icon('angle-down')
+ %ul.dropdown-menu.dropdown-menu-right
+ - if can_edit
+ %li
+ = link_to edit_project_path(@project) do
+ Edit Project
+ - if access
+ %li
+ = link_to leave_namespace_project_project_members_path(@project.namespace, @project),
+ data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project' do
+ Leave Project
.project-repo-buttons
.split-one.count-buttons
diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml
index 5081bae6801..a234536723e 100644
--- a/app/views/projects/branches/_branch.html.haml
+++ b/app/views/projects/branches/_branch.html.haml
@@ -1,4 +1,8 @@
- commit = @repository.commit(branch.target)
+- bar_graph_width_factor = @max_commits > 0 ? 100.0/@max_commits : 0
+- diverging_commit_counts = @repository.diverging_commit_counts(branch)
+- number_commits_behind = diverging_commit_counts[:behind]
+- number_commits_ahead = diverging_commit_counts[:ahead]
%li(class="js-branch-#{branch.name}")
%div
= link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do
@@ -29,6 +33,17 @@
= link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row has_tooltip', title: "Delete branch", method: :delete, data: { confirm: "Deleting the '#{branch.name}' branch cannot be undone. Are you sure?", container: 'body' }, remote: true do
= icon("trash-o")
+ - if branch.name != @repository.root_ref
+ .divergence-graph{ title: "#{number_commits_ahead} commits ahead, #{number_commits_behind} commits behind #{@repository.root_ref}" }
+ .graph-side
+ .bar.bar-behind{ style: "width: #{number_commits_behind * bar_graph_width_factor}%" }
+ %span.count.count-behind= number_commits_behind
+ .graph-separator
+ .graph-side
+ .bar.bar-ahead{ style: "width: #{number_commits_ahead * bar_graph_width_factor}%" }
+ %span.count.count-ahead= number_commits_ahead
+
+
- if commit
= render 'projects/branches/commit', commit: commit, project: @project
- else
diff --git a/app/views/projects/builds/index.html.haml b/app/views/projects/builds/index.html.haml
index 3fa21faf970..3bbfdb1e3b0 100644
--- a/app/views/projects/builds/index.html.haml
+++ b/app/views/projects/builds/index.html.haml
@@ -11,6 +11,12 @@
%ul.center-top-menu
%li{class: ('active' if @scope.nil?)}
= link_to project_builds_path(@project) do
+ All
+ %span.badge.js-totalbuilds-count
+ = number_with_delimiter(@all_builds.count(:id))
+
+ %li{class: ('active' if @scope == 'running')}
+ = link_to project_builds_path(@project, scope: :running) do
Running
%span.badge.js-running-count
= number_with_delimiter(@all_builds.running_or_pending.count(:id))
@@ -21,12 +27,6 @@
%span.badge.js-running-count
= number_with_delimiter(@all_builds.finished.count(:id))
- %li{class: ('active' if @scope == 'all')}
- = link_to project_builds_path(@project, scope: :all) do
- All
- %span.badge.js-totalbuilds-count
- = number_with_delimiter(@all_builds.count(:id))
-
.gray-content-block
#{(@scope || 'running').capitalize} builds from this project
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
index 1f639fecc30..f9ab78e7874 100644
--- a/app/views/projects/buttons/_dropdown.html.haml
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -8,9 +8,10 @@
= link_to url_for_new_issue(@project, only_path: true) do
= icon('exclamation-circle fw')
New issue
- - if can?(current_user, :create_merge_request, @project)
+ - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
+ - if merge_project
%li
- = link_to new_namespace_project_merge_request_path(@project.namespace, @project) do
+ = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project) do
= icon('tasks fw')
New merge request
- if can?(current_user, :create_snippet, @project)
diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml
index 069b8b1f169..58aa45e8d2c 100644
--- a/app/views/projects/commit/show.html.haml
+++ b/app/views/projects/commit/show.html.haml
@@ -1,4 +1,6 @@
-- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
+- page_title "#{@commit.title} (#{@commit.short_id})", "Commits"
+- page_description @commit.description
+
= render "projects/commits/header_title"
= render "commit_box"
- if @ci_commit
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 28b82dd31f3..012825f0fdb 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -5,7 +5,7 @@
- note_count = notes.user.count
- ci_commit = project.ci_commit(commit.sha)
-- cache_key = [project.path_with_namespace, commit.id, note_count]
+- cache_key = [project.path_with_namespace, commit.id, current_application_settings, note_count]
- cache_key.push(ci_commit.status) if ci_commit
= cache(cache_key) do
diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml
index 0cd9ce1f371..6c631228002 100644
--- a/app/views/projects/commits/_commits.html.haml
+++ b/app/views/projects/commits/_commits.html.haml
@@ -6,7 +6,7 @@
.col-md-2.hidden-xs.hidden-sm
%h5.commits-row-date
%i.fa.fa-calendar
- %span= day.stamp("28 Aug, 2010")
+ %span= day.strftime('%d %b, %Y')
.light
= pluralize(commits.count, 'commit')
.col-md-10.col-sm-12
diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder
index 7ffa7317196..e310fafd82c 100644
--- a/app/views/projects/commits/show.atom.builder
+++ b/app/views/projects/commits/show.atom.builder
@@ -4,14 +4,14 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_commits_url(@project.namespace, @project, @ref), rel: "alternate", type: "text/html"
xml.id namespace_project_commits_url(@project.namespace, @project, @ref)
- xml.updated @commits.first.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") if @commits.any?
+ xml.updated @commits.first.committed_date.xmlschema if @commits.any?
@commits.each do |commit|
xml.entry do
xml.id namespace_project_commit_url(@project.namespace, @project, id: commit.id)
xml.link href: namespace_project_commit_url(@project.namespace, @project, id: commit.id)
xml.title truncate(commit.title, length: 80)
- xml.updated commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")
+ xml.updated commit.committed_date.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: image_url(avatar_icon(commit.author_email))
xml.author do |author|
xml.name commit.author_name
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 2dd99cc8215..034057da42e 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -10,26 +10,30 @@
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'commits'
- .commits-feed-holder.hidden-xs.hidden-sm
+ .block-controls.hidden-xs.hidden-sm
- if create_mr_button?(@repository.root_ref, @ref)
- = link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do
- = icon('plus')
- Create Merge Request
+ .control
+ = link_to create_mr_path(@repository.root_ref, @ref), class: 'btn btn-success' do
+ = icon('plus')
+ Create Merge Request
+
+ .control
+ = form_tag(namespace_project_commits_path(@project.namespace, @project, @id), method: :get, class: 'pull-left commits-search-form') do
+ = search_field_tag :search, params[:search], { placeholder: 'Filter by commit message', id: 'commits-search', class: 'form-control search-text-input', spellcheck: false }
- if current_user && current_user.private_token
- = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'prepend-left-10 btn' do
- = icon("rss")
+ .control
+ = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'btn' do
+ = icon("rss")
%ul.breadcrumb.repo-breadcrumb
= commits_breadcrumbs
%div{id: dom_id(@project)}
- #commits-list= render "commits", project: @project
+ #commits-list.content_list= render "commits", project: @project
.clear
= spinner
-- if @commits.count == @limit
- :javascript
- CommitsList.init("#{@ref}", #{@limit});
-
+:javascript
+ CommitsList.init("#{@ref}", #{@limit});
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 650629ef1b9..31e752c6649 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -174,6 +174,19 @@
.danger-settings
+ .panel.panel-default
+ .panel-heading Housekeeping
+ .errors-holder
+ .panel-body
+ %p
+ Runs a number of housekeeping tasks within the current repository,
+ such as compressing file revisions and removing unreachable objects.
+ %br
+
+ .form-actions
+ = link_to 'Housekeeping', housekeeping_namespace_project_path(@project.namespace, @project),
+ method: :post, class: "btn btn-default"
+
- if can? current_user, :archive_project, @project
- if @project.archived?
.panel.panel-success
diff --git a/app/views/projects/find_file/show.html.haml b/app/views/projects/find_file/show.html.haml
new file mode 100644
index 00000000000..2930209fb56
--- /dev/null
+++ b/app/views/projects/find_file/show.html.haml
@@ -0,0 +1,27 @@
+- page_title "Find File", @ref
+- header_title project_title(@project, "Files", project_files_path(@project))
+
+.file-finder-holder.tree-holder.clearfix
+ .gray-content-block.top-block
+ .tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'find_file', path: @path
+ %ul.breadcrumb.repo-breadcrumb
+ %li
+ = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do
+ = @project.path
+ %li.file-finder
+ %input#file_find.form-control.file-finder-input{type: "text", placeholder: 'Find by path'}
+
+ %div.tree-content-holder
+ .table-holder
+ %table.table.files-slider{class: "table_#{@hex_path} tree-table table-striped" }
+ %tbody
+ = spinner nil, true
+
+:coffeescript
+ projectFindFile = new ProjectFindFile($(".file-finder-holder"), {
+ url: "#{escape_javascript(namespace_project_files_path(@project.namespace, @project, @ref, @options.merge(format: :json)))}"
+ treeUrl: "#{escape_javascript(namespace_project_tree_path(@project.namespace, @project, @ref))}"
+ blobUrlTemplate: "#{escape_javascript(namespace_project_blob_path(@project.namespace, @project, @id || @commit.id))}"
+ })
+ new ShortcutsFindFile(projectFindFile)
diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml
index b18d9197d0b..a0511819c9f 100644
--- a/app/views/projects/hooks/index.html.haml
+++ b/app/views/projects/hooks/index.html.haml
@@ -47,14 +47,14 @@
= f.label :issues_events, class: 'list-label' do
%strong Issues events
%p.light
- This url will be triggered when an issue is created
+ This url will be triggered when an issue is created/updated/merged
%div
= f.check_box :merge_requests_events, class: 'pull-left'
.prepend-left-20
= f.label :merge_requests_events, class: 'list-label' do
%strong Merge Request events
%p.light
- This url will be triggered when a merge request is created
+ This url will be triggered when a merge request is created/updated/merged
%div
= f.check_box :build_events, class: 'pull-left'
.prepend-left-20
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index ca5b1a8386d..e0e89b764d5 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -7,7 +7,7 @@
- if @issues.present?
.issuable-filter-count
%span.pull-right
- = @issues.total_count
+ = number_with_delimiter(@issues.total_count)
issues for this filter
= paginate @issues, theme: "gitlab"
diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index dc8e477185b..ee8a9414657 100644
--- a/app/views/projects/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: namespace_project_issues_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_issues_url(@project.namespace, @project), rel: "alternate", type: "text/html"
xml.id namespace_project_issues_url(@project.namespace, @project)
- xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
+ xml.updated @issues.first.created_at.xmlschema if @issues.any?
@issues.each do |issue|
issue_to_atom(xml, issue)
diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml
index 0af970e4b92..29d09d0a652 100644
--- a/app/views/projects/merge_requests/_merge_requests.html.haml
+++ b/app/views/projects/merge_requests/_merge_requests.html.haml
@@ -7,7 +7,7 @@
- if @merge_requests.present?
.issuable-filter-count
%span.pull-right
- = @merge_requests.total_count
+ = number_with_delimiter(@merge_requests.total_count)
merge requests for this filter
= paginate @merge_requests, theme: "gitlab"
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index ba7c2c01e93..095876450a0 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -38,7 +38,7 @@
= render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/widget/show.html.haml"
- - if @merge_request.open? && @merge_request.source_branch_exists? && @merge_request.can_be_merged? && @merge_request.can_be_merged_by?(current_user)
+ - if @merge_request.source_branch_exists? && @merge_request.mergeable? && @merge_request.can_be_merged_by?(current_user)
.light.prepend-top-default
You can also accept this merge request manually using the
= succeed '.' do
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 086298e5af1..8d5d0394a82 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -6,9 +6,10 @@
.controls
= render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
- - if can? current_user, :create_merge_request, @project
+ - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
+ - if merge_project
.pull-left.hidden-xs
- = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-new", title: "New Merge Request" do
+ = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do
%i.fa.fa-plus
New Merge Request
= render 'shared/issuable/filter', type: :merge_requests
diff --git a/app/views/projects/show.atom.builder b/app/views/projects/show.atom.builder
index 15c49767556..d6762219108 100644
--- a/app/views/projects/show.atom.builder
+++ b/app/views/projects/show.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html"
xml.id namespace_project_url(@project.namespace, @project)
- xml.updated @events.latest_update_time.strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
+ xml.updated @events.latest_update_time.xmlschema if @events.any?
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 7466a098e24..8436be433b1 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -69,14 +69,3 @@
%div{class: "project-show-#{default_project_view}"}
= render default_project_view
-
-- if current_user
- - access = user_max_access_in_project(current_user.id, @project)
- - if access
- .prepend-top-20.project-footer
- .gray-content-block.footer-block.center
- You have #{access} access to this project.
- - if @project.project_member_by_id(current_user)
- = link_to leave_namespace_project_project_members_path(@project.namespace, @project),
- data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project', class: 'cred' do
- Leave this project
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index ec14bd7f65a..c57570afa09 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -3,12 +3,12 @@
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits")
-
= render 'projects/last_push'
-- if can? current_user, :download_code, @project
- .tree-download-holder
- = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group pull-right hidden-xs hidden-sm', split_button: true
+.pull-right
+ = render 'projects/find_file_link'
+ - if can? current_user, :download_code, @project
+ = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'hidden-xs hidden-sm btn-grouped', split_button: true
#tree-holder.tree-holder.clearfix
.gray-content-block.top-block
diff --git a/app/views/shared/_logo.svg b/app/views/shared/_logo.svg
index da49c48acd3..3d279ec228c 100644
--- a/app/views/shared/_logo.svg
+++ b/app/views/shared/_logo.svg
@@ -5,13 +5,13 @@
<g id="Fill-1-+-Group-24">
<g id="Group-24">
<g id="Group">
- <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329" class="tanuki-shape"></path>
- <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26" class="tanuki-shape"></path>
- <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326" class="tanuki-shape"></path>
- <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329" class="tanuki-shape"></path>
- <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26" class="tanuki-shape"></path>
- <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326" class="tanuki-shape"></path>
- <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329" class="tanuki-shape"></path>
+ <path id="tanuki-right-ear" d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" fill="#E24329" class="tanuki-shape"></path>
+ <path id="tanuki-right-cheek" d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" fill="#FCA326" class="tanuki-shape"></path>
+ <path id="tanuki-right-eye" d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" fill="#FC6D26" class="tanuki-shape"></path>
+ <path id="tanuki-nose" d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" fill="#E24329" class="tanuki-shape"></path>
+ <path id="tanuki-left-eye" d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" fill="#FC6D26" class="tanuki-shape"></path>
+ <path id="tanuki-left-cheek" d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" fill="#FCA326" class="tanuki-shape"></path>
+ <path id="tanuki-left-ear" d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" fill="#E24329" class="tanuki-shape"></path>
</g>
</g>
</g>
diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml
index 28d6f421fea..5a60ff5a5da 100644
--- a/app/views/shared/_service_settings.html.haml
+++ b/app/views/shared/_service_settings.html.haml
@@ -50,7 +50,7 @@
= form.label :issues_events, class: 'list-label' do
%strong Issues events
%p.light
- This url will be triggered when an issue is created
+ This url will be triggered when an issue is created/updated/merged
- if @service.supported_events.include?("merge_request")
%div
= form.check_box :merge_requests_events, class: 'pull-left'
@@ -58,7 +58,7 @@
= form.label :merge_requests_events, class: 'list-label' do
%strong Merge Request events
%p.light
- This url will be triggered when a merge request is created
+ This url will be triggered when a merge request is created/updated/merged
- if @service.supported_events.include?("build")
%div
= form.check_box :build_events, class: 'pull-left'
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index 79c5cc7f40a..2299112bec7 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -54,14 +54,6 @@
= f.collection_select :label_ids, issuable.project.labels.all, :id, :name,
{ selected: issuable.label_ids }, multiple: true, class: 'select2 js-select2', data: { placeholder: "Select labels" }
- .block
- .title
- Cross-project reference
- .cross-project-reference
- %span#cross-project-reference
- = cross_project_reference(@project, issuable)
- = clipboard_button(clipboard_target: 'span#cross-project-reference')
-
= render "shared/issuable/participants", participants: issuable.participants(current_user)
- if current_user
@@ -77,7 +69,16 @@
You're not receiving notifications from this thread.
.subscribed{class: ( 'hidden' unless subscribed )}
You're receiving notifications because you're subscribed to this thread.
+ - project_ref = cross_project_reference(@project, issuable)
+ .block
+ .title
+ .cross-project-reference
+ %span#cross-project-reference
+ Reference:
+ %a{href: '#', title:project_ref}
+ = project_ref
+ = clipboard_button(clipboard_target: 'span#cross-project-reference')
:javascript
new Subscription("#{toggle_subscription_path(issuable)}");
- new IssuableContext();
+ new IssuableContext(); \ No newline at end of file
diff --git a/app/views/shared/projects/_project.html.haml b/app/views/shared/projects/_project.html.haml
index c36995b94d7..86249851a82 100644
--- a/app/views/shared/projects/_project.html.haml
+++ b/app/views/shared/projects/_project.html.haml
@@ -5,7 +5,7 @@
- css_class = '' unless local_assigns[:css_class]
- css_class += " no-description" unless project.description.present?
%li.project-row{ class: css_class }
- = cache [project.namespace, project, controller.controller_name, controller.action_name, 'v2.2'] do
+ = cache [project.namespace, project, controller.controller_name, controller.action_name, current_application_settings, 'v2.2'] do
= link_to project_path(project), class: dom_class(project) do
- if avatar
.dash-project-avatar
diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder
index 2fe5b7fac83..114d1e7a379 100644
--- a/app/views/users/show.atom.builder
+++ b/app/views/users/show.atom.builder
@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml"
xml.link href: user_url(@user), rel: "alternate", type: "text/html"
xml.id user_url(@user)
- xml.updated @events.latest_update_time.strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
+ xml.updated @events.latest_update_time.xmlschema if @events.any?
@events.each do |event|
event_to_atom(xml, event)
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 0bca8177e14..ce17fc7bca1 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -21,7 +21,7 @@
%span
#{@user.bio}.
%span
- Member since #{@user.created_at.stamp("Aug 21, 2011")}
+ Member since #{@user.created_at.to_s(:medium)}
.cover-desc
- unless @user.public_email.blank?
diff --git a/config/initializers/date_time_formats.rb b/config/initializers/date_time_formats.rb
new file mode 100644
index 00000000000..57568203cab
--- /dev/null
+++ b/config/initializers/date_time_formats.rb
@@ -0,0 +1,9 @@
+# :short - 10 Nov
+# :medium - Nov 10, 2007
+# :long - November 10, 2007
+Date::DATE_FORMATS[:medium] = '%b %-d, %Y'
+
+# :short - 18 Jan 06:10
+# :medium - Jan 18, 2007 6:10am
+# :long - January 18, 2007 06:10
+Time::DATE_FORMATS[:medium] = '%b %-d, %Y %-I:%M%P'
diff --git a/config/initializers/metrics.rb b/config/initializers/metrics.rb
index 2e4908192a1..52ace27b7ae 100644
--- a/config/initializers/metrics.rb
+++ b/config/initializers/metrics.rb
@@ -1,6 +1,5 @@
if Gitlab::Metrics.enabled?
require 'influxdb'
- require 'socket'
require 'connection_pool'
require 'method_source'
diff --git a/config/routes.rb b/config/routes.rb
index 3e7d9f78710..3d5c70987c8 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -52,9 +52,6 @@ Rails.application.routes.draw do
API::API.logger Rails.logger
mount API::API => '/api'
- # Get all keys of user
- get ':username.keys' => 'profiles/keys#get_keys' , constraints: { username: /.*/ }
-
constraint = lambda { |request| request.env['warden'].authenticate? and request.env['warden'].user.admin? }
constraints constraint do
mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq
@@ -378,6 +375,7 @@ Rails.application.routes.draw do
delete :remove_fork
post :archive
post :unarchive
+ post :housekeeping
post :toggle_star
post :markdown_preview
get :autocomplete_sources
@@ -441,6 +439,24 @@ Rails.application.routes.draw do
end
scope do
+ get(
+ '/find_file/*id',
+ to: 'find_file#show',
+ constraints: { id: /.+/, format: /html/ },
+ as: :find_file
+ )
+ end
+
+ scope do
+ get(
+ '/files/*id',
+ to: 'find_file#list',
+ constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ },
+ as: :files
+ )
+ end
+
+ scope do
post(
'/create_dir/*id',
to: 'tree#create_dir',
@@ -668,5 +684,8 @@ Rails.application.routes.draw do
end
end
+ # Get all keys of user
+ get ':username.keys' => 'profiles/keys#get_keys' , constraints: { username: /.*/ }
+
get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
end
diff --git a/db/migrate/20151228111122_remove_public_from_namespace.rb b/db/migrate/20151228111122_remove_public_from_namespace.rb
new file mode 100644
index 00000000000..f4c848bbf47
--- /dev/null
+++ b/db/migrate/20151228111122_remove_public_from_namespace.rb
@@ -0,0 +1,6 @@
+# Migration type: online
+class RemovePublicFromNamespace < ActiveRecord::Migration
+ def change
+ remove_column :namespaces, :public, :boolean
+ end
+end
diff --git a/db/migrate/20160106164438_remove_influxdb_credentials.rb b/db/migrate/20160106164438_remove_influxdb_credentials.rb
new file mode 100644
index 00000000000..47e74400b97
--- /dev/null
+++ b/db/migrate/20160106164438_remove_influxdb_credentials.rb
@@ -0,0 +1,6 @@
+class RemoveInfluxdbCredentials < ActiveRecord::Migration
+ def change
+ remove_column :application_settings, :metrics_username, :string
+ remove_column :application_settings, :metrics_password, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index df7f72d5ad4..2ded8a45e18 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: 20151229112614) do
+ActiveRecord::Schema.define(version: 20160106164438) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -32,44 +32,42 @@ ActiveRecord::Schema.define(version: 20151229112614) do
t.text "sign_in_text"
t.datetime "created_at"
t.datetime "updated_at"
- t.string "home_page_url"
- t.integer "default_branch_protection", default: 2
- t.boolean "twitter_sharing_enabled", default: true
+ t.string "home_page_url", limit: 255
+ t.integer "default_branch_protection", default: 2
+ t.boolean "twitter_sharing_enabled", default: true
t.text "restricted_visibility_levels"
- t.boolean "version_check_enabled", default: true
- t.integer "max_attachment_size", default: 10, null: false
+ t.boolean "version_check_enabled", default: true
+ t.integer "max_attachment_size", default: 10, null: false
t.integer "default_project_visibility"
t.integer "default_snippet_visibility"
t.text "restricted_signup_domains"
- t.boolean "user_oauth_applications", default: true
- t.string "after_sign_out_path"
- t.integer "session_expire_delay", default: 10080, null: false
+ t.boolean "user_oauth_applications", default: true
+ t.string "after_sign_out_path", limit: 255
+ t.integer "session_expire_delay", default: 10080, null: false
t.text "import_sources"
t.text "help_page_text"
- t.string "admin_notification_email"
- t.boolean "shared_runners_enabled", default: true, null: false
- t.integer "max_artifacts_size", default: 100, null: false
+ t.string "admin_notification_email", limit: 255
+ t.boolean "shared_runners_enabled", default: true, null: false
+ t.integer "max_artifacts_size", default: 100, null: false
t.string "runners_registration_token"
t.boolean "require_two_factor_authentication", default: false
t.integer "two_factor_grace_period", default: 48
t.boolean "metrics_enabled", default: false
t.string "metrics_host", default: "localhost"
- t.string "metrics_username"
- t.string "metrics_password"
t.integer "metrics_pool_size", default: 16
t.integer "metrics_timeout", default: 10
t.integer "metrics_method_call_threshold", default: 10
t.boolean "recaptcha_enabled", default: false
t.string "recaptcha_site_key"
t.string "recaptcha_private_key"
- t.integer "metrics_port", default: 8089
+ t.integer "metrics_port", default: 8089
end
create_table "audit_events", force: :cascade do |t|
- t.integer "author_id", null: false
- t.string "type", null: false
- t.integer "entity_id", null: false
- t.string "entity_type", null: false
+ t.integer "author_id", null: false
+ t.string "type", limit: 255, null: false
+ t.integer "entity_id", null: false
+ t.string "entity_type", limit: 255, null: false
t.text "details"
t.datetime "created_at"
t.datetime "updated_at"
@@ -80,14 +78,14 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree
create_table "broadcast_messages", force: :cascade do |t|
- t.text "message", null: false
+ t.text "message", null: false
t.datetime "starts_at"
t.datetime "ends_at"
t.integer "alert_type"
t.datetime "created_at"
t.datetime "updated_at"
- t.string "color"
- t.string "font"
+ t.string "color", limit: 255
+ t.string "font", limit: 255
end
create_table "ci_application_settings", force: :cascade do |t|
@@ -99,7 +97,7 @@ ActiveRecord::Schema.define(version: 20151229112614) do
create_table "ci_builds", force: :cascade do |t|
t.integer "project_id"
- t.string "status"
+ t.string "status", limit: 255
t.datetime "finished_at"
t.text "trace"
t.datetime "created_at"
@@ -110,19 +108,19 @@ ActiveRecord::Schema.define(version: 20151229112614) do
t.integer "commit_id"
t.text "commands"
t.integer "job_id"
- t.string "name"
- t.boolean "deploy", default: false
+ t.string "name", limit: 255
+ t.boolean "deploy", default: false
t.text "options"
- t.boolean "allow_failure", default: false, null: false
- t.string "stage"
+ t.boolean "allow_failure", default: false, null: false
+ t.string "stage", limit: 255
t.integer "trigger_request_id"
t.integer "stage_idx"
t.boolean "tag"
- t.string "ref"
+ t.string "ref", limit: 255
t.integer "user_id"
- t.string "type"
- t.string "target_url"
- t.string "description"
+ t.string "type", limit: 255
+ t.string "target_url", limit: 255
+ t.string "description", limit: 255
t.text "artifacts_file"
t.integer "gl_project_id"
end
@@ -141,13 +139,13 @@ ActiveRecord::Schema.define(version: 20151229112614) do
create_table "ci_commits", force: :cascade do |t|
t.integer "project_id"
- t.string "ref"
- t.string "sha"
- t.string "before_sha"
+ t.string "ref", limit: 255
+ t.string "sha", limit: 255
+ t.string "before_sha", limit: 255
t.text "push_data"
t.datetime "created_at"
t.datetime "updated_at"
- t.boolean "tag", default: false
+ t.boolean "tag", default: false
t.text "yaml_errors"
t.datetime "committed_at"
t.integer "gl_project_id"
@@ -174,16 +172,16 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "ci_events", ["project_id"], name: "index_ci_events_on_project_id", using: :btree
create_table "ci_jobs", force: :cascade do |t|
- t.integer "project_id", null: false
+ t.integer "project_id", null: false
t.text "commands"
- t.boolean "active", default: true, null: false
+ t.boolean "active", default: true, null: false
t.datetime "created_at"
t.datetime "updated_at"
- t.string "name"
- t.boolean "build_branches", default: true, null: false
- t.boolean "build_tags", default: false, null: false
- t.string "job_type", default: "parallel"
- t.string "refs"
+ t.string "name", limit: 255
+ t.boolean "build_branches", default: true, null: false
+ t.boolean "build_tags", default: false, null: false
+ t.string "job_type", limit: 255, default: "parallel"
+ t.string "refs", limit: 255
t.datetime "deleted_at"
end
@@ -191,25 +189,25 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "ci_jobs", ["project_id"], name: "index_ci_jobs_on_project_id", using: :btree
create_table "ci_projects", force: :cascade do |t|
- t.string "name"
- t.integer "timeout", default: 3600, null: false
+ t.string "name", limit: 255
+ t.integer "timeout", default: 3600, null: false
t.datetime "created_at"
t.datetime "updated_at"
- t.string "token"
- t.string "default_ref"
- t.string "path"
- t.boolean "always_build", default: false, null: false
+ t.string "token", limit: 255
+ t.string "default_ref", limit: 255
+ t.string "path", limit: 255
+ t.boolean "always_build", default: false, null: false
t.integer "polling_interval"
- t.boolean "public", default: false, null: false
- t.string "ssh_url_to_repo"
+ t.boolean "public", default: false, null: false
+ t.string "ssh_url_to_repo", limit: 255
t.integer "gitlab_id"
- t.boolean "allow_git_fetch", default: true, null: false
- t.string "email_recipients", default: "", null: false
- t.boolean "email_add_pusher", default: true, null: false
- t.boolean "email_only_broken_builds", default: true, null: false
- t.string "skip_refs"
- t.string "coverage_regex"
- t.boolean "shared_runners_enabled", default: false
+ t.boolean "allow_git_fetch", default: true, null: false
+ t.string "email_recipients", limit: 255, default: "", null: false
+ t.boolean "email_add_pusher", default: true, null: false
+ t.boolean "email_only_broken_builds", default: true, null: false
+ t.string "skip_refs", limit: 255
+ t.string "coverage_regex", limit: 255
+ t.boolean "shared_runners_enabled", default: false
t.text "generated_yaml_config"
end
@@ -228,34 +226,34 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "ci_runner_projects", ["runner_id"], name: "index_ci_runner_projects_on_runner_id", using: :btree
create_table "ci_runners", force: :cascade do |t|
- t.string "token"
+ t.string "token", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
- t.string "description"
+ t.string "description", limit: 255
t.datetime "contacted_at"
- t.boolean "active", default: true, null: false
- t.boolean "is_shared", default: false
- t.string "name"
- t.string "version"
- t.string "revision"
- t.string "platform"
- t.string "architecture"
+ t.boolean "active", default: true, null: false
+ t.boolean "is_shared", default: false
+ t.string "name", limit: 255
+ t.string "version", limit: 255
+ t.string "revision", limit: 255
+ t.string "platform", limit: 255
+ t.string "architecture", limit: 255
end
create_table "ci_services", force: :cascade do |t|
- t.string "type"
- t.string "title"
- t.integer "project_id", null: false
+ t.string "type", limit: 255
+ t.string "title", limit: 255
+ t.integer "project_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
- t.boolean "active", default: false, null: false
+ t.boolean "active", default: false, null: false
t.text "properties"
end
add_index "ci_services", ["project_id"], name: "index_ci_services_on_project_id", using: :btree
create_table "ci_sessions", force: :cascade do |t|
- t.string "session_id", null: false
+ t.string "session_id", limit: 255, null: false
t.text "data"
t.datetime "created_at"
t.datetime "updated_at"
@@ -267,9 +265,9 @@ ActiveRecord::Schema.define(version: 20151229112614) do
create_table "ci_taggings", force: :cascade do |t|
t.integer "tag_id"
t.integer "taggable_id"
- t.string "taggable_type"
+ t.string "taggable_type", limit: 255
t.integer "tagger_id"
- t.string "tagger_type"
+ t.string "tagger_type", limit: 255
t.string "context", limit: 128
t.datetime "created_at"
end
@@ -278,8 +276,8 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "ci_taggings", ["taggable_id", "taggable_type", "context"], name: "index_ci_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
create_table "ci_tags", force: :cascade do |t|
- t.string "name"
- t.integer "taggings_count", default: 0
+ t.string "name", limit: 255
+ t.integer "taggings_count", default: 0
end
add_index "ci_tags", ["name"], name: "index_ci_tags_on_name", unique: true, using: :btree
@@ -293,7 +291,7 @@ ActiveRecord::Schema.define(version: 20151229112614) do
end
create_table "ci_triggers", force: :cascade do |t|
- t.string "token"
+ t.string "token", limit: 255
t.integer "project_id"
t.datetime "deleted_at"
t.datetime "created_at"
@@ -306,19 +304,19 @@ ActiveRecord::Schema.define(version: 20151229112614) do
create_table "ci_variables", force: :cascade do |t|
t.integer "project_id"
- t.string "key"
+ t.string "key", limit: 255
t.text "value"
t.text "encrypted_value"
- t.string "encrypted_value_salt"
- t.string "encrypted_value_iv"
+ t.string "encrypted_value_salt", limit: 255
+ t.string "encrypted_value_iv", limit: 255
t.integer "gl_project_id"
end
add_index "ci_variables", ["gl_project_id"], name: "index_ci_variables_on_gl_project_id", using: :btree
create_table "ci_web_hooks", force: :cascade do |t|
- t.string "url", null: false
- t.integer "project_id", null: false
+ t.string "url", limit: 255, null: false
+ t.integer "project_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
@@ -333,8 +331,8 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "deploy_keys_projects", ["project_id"], name: "index_deploy_keys_projects_on_project_id", using: :btree
create_table "emails", force: :cascade do |t|
- t.integer "user_id", null: false
- t.string "email", null: false
+ t.integer "user_id", null: false
+ t.string "email", limit: 255, null: false
t.datetime "created_at"
t.datetime "updated_at"
end
@@ -343,9 +341,9 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "emails", ["user_id"], name: "index_emails_on_user_id", using: :btree
create_table "events", force: :cascade do |t|
- t.string "target_type"
+ t.string "target_type", limit: 255
t.integer "target_id"
- t.string "title"
+ t.string "title", limit: 255
t.text "data"
t.integer "project_id"
t.datetime "created_at"
@@ -371,8 +369,8 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree
create_table "identities", force: :cascade do |t|
- t.string "extern_uid"
- t.string "provider"
+ t.string "extern_uid", limit: 255
+ t.string "provider", limit: 255
t.integer "user_id"
t.datetime "created_at"
t.datetime "updated_at"
@@ -382,17 +380,17 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree
create_table "issues", force: :cascade do |t|
- t.string "title"
+ t.string "title", limit: 255
t.integer "assignee_id"
t.integer "author_id"
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.integer "position", default: 0
- t.string "branch_name"
+ t.integer "position", default: 0
+ t.string "branch_name", limit: 255
t.text "description"
t.integer "milestone_id"
- t.string "state"
+ t.string "state", limit: 255
t.integer "iid"
t.integer "updated_by_id"
end
@@ -412,10 +410,10 @@ ActiveRecord::Schema.define(version: 20151229112614) do
t.datetime "created_at"
t.datetime "updated_at"
t.text "key"
- t.string "title"
- t.string "type"
- t.string "fingerprint"
- t.boolean "public", default: false, null: false
+ t.string "title", limit: 255
+ t.string "type", limit: 255
+ t.string "fingerprint", limit: 255
+ t.boolean "public", default: false, null: false
end
add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree
@@ -424,7 +422,7 @@ ActiveRecord::Schema.define(version: 20151229112614) do
create_table "label_links", force: :cascade do |t|
t.integer "label_id"
t.integer "target_id"
- t.string "target_type"
+ t.string "target_type", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
end
@@ -433,22 +431,22 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "label_links", ["target_id", "target_type"], name: "index_label_links_on_target_id_and_target_type", using: :btree
create_table "labels", force: :cascade do |t|
- t.string "title"
- t.string "color"
+ t.string "title", limit: 255
+ t.string "color", limit: 255
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.boolean "template", default: false
+ t.boolean "template", default: false
end
add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree
create_table "lfs_objects", force: :cascade do |t|
- t.string "oid", null: false
- t.integer "size", null: false
+ t.string "oid", limit: 255, null: false
+ t.integer "size", null: false
t.datetime "created_at"
t.datetime "updated_at"
- t.string "file"
+ t.string "file", limit: 255
end
add_index "lfs_objects", ["oid"], name: "index_lfs_objects_on_oid", unique: true, using: :btree
@@ -463,17 +461,17 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "lfs_objects_projects", ["project_id"], name: "index_lfs_objects_projects_on_project_id", using: :btree
create_table "members", force: :cascade do |t|
- t.integer "access_level", null: false
- t.integer "source_id", null: false
- t.string "source_type", null: false
+ t.integer "access_level", null: false
+ t.integer "source_id", null: false
+ t.string "source_type", limit: 255, null: false
t.integer "user_id"
- t.integer "notification_level", null: false
- t.string "type"
+ t.integer "notification_level", null: false
+ t.string "type", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
t.integer "created_by_id"
- t.string "invite_email"
- t.string "invite_token"
+ t.string "invite_email", limit: 255
+ t.string "invite_token", limit: 255
t.datetime "invite_accepted_at"
end
@@ -485,10 +483,10 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree
create_table "merge_request_diffs", force: :cascade do |t|
- t.string "state"
+ t.string "state", limit: 255
t.text "st_commits"
t.text "st_diffs"
- t.integer "merge_request_id", null: false
+ t.integer "merge_request_id", null: false
t.datetime "created_at"
t.datetime "updated_at"
end
@@ -496,26 +494,26 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "merge_request_diffs", ["merge_request_id"], name: "index_merge_request_diffs_on_merge_request_id", unique: true, using: :btree
create_table "merge_requests", force: :cascade do |t|
- t.string "target_branch", null: false
- t.string "source_branch", null: false
- t.integer "source_project_id", null: false
+ t.string "target_branch", limit: 255, null: false
+ t.string "source_branch", limit: 255, null: false
+ t.integer "source_project_id", null: false
t.integer "author_id"
t.integer "assignee_id"
- t.string "title"
+ t.string "title", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
t.integer "milestone_id"
- t.string "state"
- t.string "merge_status"
- t.integer "target_project_id", null: false
+ t.string "state", limit: 255
+ t.string "merge_status", limit: 255
+ t.integer "target_project_id", null: false
t.integer "iid"
t.text "description"
- t.integer "position", default: 0
+ t.integer "position", default: 0
t.datetime "locked_at"
t.integer "updated_by_id"
- t.string "merge_error"
+ t.string "merge_error", limit: 255
t.text "merge_params"
- t.boolean "merge_when_build_succeeds", default: false, null: false
+ t.boolean "merge_when_build_succeeds", default: false, null: false
t.integer "merge_user_id"
end
@@ -531,13 +529,13 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "merge_requests", ["title"], name: "index_merge_requests_on_title", using: :btree
create_table "milestones", force: :cascade do |t|
- t.string "title", null: false
- t.integer "project_id", null: false
+ t.string "title", limit: 255, null: false
+ t.integer "project_id", null: false
t.text "description"
t.date "due_date"
t.datetime "created_at"
t.datetime "updated_at"
- t.string "state"
+ t.string "state", limit: 255
t.integer "iid"
end
@@ -547,39 +545,37 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree
create_table "namespaces", force: :cascade do |t|
- t.string "name", null: false
- t.string "path", null: false
+ t.string "name", limit: 255, null: false
+ t.string "path", limit: 255, null: false
t.integer "owner_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.string "type"
- t.string "description", default: "", null: false
- t.string "avatar"
- t.boolean "public", default: false
+ t.string "type", limit: 255
+ t.string "description", limit: 255, default: "", null: false
+ t.string "avatar", limit: 255
end
add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree
add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree
add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree
add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree
- add_index "namespaces", ["public"], name: "index_namespaces_on_public", using: :btree
add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree
create_table "notes", force: :cascade do |t|
t.text "note"
- t.string "noteable_type"
+ t.string "noteable_type", limit: 255
t.integer "author_id"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "project_id"
- t.string "attachment"
- t.string "line_code"
- t.string "commit_id"
+ t.string "attachment", limit: 255
+ t.string "line_code", limit: 255
+ t.string "commit_id", limit: 255
t.integer "noteable_id"
- t.boolean "system", default: false, null: false
+ t.boolean "system", default: false, null: false
t.text "st_diff"
t.integer "updated_by_id"
- t.boolean "is_award", default: false, null: false
+ t.boolean "is_award", default: false, null: false
end
add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree
@@ -595,14 +591,14 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree
create_table "oauth_access_grants", force: :cascade do |t|
- t.integer "resource_owner_id", null: false
- t.integer "application_id", null: false
- t.string "token", null: false
- t.integer "expires_in", null: false
- t.text "redirect_uri", null: false
- t.datetime "created_at", null: false
+ t.integer "resource_owner_id", null: false
+ t.integer "application_id", null: false
+ t.string "token", limit: 255, null: false
+ t.integer "expires_in", null: false
+ t.text "redirect_uri", null: false
+ t.datetime "created_at", null: false
t.datetime "revoked_at"
- t.string "scopes"
+ t.string "scopes", limit: 255
end
add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree
@@ -610,12 +606,12 @@ ActiveRecord::Schema.define(version: 20151229112614) do
create_table "oauth_access_tokens", force: :cascade do |t|
t.integer "resource_owner_id"
t.integer "application_id"
- t.string "token", null: false
- t.string "refresh_token"
+ t.string "token", limit: 255, null: false
+ t.string "refresh_token", limit: 255
t.integer "expires_in"
t.datetime "revoked_at"
- t.datetime "created_at", null: false
- t.string "scopes"
+ t.datetime "created_at", null: false
+ t.string "scopes", limit: 255
end
add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree
@@ -623,15 +619,15 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree
create_table "oauth_applications", force: :cascade do |t|
- t.string "name", null: false
- t.string "uid", null: false
- t.string "secret", null: false
- t.text "redirect_uri", null: false
- t.string "scopes", default: "", null: false
+ t.string "name", limit: 255, null: false
+ t.string "uid", limit: 255, null: false
+ t.string "secret", limit: 255, null: false
+ t.text "redirect_uri", null: false
+ t.string "scopes", limit: 255, default: "", null: false
t.datetime "created_at"
t.datetime "updated_at"
t.integer "owner_id"
- t.string "owner_type"
+ t.string "owner_type", limit: 255
end
add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree
@@ -643,39 +639,39 @@ ActiveRecord::Schema.define(version: 20151229112614) do
end
create_table "projects", force: :cascade do |t|
- t.string "name"
- t.string "path"
+ t.string "name", limit: 255
+ t.string "path", limit: 255
t.text "description"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "creator_id"
- t.boolean "issues_enabled", default: true, null: false
- t.boolean "wall_enabled", default: true, null: false
- t.boolean "merge_requests_enabled", default: true, null: false
- t.boolean "wiki_enabled", default: true, null: false
+ t.boolean "issues_enabled", default: true, null: false
+ t.boolean "wall_enabled", default: true, null: false
+ t.boolean "merge_requests_enabled", default: true, null: false
+ t.boolean "wiki_enabled", default: true, null: false
t.integer "namespace_id"
- t.string "issues_tracker", default: "gitlab", null: false
- t.string "issues_tracker_id"
- t.boolean "snippets_enabled", default: true, null: false
+ t.string "issues_tracker", limit: 255, default: "gitlab", null: false
+ t.string "issues_tracker_id", limit: 255
+ t.boolean "snippets_enabled", default: true, null: false
t.datetime "last_activity_at"
- t.string "import_url"
- t.integer "visibility_level", default: 0, null: false
- t.boolean "archived", default: false, null: false
- t.string "avatar"
- t.string "import_status"
- t.float "repository_size", default: 0.0
- t.integer "star_count", default: 0, null: false
- t.string "import_type"
- t.string "import_source"
- t.integer "commit_count", default: 0
+ t.string "import_url", limit: 255
+ t.integer "visibility_level", default: 0, null: false
+ t.boolean "archived", default: false, null: false
+ t.string "avatar", limit: 255
+ t.string "import_status", limit: 255
+ t.float "repository_size", default: 0.0
+ t.integer "star_count", default: 0, null: false
+ t.string "import_type", limit: 255
+ t.string "import_source", limit: 255
+ t.integer "commit_count", default: 0
t.text "import_error"
t.integer "ci_id"
- t.boolean "builds_enabled", default: true, null: false
- t.boolean "shared_runners_enabled", default: true, null: false
+ t.boolean "builds_enabled", default: true, null: false
+ t.boolean "shared_runners_enabled", default: true, null: false
t.string "runners_token"
t.string "build_coverage_regex"
- t.boolean "build_allow_git_fetch", default: true, null: false
- t.integer "build_timeout", default: 3600, null: false
+ t.boolean "build_allow_git_fetch", default: true, null: false
+ t.integer "build_timeout", default: 3600, null: false
end
add_index "projects", ["builds_enabled", "shared_runners_enabled"], name: "index_projects_on_builds_enabled_and_shared_runners_enabled", using: :btree
@@ -691,17 +687,17 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "projects", ["visibility_level"], name: "index_projects_on_visibility_level", using: :btree
create_table "protected_branches", force: :cascade do |t|
- t.integer "project_id", null: false
- t.string "name", null: false
+ t.integer "project_id", null: false
+ t.string "name", limit: 255, null: false
t.datetime "created_at"
t.datetime "updated_at"
- t.boolean "developers_can_push", default: false, null: false
+ t.boolean "developers_can_push", default: false, null: false
end
add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree
create_table "releases", force: :cascade do |t|
- t.string "tag"
+ t.string "tag", limit: 255
t.text "description"
t.integer "project_id"
t.datetime "created_at"
@@ -714,30 +710,30 @@ ActiveRecord::Schema.define(version: 20151229112614) do
create_table "sent_notifications", force: :cascade do |t|
t.integer "project_id"
t.integer "noteable_id"
- t.string "noteable_type"
+ t.string "noteable_type", limit: 255
t.integer "recipient_id"
- t.string "commit_id"
- t.string "reply_key", null: false
- t.string "line_code"
+ t.string "commit_id", limit: 255
+ t.string "reply_key", limit: 255, null: false
+ t.string "line_code", limit: 255
end
add_index "sent_notifications", ["reply_key"], name: "index_sent_notifications_on_reply_key", unique: true, using: :btree
create_table "services", force: :cascade do |t|
- t.string "type"
- t.string "title"
+ t.string "type", limit: 255
+ t.string "title", limit: 255
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.boolean "active", default: false, null: false
+ t.boolean "active", default: false, null: false
t.text "properties"
- t.boolean "template", default: false
- t.boolean "push_events", default: true
- t.boolean "issues_events", default: true
- t.boolean "merge_requests_events", default: true
- t.boolean "tag_push_events", default: true
- t.boolean "note_events", default: true, null: false
- t.boolean "build_events", default: false, null: false
+ t.boolean "template", default: false
+ t.boolean "push_events", default: true
+ t.boolean "issues_events", default: true
+ t.boolean "merge_requests_events", default: true
+ t.boolean "tag_push_events", default: true
+ t.boolean "note_events", default: true, null: false
+ t.boolean "build_events", default: false, null: false
end
add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree
@@ -745,16 +741,16 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "services", ["template"], name: "index_services_on_template", using: :btree
create_table "snippets", force: :cascade do |t|
- t.string "title"
+ t.string "title", limit: 255
t.text "content"
- t.integer "author_id", null: false
+ t.integer "author_id", null: false
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.string "file_name"
+ t.string "file_name", limit: 255
t.datetime "expires_at"
- t.string "type"
- t.integer "visibility_level", default: 0, null: false
+ t.string "type", limit: 255
+ t.integer "visibility_level", default: 0, null: false
end
add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree
@@ -767,7 +763,7 @@ ActiveRecord::Schema.define(version: 20151229112614) do
create_table "subscriptions", force: :cascade do |t|
t.integer "user_id"
t.integer "subscribable_id"
- t.string "subscribable_type"
+ t.string "subscribable_type", limit: 255
t.boolean "subscribed"
t.datetime "created_at"
t.datetime "updated_at"
@@ -778,10 +774,10 @@ ActiveRecord::Schema.define(version: 20151229112614) do
create_table "taggings", force: :cascade do |t|
t.integer "tag_id"
t.integer "taggable_id"
- t.string "taggable_type"
+ t.string "taggable_type", limit: 255
t.integer "tagger_id"
- t.string "tagger_type"
- t.string "context"
+ t.string "tagger_type", limit: 255
+ t.string "context", limit: 255
t.datetime "created_at"
end
@@ -789,67 +785,67 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "taggings", ["taggable_id", "taggable_type", "context"], name: "index_taggings_on_taggable_id_and_taggable_type_and_context", using: :btree
create_table "tags", force: :cascade do |t|
- t.string "name"
- t.integer "taggings_count", default: 0
+ t.string "name", limit: 255
+ t.integer "taggings_count", default: 0
end
add_index "tags", ["name"], name: "index_tags_on_name", unique: true, using: :btree
create_table "users", force: :cascade do |t|
- t.string "email", default: "", null: false
- t.string "encrypted_password", default: "", null: false
- t.string "reset_password_token"
+ t.string "email", limit: 255, default: "", null: false
+ t.string "encrypted_password", limit: 255, default: "", null: false
+ t.string "reset_password_token", limit: 255
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
- t.integer "sign_in_count", default: 0
+ t.integer "sign_in_count", default: 0
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
- t.string "current_sign_in_ip"
- t.string "last_sign_in_ip"
- t.datetime "created_at"
- t.datetime "updated_at"
- t.string "name"
- t.boolean "admin", default: false, null: false
- t.integer "projects_limit", default: 10
- t.string "skype", default: "", null: false
- t.string "linkedin", default: "", null: false
- t.string "twitter", default: "", null: false
- t.string "authentication_token"
- t.integer "theme_id", default: 1, null: false
- t.string "bio"
- t.integer "failed_attempts", default: 0
+ t.string "current_sign_in_ip", limit: 255
+ t.string "last_sign_in_ip", limit: 255
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ t.string "name", limit: 255
+ t.boolean "admin", default: false, null: false
+ t.integer "projects_limit", default: 10
+ t.string "skype", limit: 255, default: "", null: false
+ t.string "linkedin", limit: 255, default: "", null: false
+ t.string "twitter", limit: 255, default: "", null: false
+ t.string "authentication_token", limit: 255
+ t.integer "theme_id", default: 1, null: false
+ t.string "bio", limit: 255
+ t.integer "failed_attempts", default: 0
t.datetime "locked_at"
- t.string "username"
- t.boolean "can_create_group", default: true, null: false
- t.boolean "can_create_team", default: true, null: false
- t.string "state"
- t.integer "color_scheme_id", default: 1, null: false
- t.integer "notification_level", default: 1, null: false
+ t.string "username", limit: 255
+ t.boolean "can_create_group", default: true, null: false
+ t.boolean "can_create_team", default: true, null: false
+ t.string "state", limit: 255
+ t.integer "color_scheme_id", default: 1, null: false
+ t.integer "notification_level", default: 1, null: false
t.datetime "password_expires_at"
t.integer "created_by_id"
t.datetime "last_credential_check_at"
- t.string "avatar"
- t.string "confirmation_token"
+ t.string "avatar", limit: 255
+ t.string "confirmation_token", limit: 255
t.datetime "confirmed_at"
t.datetime "confirmation_sent_at"
- t.string "unconfirmed_email"
- t.boolean "hide_no_ssh_key", default: false
- t.string "website_url", default: "", null: false
- t.string "notification_email"
- t.boolean "hide_no_password", default: false
- t.boolean "password_automatically_set", default: false
- t.string "location"
- t.string "encrypted_otp_secret"
- t.string "encrypted_otp_secret_iv"
- t.string "encrypted_otp_secret_salt"
- t.boolean "otp_required_for_login", default: false, null: false
+ t.string "unconfirmed_email", limit: 255
+ t.boolean "hide_no_ssh_key", default: false
+ t.string "website_url", limit: 255, default: "", null: false
+ t.string "notification_email", limit: 255
+ t.boolean "hide_no_password", default: false
+ t.boolean "password_automatically_set", default: false
+ t.string "location", limit: 255
+ t.string "encrypted_otp_secret", limit: 255
+ t.string "encrypted_otp_secret_iv", limit: 255
+ t.string "encrypted_otp_secret_salt", limit: 255
+ t.boolean "otp_required_for_login", default: false, null: false
t.text "otp_backup_codes"
- t.string "public_email", default: "", null: false
- t.integer "dashboard", default: 0
- t.integer "project_view", default: 0
+ t.string "public_email", limit: 255, default: "", null: false
+ t.integer "dashboard", default: 0
+ t.integer "project_view", default: 0
t.integer "consumed_timestep"
- t.integer "layout", default: 0
- t.boolean "hide_project_limit", default: false
+ t.integer "layout", default: 0
+ t.boolean "hide_project_limit", default: false
t.string "unlock_token"
t.datetime "otp_grace_period_started_at"
end
@@ -876,19 +872,19 @@ ActiveRecord::Schema.define(version: 20151229112614) do
add_index "users_star_projects", ["user_id"], name: "index_users_star_projects_on_user_id", using: :btree
create_table "web_hooks", force: :cascade do |t|
- t.string "url"
+ t.string "url", limit: 255
t.integer "project_id"
t.datetime "created_at"
t.datetime "updated_at"
- t.string "type", default: "ProjectHook"
+ t.string "type", limit: 255, default: "ProjectHook"
t.integer "service_id"
- t.boolean "push_events", default: true, null: false
- t.boolean "issues_events", default: false, null: false
- t.boolean "merge_requests_events", default: false, null: false
- t.boolean "tag_push_events", default: false
- t.boolean "note_events", default: false, null: false
- t.boolean "enable_ssl_verification", default: true
- t.boolean "build_events", default: false, null: false
+ t.boolean "push_events", default: true, null: false
+ t.boolean "issues_events", default: false, null: false
+ t.boolean "merge_requests_events", default: false, null: false
+ t.boolean "tag_push_events", default: false
+ t.boolean "note_events", default: false, null: false
+ t.boolean "enable_ssl_verification", default: true
+ t.boolean "build_events", default: false, null: false
end
add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree
diff --git a/doc/README.md b/doc/README.md
index 8a297f8267f..25fe3abcb9a 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -7,7 +7,7 @@
- [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab.
- [Importing to GitLab](workflow/importing/README.md).
- [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
-- [Migrating from SVN](migration/README.md) Convert a SVN repository to Git and GitLab
+- [Migrating from SVN](workflow/importing/migrating_from_svn.md) Convert a SVN repository to Git and GitLab
- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
- [Profile Settings](profile/README.md)
- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
@@ -19,6 +19,7 @@
## CI Documentation
- [Quick Start](ci/quick_start/README.md)
+- [Enable or disable GitLab CI](ci/enable_or_disable_ci.md)
- [Configuring project (.gitlab-ci.yml)](ci/yaml/README.md)
- [Configuring runner](ci/runners/README.md)
- [Configuring deployment](ci/deployment/README.md)
@@ -56,7 +57,7 @@
- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
- [Log system](logs/logs.md) Log system.
-- [Environmental Variables](administration/environment_variables.md) to configure GitLab.
+- [Environment Variables](administration/environment_variables.md) to configure GitLab.
- [Operations](operations/README.md) Keeping GitLab up and running
- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
diff --git a/doc/administration/enviroment_variables.md b/doc/administration/environment_variables.md
index 7d8f9d29eef..1eb3a74d304 100644
--- a/doc/administration/enviroment_variables.md
+++ b/doc/administration/environment_variables.md
@@ -42,7 +42,12 @@ GITLAB_DATABASE_PASSWORD |
GITLAB_DATABASE_HOST | localhost
GITLAB_DATABASE_PORT | 5432
-## Other variables
+## Adding more variables
We welcome merge requests to make more settings configurable via variables.
Please stick to the naming scheme "GITLAB_#{name 1_settings.rb in upper case}".
+
+## Omnibus configuration
+
+It's possible to preconfigure the GitLab image by adding the environment variable: `GITLAB_OMNIBUS_CONFIG` to docker run command.
+For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://doc.gitlab.com/omnibus/docker/#preconfigure-docker-container).
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 0ca81ffd49e..37d74216c1b 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -482,6 +482,34 @@ Parameters:
- `id` (required) - The ID of a project
+## Uploads
+
+### Upload a file
+
+Uploads a file to the specified project to be used in an issue or merge request description, or a comment.
+
+```
+POST /projects/:id/uploads
+```
+
+Parameters:
+
+- `id` (required) - The ID of the project
+- `file` (required) - The file to be uploaded
+
+```json
+{
+ "alt": "dk",
+ "url": "/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png",
+ "is_image": true,
+ "markdown": "![dk](/uploads/66dbcd21ec5d24ed6ea225176098d52b/dk.png)"
+}
+```
+
+**Note**: The returned `url` is relative to the project path.
+In Markdown contexts, the link is automatically expanded when the format in `markdown` is used.
+
+
## Team members
### List project team members
diff --git a/doc/api/tags.md b/doc/api/tags.md
index 085d387e824..17d12e9cc62 100644
--- a/doc/api/tags.md
+++ b/doc/api/tags.md
@@ -83,6 +83,26 @@ it will contain the annotation.
It returns 200 if the operation succeed. In case of an error,
405 with an explaining error message is returned.
+## Delete a tag
+
+Deletes a tag of a repository with given name. On success, this API method
+returns 200 with the name of the deleted tag. If the tag does not exist, the
+API returns 404.
+
+```
+DELETE /projects/:id/repository/tags/:tag_name
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `tag_name` (required) - The name of a tag
+
+```json
+{
+ "tag_name": "v4.3.0"
+}
+```
## Create a new release
diff --git a/doc/api/users.md b/doc/api/users.md
index 66d2fd52526..773fe36d277 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -123,6 +123,13 @@ Parameters:
"name": "John Smith",
"state": "active",
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
+ "created_at": "2012-05-23T08:00:58Z",
+ "is_admin": false,
+ "bio": null,
+ "skype": "",
+ "linkedin": "",
+ "twitter": "",
+ "website_url": ""
}
```
diff --git a/doc/ci/README.md b/doc/ci/README.md
index a1f5513d88e..4cdd2e1ad33 100644
--- a/doc/ci/README.md
+++ b/doc/ci/README.md
@@ -3,6 +3,7 @@
### User documentation
* [Quick Start](quick_start/README.md)
+* [Enable or disable GitLab CI](enable_or_disable_ci.md)
* [Configuring project (.gitlab-ci.yml)](yaml/README.md)
* [Configuring runner](runners/README.md)
* [Configuring deployment](deployment/README.md)
diff --git a/doc/ci/enable_or_disable_ci.md b/doc/ci/enable_or_disable_ci.md
new file mode 100644
index 00000000000..9bd2f5aff22
--- /dev/null
+++ b/doc/ci/enable_or_disable_ci.md
@@ -0,0 +1,70 @@
+## Enable or disable GitLab CI
+
+_To effectively use GitLab CI, you need a valid [`.gitlab-ci.yml`](yaml/README.md)
+file present at the root directory of your project and a
+[runner](runners/README.md) properly set up. You can read our
+[quick start guide](quick_start/README.md) to get you started._
+
+If you are using an external CI server like Jenkins or Drone CI, it is advised
+to disable GitLab CI in order to not have any conflicts with the commits status
+API.
+
+---
+
+As of GitLab 8.2, GitLab CI is mainly exposed via the `/builds` page of a
+project. Disabling GitLab CI in a project does not delete any previous builds.
+In fact, the `/builds` page can still be accessed, although it's hidden from
+the left sidebar menu.
+
+GitLab CI is enabled by default on new installations and can be disabled either
+individually under each project's settings, or site-wide by modifying the
+settings in `gitlab.yml` and `gitlab.rb` for source and Omnibus installations
+respectively.
+
+### Per-project user setting
+
+The setting to enable or disable GitLab CI can be found with the name **Builds**
+under the **Features** area of a project's settings along with **Issues**,
+**Merge Requests**, **Wiki** and **Snippets**. Select or deselect the checkbox
+and hit **Save** for the settings to take effect.
+
+![Features settings](img/features_settings.png)
+
+---
+
+### Site-wide administrator setting
+
+You can disable GitLab CI site-wide, by modifying the settings in `gitlab.yml`
+and `gitlab.rb` for source and Omnibus installations respectively.
+
+Two things to note:
+
+1. Disabling GitLab CI, will affect only newly-created projects. Projects that
+ had it enabled prior to this modification, will work as before.
+1. Even if you disable GitLab CI, users will still be able to enable it in the
+ project's settings.
+
+---
+
+For installations from source, open `gitlab.yml` with your editor and set
+`builds` to `false`:
+
+```yaml
+## Default project features settings
+default_projects_features:
+ issues: true
+ merge_requests: true
+ wiki: true
+ snippets: false
+ builds: false
+```
+
+Save the file and restart GitLab: `sudo service gitlab restart`.
+
+For Omnibus installations, edit `/etc/gitlab/gitlab.rb` and add the line:
+
+```
+gitlab-rails['gitlab_default_projects_features_builds'] = false
+```
+
+Save the file and reconfigure GitLab: `sudo gitlab-ctl reconfigure`.
diff --git a/doc/ci/img/features_settings.png b/doc/ci/img/features_settings.png
new file mode 100644
index 00000000000..17aba5d14d8
--- /dev/null
+++ b/doc/ci/img/features_settings.png
Binary files differ
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index 68dcfe23ffb..295d953db11 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -62,8 +62,9 @@ Now simply register the runner as any runner:
sudo gitlab-runner register
```
-Note that you will have to enable `Allows shared runners` for each project
-that you want to make use of a shared runner. This is by default `off`.
+Shared runners are enabled by default as of GitLab 8.2, but can be disabled with the
+`DISABLE SHARED RUNNERS` button. Previous versions of GitLab defaulted shared runners to
+disabled.
## Registering a Specific Runner
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 81edd8da2b8..dd59733cf4d 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -552,6 +552,6 @@ Apart from the always supported markdown style there are other rich text files t
If you see this message when attempting to clone a repository hosted by GitLab,
this is likely due to an outdated Nginx or Apache configuration, or a missing or
-misconfigured `gitlab-git-http-server` instance. Double-check that you've
-[installed Go](#3-go), [installed gitlab-git-http-server](#install-gitlab-git-http-server),
+misconfigured gitlab-workhorse instance. Double-check that you've
+[installed Go](#3-go), [installed gitlab-workhorse](#install-gitlab-workhorse),
and correctly [configured Nginx](#site-configuration).
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 2a9f76533b7..5edac746c7b 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -1,6 +1,7 @@
# GitLab Integration
-GitLab integrates with multiple third-party services to allow external issue trackers and external authentication.
+GitLab integrates with multiple third-party services to allow external issue
+trackers and external authentication.
See the documentation below for details on how to configure these services.
@@ -15,13 +16,25 @@ See the documentation below for details on how to configure these services.
- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
- [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users
-GitLab Enterprise Edition contains [advanced JIRA support](http://doc.gitlab.com/ee/integration/jira.html) and [advanced Jenkins support](http://doc.gitlab.com/ee/integration/jenkins.html).
+GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
## Project services
-Integration with services such as Campfire, Flowdock, Gemnasium, HipChat, Pivotal Tracker, and Slack are available in the form of a Project Service.
-You can find these within GitLab in the Services page under Project Settings if you are at least a master on the project.
-Project Services are a bit like plugins in that they allow a lot of freedom in adding functionality to GitLab, for example there is also a service that can send an email every time someone pushes new commits.
-Because GitLab is open source we can ship with the code and tests for all plugins.
-This allows the community to keep the plugins up to date so that they always work in newer GitLab versions.
-For an overview of what projects services are available without logging in please see the [project_services directory](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/models/project_services).
+Integration with services such as Campfire, Flowdock, Gemnasium, HipChat,
+Pivotal Tracker, and Slack are available in the form of a [Project Service][].
+You can find these within GitLab in the Services page under Project Settings if
+you are at least a master on the project.
+Project Services are a bit like plugins in that they allow a lot of freedom in
+adding functionality to GitLab. For example there is also a service that can
+send an email every time someone pushes new commits.
+
+Because GitLab is open source we can ship with the code and tests for all
+plugins. This allows the community to keep the plugins up to date so that they
+always work in newer GitLab versions.
+
+For an overview of what projects services are available without logging in,
+please see the [project_services directory][projects-code].
+
+[jenkins]: http://doc.gitlab.com/ee/integration/jenkins.html
+[Project Service]: ../project_services/project_services.md
+[projects-code]: https://gitlab.com/gitlab-org/gitlab-ce/tree/master/app/models/project_services
diff --git a/doc/integration/azure.md b/doc/integration/azure.md
new file mode 100644
index 00000000000..48dddf7df44
--- /dev/null
+++ b/doc/integration/azure.md
@@ -0,0 +1,83 @@
+# Microsoft Azure OAuth2 OmniAuth Provider
+
+To enable the Microsoft Azure OAuth2 OmniAuth provider you must register your application with Azure. Azure will generate a client ID and secret key for you to use.
+
+1. Sign in to the [Azure Management Portal](https://manage.windowsazure.com>).
+
+1. Select "Active Directory" on the left and choose the directory you want to use to register GitLab.
+
+1. Select "Applications" at the top bar and click the "Add" button the bottom.
+
+1. Select "Add an application my organization is developing".
+
+1. Provide the project information and click the "Next" button.
+ - Name: 'GitLab' works just fine here.
+ - Type: 'WEB APPLICATION AND/OR WEB API'
+
+1. On the "App properties" page enter the needed URI's and click the "Complete" button.
+ - SIGN-IN URL: Enter the URL of your GitLab installation (e.g 'https://gitlab.mycompany.com/')
+ - APP ID URI: Enter the endpoint URL for Microsoft to use, just has to be unique (e.g 'https://mycompany.onmicrosoft.com/gitlab')
+
+1. Select "Configure" in the top menu.
+
+1. Add a "Reply URL" pointing to the Azure OAuth callback of your GitLab installation (e.g. https://gitlab.mycompany.com/users/auth/azure_oauth2/callback).
+
+1. Create a "Client secret" by selecting a duration, the secret will be generated as soon as you click the "Save" button in the bottom menu..
+
+1. Note the "CLIENT ID" and the "CLIENT SECRET".
+
+1. Select "View endpoints" from the bottom menu.
+
+1. You will see lots of endpoint URLs in the form 'https://login.microsoftonline.com/TENANT ID/...', note down the TENANT ID part of one of those endpoints.
+
+1. On your GitLab server, open the configuration file.
+
+ For omnibus package:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For installations 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" => "azure_oauth2",
+ "args" => {
+ "client_id" => "CLIENT ID",
+ "client_secret" => "CLIENT SECRET",
+ "tenant_id" => "TENANT ID",
+ }
+ }
+ ]
+ ```
+
+ For installations from source:
+
+ ```
+ - { name: 'azure_oauth2',
+ args: { client_id: "CLIENT ID",
+ client_secret: "CLIENT SECRET",
+ tenant_id: "TENANT ID" } }
+ ```
+
+1. Replace 'CLIENT ID', 'CLIENT SECRET' and 'TENANT ID' with the values you got above.
+
+1. Save the configuration file.
+
+1. Restart GitLab for the changes to take effect.
+
+On the sign in page there should now be a Microsoft icon below the regular sign in form. Click the icon to begin the authentication process. Microsoft will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in.
diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md
index 3e660cfba1e..3543a67dd49 100644
--- a/doc/integration/external-issue-tracker.md
+++ b/doc/integration/external-issue-tracker.md
@@ -1,44 +1,30 @@
# External issue tracker
-GitLab has a great issue tracker but you can also use an external issue tracker such as Jira, Bugzilla or Redmine. You can configure issue trackers per GitLab project. For instance, if you configure Jira it allows you to do the following:
+GitLab has a great issue tracker but you can also use an external one such as
+Jira or Redmine. Issue trackers are configurable per GitLab project and allow
+you to do the following:
-- the 'Issues' link on the GitLab project pages takes you to the appropriate Jira issue index;
-- clicking 'New issue' on the project dashboard creates a new Jira issue;
-- To reference Jira issue PROJECT-1234 in comments, use syntax PROJECT-1234. Commit messages get turned into HTML links to the corresponding Jira issue.
-
-![Jira screenshot](jira-integration-points.png)
-
-GitLab Enterprise Edition contains [advanced JIRA support](http://doc.gitlab.com/ee/integration/jira.html).
+- the **Issues** link on the GitLab project pages takes you to the appropriate
+ issue index of the external tracker
+- clicking **New issue** on the project dashboard creates a new issue on the
+ external tracker
## Configuration
-### Project Service
+The configuration is done via a project's **Services**.
-You can enable an external issue tracker per project. As an example, we will configure `Redmine` for project named gitlab-ci.
-
-Fill in the required details on the page:
+### Project Service
-![redmine configuration](redmine_configuration.png)
+To enable an external issue tracker you must configure the appropriate **Service**.
+Visit the links below for details:
-* `description` A name for the issue tracker (to differentiate between instances, for example).
-* `project_url` The URL to the project in Redmine which is being linked to this GitLab project.
-* `issues_url` The URL to the issue in Redmine project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the url. This id is used by GitLab as a placeholder to replace the issue number.
-* `new_issue_url` This is the URL to create a new issue in Redmine for the project linked to this GitLab project.
+- [Redmine](../project_services/redmine.md)
+- [Jira](jira.md)
### Service Template
-It is necessary to configure the external issue tracker per project, because project specific details are needed for the integration with GitLab.
-The admin can add a service template that sets a default for each project. This makes it much easier to configure individual projects.
-
-In GitLab Admin section, navigate to `Service Templates` and choose the service template you want to create:
-
-![redmine service template](redmine_service_template.png)
-
-After the template is created, the template details will be pre-filled on the project service page.
-
-NOTE: For each project, you will still need to configure the issue tracking URLs by replacing `:issues_tracker_id` in the above screenshot
-with the ID used by your external issue tracker. Prior to GitLab v7.8, this ID was configured in the project settings, and GitLab would automatically
-update the URL configured in `gitlab.yml`. This behavior is now depecated, and all issue tracker URLs must be configured directly
-within the project's Services settings.
+To save you the hassle from configuring each project's service individually,
+GitLab provides the ability to set Service Templates which can then be
+overridden in each project's settings.
-Support to add your commits to the Jira ticket automatically is [available in GitLab EE](http://doc.gitlab.com/ee/integration/jira.html).
+Read more on [Services Templates](../project_services/services_templates.md).
diff --git a/doc/integration/jira_issue_reference.png b/doc/integration/img/jira_issue_reference.png
index 15739a22dc7..15739a22dc7 100644
--- a/doc/integration/jira_issue_reference.png
+++ b/doc/integration/img/jira_issue_reference.png
Binary files differ
diff --git a/doc/integration/img/jira_merge_request_close.png b/doc/integration/img/jira_merge_request_close.png
new file mode 100644
index 00000000000..1e78daf105f
--- /dev/null
+++ b/doc/integration/img/jira_merge_request_close.png
Binary files differ
diff --git a/doc/integration/jira_project_name.png b/doc/integration/img/jira_project_name.png
index 5986fdb63fb..5986fdb63fb 100644
--- a/doc/integration/jira_project_name.png
+++ b/doc/integration/img/jira_project_name.png
Binary files differ
diff --git a/doc/integration/jira_service.png b/doc/integration/img/jira_service.png
index 1f6628c4371..1f6628c4371 100644
--- a/doc/integration/jira_service.png
+++ b/doc/integration/img/jira_service.png
Binary files differ
diff --git a/doc/integration/jira_service_close_issue.png b/doc/integration/img/jira_service_close_issue.png
index 67dfc6144c4..67dfc6144c4 100644
--- a/doc/integration/jira_service_close_issue.png
+++ b/doc/integration/img/jira_service_close_issue.png
Binary files differ
diff --git a/doc/integration/img/jira_service_page.png b/doc/integration/img/jira_service_page.png
new file mode 100644
index 00000000000..2b37eda3520
--- /dev/null
+++ b/doc/integration/img/jira_service_page.png
Binary files differ
diff --git a/doc/integration/jira_workflow_screenshot.png b/doc/integration/img/jira_workflow_screenshot.png
index 8635a32eb68..8635a32eb68 100644
--- a/doc/integration/jira_workflow_screenshot.png
+++ b/doc/integration/img/jira_workflow_screenshot.png
Binary files differ
diff --git a/doc/integration/jira.md b/doc/integration/jira.md
index 624601d0fac..de574d53410 100644
--- a/doc/integration/jira.md
+++ b/doc/integration/jira.md
@@ -1,14 +1,15 @@
# GitLab Jira integration
-GitLab can be configured to interact with Jira.
-Configuration happens via username and password.
-Connecting to a Jira server via CAS is not possible.
+GitLab can be configured to interact with Jira. Configuration happens via
+username and password. Connecting to a Jira server via CAS is not possible.
-Each project can be configured to connect to a different Jira instance, configuration is explained [here](#configuration).
-If you have one Jira instance you can pre-fill the settings page with a default template. To configure the template [see external issue tracker document](external-issue-tracker.md#service-template)).
-
-Once the project is connected to Jira, you can reference and close the issues in Jira directly from GitLab.
+Each project can be configured to connect to a different Jira instance, see the
+[configuration](#configuration) section. If you have one Jira instance you can
+pre-fill the settings page with a default template. To configure the template
+see the [Services Templates][services-templates] document.
+Once the project is connected to Jira, you can reference and close the issues
+in Jira directly from GitLab.
## Table of Contents
@@ -18,8 +19,11 @@ Once the project is connected to Jira, you can reference and close the issues in
### Referencing Jira Issues
-When GitLab project has Jira issue tracker configured and enabled, mentioning Jira issue in GitLab will automatically add a comment in Jira issue with the link back to GitLab. This means that in comments in merge requests and commits referencing an issue, eg. `PROJECT-7`, will add a comment in Jira issue in the format:
-
+When GitLab project has Jira issue tracker configured and enabled, mentioning
+Jira issue in GitLab will automatically add a comment in Jira issue with the
+link back to GitLab. This means that in comments in merge requests and commits
+referencing an issue, eg. `PROJECT-7`, will add a comment in Jira issue in the
+format:
```
USER mentioned this issue in LINK_TO_THE_MENTION
@@ -29,85 +33,117 @@ When GitLab project has Jira issue tracker configured and enabled, mentioning Ji
* `LINK_TO_THE_MENTION` Link to the origin of mention with a name of the entity where Jira issue was mentioned.
Can be commit or merge request.
+![example of mentioning or closing the Jira issue](img/jira_issue_reference.png)
-![example of mentioning or closing the Jira issue](jira_issue_reference.png)
-
+---
### Closing Jira Issues
-Jira issues can be closed directly from GitLab by using trigger words, eg. `Resolves PROJECT-1`, `Closes PROJECT-1` or `Fixes PROJECT-1`, in commits and merge requests.
-When a commit which contains the trigger word in the commit message is pushed, GitLab will add a comment in the mentioned Jira issue.
+Jira issues can be closed directly from GitLab by using trigger words, eg.
+`Resolves PROJECT-1`, `Closes PROJECT-1` or `Fixes PROJECT-1`, in commits and
+merge requests. When a commit which contains the trigger word in the commit
+message is pushed, GitLab will add a comment in the mentioned Jira issue.
-For example, for project named PROJECT in Jira, we implemented a new feature and created a merge request in GitLab.
+For example, for project named `PROJECT` in Jira, we implemented a new feature
+and created a merge request in GitLab.
-This feature was requested in Jira issue PROJECT-7. Merge request in GitLab contains the improvement and in merge request description we say that this merge request `Closes PROJECT-7` issue.
+This feature was requested in Jira issue `PROJECT-7`. Merge request in GitLab
+contains the improvement and in merge request description we say that this
+merge request `Closes PROJECT-7` issue.
-Once this merge request is merged, Jira issue will be automatically closed with a link to the commit that resolved the issue.
+Once this merge request is merged, the Jira issue will be automatically closed
+with a link to the commit that resolved the issue.
-![A Git commit that causes the Jira issue to be closed](merge_request_close_jira.png)
+![A Git commit that causes the Jira issue to be closed](img/jira_merge_request_close.png)
+---
-![The GitLab integration user leaves a comment on Jira](jira_service_close_issue.png)
+![The GitLab integration user leaves a comment on Jira](img/jira_service_close_issue.png)
+---
## Configuration
### Configuring JIRA
-We need to create a user in JIRA which will have access to all projects that need to integrate with GitLab.
-Login to your JIRA instance as admin and under Administration go to User Management and create a new user.
-As an example, we'll create a user named `gitlab` and add it to `jira-developers` group.
+We need to create a user in JIRA which will have access to all projects that
+need to integrate with GitLab. Login to your JIRA instance as admin and under
+Administration go to User Management and create a new user.
+
+As an example, we'll create a user named `gitlab` and add it to `jira-developers`
+group.
**It is important that the user `gitlab` has write-access to projects in JIRA**
### Configuring GitLab
-### GitLab 7.8 EE and up with JIRA v6.x
+JIRA configuration in GitLab is done via a project's **Services**.
-To enable JIRA integration in a project, navigate to the project Settings page and go to Services. Here you will find JIRA.
+#### GitLab 7.8 and up with JIRA v6.x
-Fill in the required details on the page:
+See next section.
-![Jira service page](jira_service_page.png)
+#### GitLab 7.8 and up
-* `description` A name for the issue tracker (to differentiate between instances, for instance).
-* `project url` The URL to the JIRA project which is being linked to this GitLab project.
-* `issues url` The URL to the JIRA project issues overview for the project that is linked to this GitLab project.
-* `new issue url` This is the URL to create a new issue in JIRA for the project linked to this GitLab project.
-* `api url` The base URL of the JIRA API. It may be omitted, in which case GitLab will automatically use API version `2` based on the `project url`, i.e. `https://jira.example.com/rest/api/2`.
-* `username` The username of the user created in [configuring JIRA step](#configuring-jira).
-* `password` The password of the user created in [configuring JIRA step](#configuring-jira).
-* `Jira issue transition` This is the id of a transition that moves issues to a closed state. You can find this number under [JIRA workflow administration, see screenshot](jira_workflow_screenshot.png). By default, this id is `2`. (In the example image, this is `2` as well)
+_The currently supported JIRA versions are v6.x and v7.x._
-After saving the configuration, your GitLab project will be able to interact with the linked JIRA project.
+To enable JIRA integration in a project, navigate to the project's
+**Settings > Services > JIRA**.
+Fill in the required details on the page as described in the table below.
-### GitLab 6.x-7.7 with JIRA v6.x
+| Field | Description |
+| ----- | ----------- |
+| `description` | A name for the issue tracker (to differentiate between instances, for instance). |
+| `project url` | The URL to the JIRA project which is being linked to this GitLab project. |
+| `issues url` | The URL to the JIRA project issues overview for the project that is linked to this GitLab project. |
+| `new issue url` | This is the URL to create a new issue in JIRA for the project linked to this GitLab project. |
+| `api url` | The base URL of the JIRA API. It may be omitted, in which case GitLab will automatically use API version `2` based on the `project url`, i.e. `https://jira.example.com/rest/api/2`. |
+| `username` | The username of the user created in [configuring JIRA step](#configuring-jira). |
+| `password` |The password of the user created in [configuring JIRA step](#configuring-jira). |
+| `Jira issue transition` | This is the ID of a transition that moves issues to a closed state. You can find this number under JIRA workflow administration ([see screenshot](img/jira_workflow_screenshot.png)). By default, this ID is `2` (in the example image, this is `2` as well) |
-**Note: GitLab 7.8 and up contain various integration improvements. We strongly recommend upgrading.**
+After saving the configuration, your GitLab project will be able to interact
+with the linked JIRA project.
+![Jira service page](img/jira_service_page.png)
-In `gitlab.yml` enable [JIRA issue tracker section by uncommenting the lines](https://gitlab.com/subscribers/gitlab-ee/blob/6-8-stable-ee/config/gitlab.yml.example#L111-115).
-This will make sure that all issues within GitLab are pointing to the JIRA issue tracker.
+---
-We can also enable JIRA service that will allow us to interact with JIRA issues.
+#### GitLab 6.x-7.7 with JIRA v6.x
-For example, we can close issues in JIRA by a commit in GitLab.
+_**Note:** GitLab versions 7.8 and up contain various integration improvements.
+We strongly recommend upgrading._
-Go to project settings page and fill in the project name for the JIRA project:
+In `gitlab.yml` enable the JIRA issue tracker section by
+[uncommenting these lines][jira-gitlab-yml]. This will make sure that all
+issues within GitLab are pointing to the JIRA issue tracker.
-![Set the JIRA project name in GitLab to 'NEW'](jira_project_name.png)
+After you set this, you will be able to close issues in JIRA by a commit in
+GitLab.
-Next, go to the services page and find JIRA.
+Go to your project's **Settings** page and fill in the project name for the
+JIRA project:
-![Jira services page](jira_service.png)
+![Set the JIRA project name in GitLab to 'NEW'](img/jira_project_name.png)
-1. Tick the active check box to enable the service.
-1. Supply the url to JIRA server, for example http://jira.sample
-1. Supply the username of a user we created under `Configuring JIRA` section, for example `gitlab`
+---
+
+You can also enable the JIRA service that will allow you to interact with JIRA
+issues. Go to the **Settings > Services > JIRA** and:
+
+1. Tick the active check box to enable the service
+1. Supply the URL to JIRA server, for example http://jira.example.com
+1. Supply the username of a user we created under `Configuring JIRA` section,
+ for example `gitlab`
1. Supply the password of the user
-1. Optional: supply the JIRA api version, default is version
-1. Optional: supply the JIRA issue transition ID (issue transition to closed). This is dependant on JIRA settings, default is 2
-1. Save
+1. Optional: supply the JIRA API version, default is version `2`
+1. Optional: supply the JIRA issue transition ID (issue transition to closed).
+ This is dependent on JIRA settings, default is `2`
+1. Hit save
+
+
+![Jira services page](img/jira_service.png)
-Now we should be able to interact with JIRA issues.
+[services-templates]: ../project_services/services_templates.md
+[jira-gitlab-yml]: https://gitlab.com/subscribers/gitlab-ee/blob/6-8-stable-ee/config/gitlab.yml.example#L111-115
diff --git a/doc/integration/jira_service_page.png b/doc/integration/jira_service_page.png
deleted file mode 100644
index 69ec44e826f..00000000000
--- a/doc/integration/jira_service_page.png
+++ /dev/null
Binary files differ
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index f2b1721fc03..e9e17eb4165 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -78,6 +78,7 @@ Now we can choose one or more of the Supported Providers below to continue confi
- [Shibboleth](shibboleth.md)
- [SAML](saml.md)
- [Crowd](crowd.md)
+- [Azure](azure.md)
## Enable OmniAuth for an Existing User
diff --git a/doc/integration/redmine_configuration.png b/doc/integration/redmine_configuration.png
deleted file mode 100644
index 6b145363229..00000000000
--- a/doc/integration/redmine_configuration.png
+++ /dev/null
Binary files differ
diff --git a/doc/integration/redmine_service_template.png b/doc/integration/redmine_service_template.png
deleted file mode 100644
index 1159eb5b964..00000000000
--- a/doc/integration/redmine_service_template.png
+++ /dev/null
Binary files differ
diff --git a/doc/project_services/img/redmine_configuration.png b/doc/project_services/img/redmine_configuration.png
new file mode 100644
index 00000000000..d14e526ad33
--- /dev/null
+++ b/doc/project_services/img/redmine_configuration.png
Binary files differ
diff --git a/doc/project_services/img/services_templates_redmine_example.png b/doc/project_services/img/services_templates_redmine_example.png
new file mode 100644
index 00000000000..384d057fc8e
--- /dev/null
+++ b/doc/project_services/img/services_templates_redmine_example.png
Binary files differ
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
index 03937d20728..e3403127723 100644
--- a/doc/project_services/project_services.md
+++ b/doc/project_services/project_services.md
@@ -1,20 +1,37 @@
# Project Services
-
-__Project integrations with external services for continuous integration and more.__
+
+Project services allow you to integrate GitLab with other applications. Below
+is list of the currently supported ones. Click on the service links to see
+further configuration instructions and details. Contributions are welcome.
## Services
-- Assembla
-- [Atlassian Bamboo CI](bamboo.md) An Atlassian product for continuous integration.
-- Build box
-- Campfire
-- Emails on push
-- Flowdock
-- Gemnasium
-- GitLab CI
-- [HipChat](hipchat.md) An Atlassian product for private group chat and instant messaging.
-- [Irker](irker.md) An IRC gateway to receive messages on repository updates.
-- Pivotal Tracker
-- Pushover
-- Slack
-- TeamCity
+| Service | Description |
+| ------- | ----------- |
+| Asana | Asana - Teamwork without email |
+| Assembla | Project Management Software (Source Commits Endpoint) |
+| [Atlassian Bamboo CI](bamboo.md) | A continuous integration and build server |
+| Buildkite | Continuous integration and deployments |
+| Builds emails | Email the builds status to a list of recipients |
+| Campfire | Simple web-based real-time group chat |
+| Custom Issue Tracker | Custom issue tracker |
+| Drone CI | Continuous Integration platform built on Docker, written in Go |
+| Emails on push | Email the commits and diff of each push to a list of recipients |
+| External Wiki | Replaces the link to the internal wiki with a link to an external wiki |
+| Flowdock | Flowdock is a collaboration web app for technical teams |
+| Gemnasium | Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities |
+| [HipChat](hipchat.md) | Private group chat and IM |
+| [Irker (IRC gateway)](irker.md) | Send IRC messages, on update, to a list of recipients through an Irker gateway |
+| JIRA | Jira issue tracker |
+| JetBrains TeamCity CI | A continuous integration and build server |
+| PivotalTracker | Project Management Software (Source Commits Endpoint) |
+| Pushover | Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop |
+| [Redmine](redmine.md) | Redmine issue tracker |
+| Slack | A team communication tool for the 21st century |
+
+## Services Templates
+
+Services templates is a way to set some predefined values in the Service of
+your liking which will then be pre-filled on each project's Service.
+
+Read more about [Services Templates in this document](services_templates.md).
diff --git a/doc/project_services/redmine.md b/doc/project_services/redmine.md
new file mode 100644
index 00000000000..b9830ea7c38
--- /dev/null
+++ b/doc/project_services/redmine.md
@@ -0,0 +1,21 @@
+# Redmine Service
+
+Go to your project's **Settings > Services > Redmine** and fill in the required
+details as described in the table below.
+
+| Field | Description |
+| ----- | ----------- |
+| `description` | A name for the issue tracker (to differentiate between instances, for example) |
+| `project_url` | The URL to the project in Redmine which is being linked to this GitLab project |
+| `issues_url` | The URL to the issue in Redmine project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the URL. This ID is used by GitLab as a placeholder to replace the issue number. |
+| `new_issue_url` | This is the URL to create a new issue in Redmine for the project linked to this GitLab project |
+
+Once you have configured and enabled Redmine:
+
+- the **Issues** link on the GitLab project pages takes you to the appropriate
+ Redmine issue index
+- clicking **New issue** on the project dashboard creates a new Redmine issue
+
+As an example, below is a configuration for a project named gitlab-ci.
+
+![Redmine configuration](img/redmine_configuration.png)
diff --git a/doc/project_services/services_templates.md b/doc/project_services/services_templates.md
new file mode 100644
index 00000000000..be6d13b6d2b
--- /dev/null
+++ b/doc/project_services/services_templates.md
@@ -0,0 +1,25 @@
+# Services Templates
+
+A GitLab administrator can add a service template that sets a default for each
+project. This makes it much easier to configure individual projects.
+
+After the template is created, the template details will be pre-filled on a
+project's Service page.
+
+## Enable a Service template
+
+In GitLab's Admin area, navigate to **Service Templates** and choose the
+service template you wish to create.
+
+For example, in the image below you can see Redmine.
+
+![Redmine service template](img/services_templates_redmine_example.png)
+
+---
+
+**NOTE:** For each project, you will still need to configure the issue tracking
+URLs by replacing `:issues_tracker_id` in the above screenshot with the ID used
+by your external issue tracker. Prior to GitLab v7.8, this ID was configured in
+the project settings, and GitLab would automatically update the URL configured
+in `gitlab.yml`. This behavior is now deprecated and all issue tracker URLs
+must be configured directly within the project's **Services** settings.
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 093450a6de3..cdd6652b7b0 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -153,6 +153,49 @@ with the name of your bucket:
}
```
+### Uploading to locally mounted shares
+
+You may also send backups to a mounted share (`NFS` / `CIFS` / `SMB` / etc.) by
+using the [`Local`](https://github.com/fog/fog-local#usage) storage provider.
+The directory pointed to by the `local_root` key **must** be owned by the `git`
+user **when mounted** (mounting with the `uid=` of the `git` user for `CIFS` and
+`SMB`) or the user that you are executing the backup tasks under (for omnibus
+packages, this is the `git` user).
+
+The `backup_upload_remote_directory` **must** be set in addition to the
+`local_root` key. This is the sub directory inside the mounted directory that
+backups will be copied to, and will be created if it does not exist. If the
+directory that you want to copy the tarballs to is the root of your mounted
+directory, just use `.` instead.
+
+For omnibus packages:
+
+```ruby
+gitlab_rails['backup_upload_connection'] = {
+ :provider => 'Local',
+ :local_root => '/mnt/backups'
+}
+
+# The directory inside the mounted folder to copy backups to
+# Use '.' to store them in the root directory
+gitlab_rails['backup_upload_remote_directory'] = 'gitlab_backups'
+```
+
+For installations from source:
+
+```yaml
+ backup:
+ # snip
+ upload:
+ # Fog storage connection settings, see http://fog.io/storage/ .
+ connection:
+ provider: Local
+ local_root: '/mnt/backups'
+ # The directory inside the mounted folder to copy backups to
+ # Use '.' to store them in the root directory
+ remote_directory: 'gitlab_backups'
+```
+
## Backup archive permissions
The backup archives created by GitLab (123456_gitlab_backup.tar) will have owner/group git:git and 0600 permissions by default.
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index fe5b45dd432..77eb53427e2 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -9,7 +9,7 @@ already has one by running the following command:
cat ~/.ssh/id_rsa.pub
```
-If you see a long string starting with `ssh-rsa` or `ssh-dsa`, you can skip the `ssh-keygen` step.
+If you see a long string starting with `ssh-rsa`, you can skip the `ssh-keygen` step.
Note: It is a best practice to use a password for an SSH key, but it is not
required and you can skip creating a password by pressing enter. Note that
@@ -20,8 +20,9 @@ To generate a new SSH key, use the following command:
ssh-keygen -t rsa -C "$your_email"
```
This command will prompt you for a location and filename to store the key
-pair and for a password. When prompted for the location and filename, you
-can press enter to use the default.
+pair and for a password. When prompted for the location and filename, just
+press enter to use the default. If you use a different name, the key will not
+be used automatically.
Use the command below to show your public key:
```bash
@@ -29,10 +30,10 @@ cat ~/.ssh/id_rsa.pub
```
Copy-paste the key to the 'My SSH Keys' section under the 'SSH' tab in your
-user profile. Please copy the complete key starting with `ssh-` and ending
+user profile. Please copy the complete key starting with `ssh-rsa` and ending
with your username and host.
-To copy your public key to the clipboard, use code below. Depending on your
+To copy your public key to the clipboard, use the code below. Depending on your
OS you'll need to use a different command:
**Windows:**
diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md
index 5cb05b13b3e..49f98ded046 100644
--- a/doc/system_hooks/system_hooks.md
+++ b/doc/system_hooks/system_hooks.md
@@ -1,6 +1,6 @@
# System hooks
-Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`.
+Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `project_rename`, `project_transfer`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`.
System hooks can be used, e.g. for logging or changing information in a LDAP server.
@@ -17,6 +17,7 @@ X-Gitlab-Event: System Hook
```json
{
"created_at": "2012-07-21T07:30:54Z",
+ "updated_at": "2012-07-21T07:38:22Z",
"event_name": "project_create",
"name": "StoreCloud",
"owner_email": "johnsmith@gmail.com",
@@ -33,6 +34,7 @@ X-Gitlab-Event: System Hook
```json
{
"created_at": "2012-07-21T07:30:58Z",
+ "updated_at": "2012-07-21T07:38:22Z",
"event_name": "project_destroy",
"name": "Underscore",
"owner_email": "johnsmith@gmail.com",
@@ -44,11 +46,48 @@ X-Gitlab-Event: System Hook
}
```
+**Project renamed:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:58Z",
+ "updated_at": "2012-07-21T07:38:22Z",
+ "event_name": "project_rename",
+ "name": "Underscore",
+ "path": "underscore",
+ "path_with_namespace": "jsmith/underscore",
+ "project_id": 73,
+ "owner_name": "John Smith",
+ "owner_email": "johnsmith@gmail.com",
+ "project_visibility": "internal",
+ "old_path_with_namespace": "jsmith/overscore",
+}
+```
+
+**Project transferred:**
+
+```json
+{
+ "created_at": "2012-07-21T07:30:58Z",
+ "updated_at": "2012-07-21T07:38:22Z",
+ "event_name": "project_transfer",
+ "name": "Underscore",
+ "path": "underscore",
+ "path_with_namespace": "scores/underscore",
+ "project_id": 73,
+ "owner_name": "John Smith",
+ "owner_email": "johnsmith@gmail.com",
+ "project_visibility": "internal",
+ "old_path_with_namespace": "jsmith/overscore",
+}
+```
+
**New Team Member:**
```json
{
"created_at": "2012-07-21T07:30:56Z",
+ "updated_at": "2012-07-21T07:38:22Z",
"event_name": "user_add_to_team",
"project_access": "Master",
"project_id": 74,
@@ -67,6 +106,7 @@ X-Gitlab-Event: System Hook
```json
{
"created_at": "2012-07-21T07:30:56Z",
+ "updated_at": "2012-07-21T07:38:22Z",
"event_name": "user_remove_from_team",
"project_access": "Master",
"project_id": 74,
@@ -85,6 +125,7 @@ X-Gitlab-Event: System Hook
```json
{
"created_at": "2012-07-21T07:44:07Z",
+ "updated_at": "2012-07-21T07:38:22Z",
"email": "js@gitlabhq.com",
"event_name": "user_create",
"name": "John Smith",
@@ -97,6 +138,7 @@ X-Gitlab-Event: System Hook
```json
{
"created_at": "2012-07-21T07:44:07Z",
+ "updated_at": "2012-07-21T07:38:22Z",
"email": "js@gitlabhq.com",
"event_name": "user_destroy",
"name": "John Smith",
@@ -110,6 +152,7 @@ X-Gitlab-Event: System Hook
{
"event_name": "key_create",
"created_at": "2014-08-18 18:45:16 UTC",
+ "updated_at": "2012-07-21T07:38:22Z",
"username": "root",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC58FwqHUbebw2SdT7SP4FxZ0w+lAO/erhy2ylhlcW/tZ3GY3mBu9VeeiSGoGz8hCx80Zrz+aQv28xfFfKlC8XQFpCWwsnWnQqO2Lv9bS8V1fIHgMxOHIt5Vs+9CAWGCCvUOAurjsUDoE2ALIXLDMKnJxcxD13XjWdK54j6ZXDB4syLF0C2PnAQSVY9X7MfCYwtuFmhQhKaBussAXpaVMRHltie3UYSBUUuZaB3J4cg/7TxlmxcNd+ppPRIpSZAB0NI6aOnqoBCpimscO/VpQRJMVLr3XiSYeT6HBiDXWHnIVPfQc03OGcaFqOit6p8lYKMaP/iUQLm+pgpZqrXZ9vB john@localhost",
"id": 4
@@ -122,6 +165,7 @@ X-Gitlab-Event: System Hook
{
"event_name": "key_destroy",
"created_at": "2014-08-18 18:45:16 UTC",
+ "updated_at": "2012-07-21T07:38:22Z",
"username": "root",
"key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC58FwqHUbebw2SdT7SP4FxZ0w+lAO/erhy2ylhlcW/tZ3GY3mBu9VeeiSGoGz8hCx80Zrz+aQv28xfFfKlC8XQFpCWwsnWnQqO2Lv9bS8V1fIHgMxOHIt5Vs+9CAWGCCvUOAurjsUDoE2ALIXLDMKnJxcxD13XjWdK54j6ZXDB4syLF0C2PnAQSVY9X7MfCYwtuFmhQhKaBussAXpaVMRHltie3UYSBUUuZaB3J4cg/7TxlmxcNd+ppPRIpSZAB0NI6aOnqoBCpimscO/VpQRJMVLr3XiSYeT6HBiDXWHnIVPfQc03OGcaFqOit6p8lYKMaP/iUQLm+pgpZqrXZ9vB john@localhost",
"id": 4
@@ -133,6 +177,7 @@ X-Gitlab-Event: System Hook
```json
{
"created_at": "2012-07-21T07:30:54Z",
+ "updated_at": "2012-07-21T07:38:22Z",
"event_name": "group_create",
"name": "StoreCloud",
"owner_email": "johnsmith@gmail.com",
@@ -147,6 +192,7 @@ X-Gitlab-Event: System Hook
```json
{
"created_at": "2012-07-21T07:30:54Z",
+ "updated_at": "2012-07-21T07:38:22Z",
"event_name": "group_destroy",
"name": "StoreCloud",
"owner_email": "johnsmith@gmail.com",
@@ -161,6 +207,7 @@ X-Gitlab-Event: System Hook
```json
{
"created_at": "2012-07-21T07:30:56Z",
+ "updated_at": "2012-07-21T07:38:22Z",
"event_name": "user_add_to_group",
"group_access": "Master",
"group_id": 78,
@@ -176,6 +223,7 @@ X-Gitlab-Event: System Hook
```json
{
"created_at": "2012-07-21T07:30:56Z",
+ "updated_at": "2012-07-21T07:38:22Z",
"event_name": "user_remove_from_group",
"group_access": "Master",
"group_id": 78,
diff --git a/doc/workflow/add-user/add-user.md b/doc/workflow/add-user/add-user.md
index 8c9b4f72631..fffa0aba57f 100644
--- a/doc/workflow/add-user/add-user.md
+++ b/doc/workflow/add-user/add-user.md
@@ -1,25 +1,89 @@
# Project users
-You can manage the groups and users and their access levels in all of your projects. You can also personalize the access level you give each user, per project.
+You can manage the groups and users and their access levels in all of your
+projects. You can also personalize the access level you give each user,
+per-project.
-Here's how to add or import users to your projects.
-
-You should have 'master' or 'owner' permissions to add or import a new user
+You should have `master` or `owner` permissions to add or import a new user
to your project.
-To add or import a user, go to your project and click on "Members" on the left side of your screen:
+The first step to add or import a user, go to your project and click on
+**Members** on the left side of your screen.
+
+![Members](img/add_user_members_menu.png)
+
+---
+
+## Add a user
+
+Right next to **People**, start typing the name or username of the user you
+want to add.
+
+![Search for people](img/add_user_search_people.png)
+
+---
+
+Select the user and the [permission level](../../permissions/permissions.md)
+that you'd like to give the user. Note that you can select more than one user.
+
+![Give user permissions](img/add_user_give_permissions.png)
+
+---
+
+Once done, hit **Add users to project** and they will be immediately added to
+your project with the permissions you gave them above.
+
+![List members](img/add_user_list_members.png)
+
+---
+
+From there on, you can either remove an existing user or change their access
+level to the project.
+
+## Import users from another project
+
+You can import another project's users in your own project by hitting the
+**Import members** button on the upper right corner of the **Members** menu.
+
+In the dropdown menu, you can see only the projects you are Master on.
+
+![Import members from another project](img/add_user_import_members_from_another_project.png)
+
+---
+
+Select the one you want and hit **Import project members**. A flash message
+notifying you that the import was successful will appear, and the new members
+are now in the project's members list. Notice that the permissions that they
+had on the project you imported from are retained.
+
+![Members list of new members](img/add_user_imported_members.png)
+
+---
+
+## Invite people using their e-mail address
+
+If a user you want to give access to doesn't have an account on your GitLab
+instance, you can invite them just by typing their e-mail address in the
+user search field.
+
+![Invite user by mail](img/add_user_email_search.png)
+
+---
-![Members](images/members.png)
+As you can imagine, you can mix inviting multiple people and adding existing
+GitLab users to the project.
-Select "Add members" or "Import members" on the right side of your screen:
+![Invite user by mail ready to submit](img/add_user_email_ready.png)
-![Add or Import](images/add-members.png)
+---
-If you are adding a user, select the user and the [permission level](doc/permissions/permissions.md) that you'd like to
-give the user:
+Once done, hit **Add users to project** and watch that there is a new member
+with the e-mail address we used above. From there on, you can resend the
+invitation, change their access level or even delete them.
-![Add or Import](images/new-member.png)
+![Invite user members list](img/add_user_email_accept.png)
-If you are importing a user, follow the steps to select the project where you'd like to import the user from:
+---
-![Add or Import](images/select-project.png)
+Once the user accepts the invitation, they will be prompted to create a new
+GitLab account using the same e-mail address the invitation was sent to.
diff --git a/doc/workflow/add-user/images/add-members.png b/doc/workflow/add-user/images/add-members.png
deleted file mode 100644
index 2805c5764a5..00000000000
--- a/doc/workflow/add-user/images/add-members.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/add-user/images/new-member.png b/doc/workflow/add-user/images/new-member.png
deleted file mode 100644
index d500daea56e..00000000000
--- a/doc/workflow/add-user/images/new-member.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/add-user/images/select-project.png b/doc/workflow/add-user/images/select-project.png
deleted file mode 100644
index dd3844edff8..00000000000
--- a/doc/workflow/add-user/images/select-project.png
+++ /dev/null
Binary files differ
diff --git a/doc/workflow/add-user/img/add_new_user_to_project_settings.png b/doc/workflow/add-user/img/add_new_user_to_project_settings.png
new file mode 100644
index 00000000000..3da18cdae53
--- /dev/null
+++ b/doc/workflow/add-user/img/add_new_user_to_project_settings.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_email_accept.png b/doc/workflow/add-user/img/add_user_email_accept.png
new file mode 100644
index 00000000000..910affc9659
--- /dev/null
+++ b/doc/workflow/add-user/img/add_user_email_accept.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_email_ready.png b/doc/workflow/add-user/img/add_user_email_ready.png
new file mode 100644
index 00000000000..5f02ce89b3e
--- /dev/null
+++ b/doc/workflow/add-user/img/add_user_email_ready.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_email_search.png b/doc/workflow/add-user/img/add_user_email_search.png
new file mode 100644
index 00000000000..140979fbe13
--- /dev/null
+++ b/doc/workflow/add-user/img/add_user_email_search.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_give_permissions.png b/doc/workflow/add-user/img/add_user_give_permissions.png
new file mode 100644
index 00000000000..8ef9156c8d5
--- /dev/null
+++ b/doc/workflow/add-user/img/add_user_give_permissions.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_import_members_from_another_project.png b/doc/workflow/add-user/img/add_user_import_members_from_another_project.png
new file mode 100644
index 00000000000..5770d5cf0c4
--- /dev/null
+++ b/doc/workflow/add-user/img/add_user_import_members_from_another_project.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_imported_members.png b/doc/workflow/add-user/img/add_user_imported_members.png
new file mode 100644
index 00000000000..dea4b3f40ad
--- /dev/null
+++ b/doc/workflow/add-user/img/add_user_imported_members.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_list_members.png b/doc/workflow/add-user/img/add_user_list_members.png
new file mode 100644
index 00000000000..7daa6ca7d9e
--- /dev/null
+++ b/doc/workflow/add-user/img/add_user_list_members.png
Binary files differ
diff --git a/doc/workflow/add-user/images/members.png b/doc/workflow/add-user/img/add_user_members_menu.png
index f1797b95f67..f1797b95f67 100644
--- a/doc/workflow/add-user/images/members.png
+++ b/doc/workflow/add-user/img/add_user_members_menu.png
Binary files differ
diff --git a/doc/workflow/add-user/img/add_user_search_people.png b/doc/workflow/add-user/img/add_user_search_people.png
new file mode 100644
index 00000000000..5ac10ce80d4
--- /dev/null
+++ b/doc/workflow/add-user/img/add_user_search_people.png
Binary files differ
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
index 2d77c6d1172..2027a055c37 100644
--- a/doc/workflow/importing/import_projects_from_github.md
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -14,7 +14,7 @@ If you want to import from a GitHub Enterprise instance, you need to use GitLab
![Importer page](github_importer/importer.png)
-* To import a project, you can simple click "Add". The importer will import your repository and issues. Once the importer is done, a new GitLab project will be created with your imported data.
+* To import a project, you can simple click "Add". The importer will import your repository, issues, and pull requests. Once the importer is done, a new GitLab project will be created with your imported data.
### Note
-When you import your projects from GitHub, it is not possible to keep your labels and milestones. We are working on improving this in the near future.
+When you import your projects from GitHub, it is not possible to keep your labels, milestones, and cross-repository pull requests. We are working on improving this in the near future.
diff --git a/doc/workflow/shortcuts.png b/doc/workflow/shortcuts.png
index 68756ed1f98..e5914aa8e67 100644
--- a/doc/workflow/shortcuts.png
+++ b/doc/workflow/shortcuts.png
Binary files differ
diff --git a/features/explore/groups.feature b/features/explore/groups.feature
index a42e59c98f2..5fc9b135601 100644
--- a/features/explore/groups.feature
+++ b/features/explore/groups.feature
@@ -105,15 +105,6 @@ Feature: Explore Groups
When I visit the public groups area
Then I should see group "TestGroup"
- Scenario: I should not see group with internal project in public groups area
- Given group "TestGroup" has internal project "Internal"
- When I visit the public groups area
- Then I should not see group "TestGroup"
-
- Scenario: I should not see group with private project in public groups area
- When I visit the public groups area
- Then I should not see group "TestGroup"
-
Scenario: I should see group with public project in public groups area as user
Given group "TestGroup" has public project "Community"
When I sign in as a user
@@ -125,9 +116,3 @@ Feature: Explore Groups
When I sign in as a user
And I visit the public groups area
Then I should see group "TestGroup"
-
- Scenario: I should not see group with private project in public groups area as user
- When I sign in as a user
- And I visit the public groups area
- Then I should not see group "TestGroup"
-
diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature
index 5bb2d0e976b..01c10721312 100644
--- a/features/project/commits/commits.feature
+++ b/features/project/commits/commits.feature
@@ -55,3 +55,8 @@ Feature: Project Commits
Scenario: I browse a commit with an image
Given I visit a commit with an image that changed
Then The diff links to both the previous and current image
+
+ @javascript
+ Scenario: I filter commits by message
+ When I search "submodules" commits
+ Then I should see only "submodules" commits
diff --git a/features/project/find_file.feature b/features/project/find_file.feature
new file mode 100644
index 00000000000..ae8fa245923
--- /dev/null
+++ b/features/project/find_file.feature
@@ -0,0 +1,42 @@
+@dashboard
+Feature: Project Find File
+ Background:
+ Given I sign in as a user
+ And I own a project
+ And I visit my project's files page
+
+ @javascript
+ Scenario: Navigate to find file by shortcut
+ Given I press "t"
+ Then I should see "find file" page
+
+ Scenario: Navigate to find file
+ Given I click Find File button
+ Then I should see "find file" page
+
+ @javascript
+ Scenario: I search file
+ Given I visit project find file page
+ And I fill in file find with "change"
+ Then I should not see ".gitignore" in files
+ And I should not see ".gitmodules" in files
+ And I should see "CHANGELOG" in files
+ And I should not see "VERSION" in files
+
+ @javascript
+ Scenario: I search file that not exist
+ Given I visit project find file page
+ And I fill in file find with "asdfghjklqwertyuizxcvbnm"
+ Then I should not see ".gitignore" in files
+ And I should not see ".gitmodules" in files
+ And I should not see "CHANGELOG" in files
+ And I should not see "VERSION" in files
+
+ @javascript
+ Scenario: I search file that partially matches
+ Given I visit project find file page
+ And I fill in file find with "git"
+ Then I should see ".gitignore" in files
+ And I should see ".gitmodules" in files
+ And I should not see "CHANGELOG" in files
+ And I should not see "VERSION" in files
diff --git a/features/project/fork.feature b/features/project/fork.feature
index 22f68e5b340..37cd53ee977 100644
--- a/features/project/fork.feature
+++ b/features/project/fork.feature
@@ -14,3 +14,14 @@ Feature: Project Fork
And I click link "Fork"
When I fork to my namespace
Then I should see a "Name has already been taken" warning
+
+ Scenario: Merge request on canonical repo goes to fork merge request page
+ Given I click link "Fork"
+ And I fork to my namespace
+ Then I should see the forked project page
+ When I visit project "Shop" page
+ Then I should see "New merge request"
+ And I goto the Merge Requests page
+ Then I should see "New merge request"
+ And I click link "New merge request"
+ Then I should see the new merge request page for my namespace
diff --git a/features/steps/group/milestones.rb b/features/steps/group/milestones.rb
index 6e57b16ccb6..2363ad797fa 100644
--- a/features/steps/group/milestones.rb
+++ b/features/steps/group/milestones.rb
@@ -28,7 +28,7 @@ class Spinach::Features::GroupMilestones < Spinach::FeatureSteps
end
step 'I should see group milestone with descriptions and expiry date' do
- expect(page).to have_content('expires at Aug 20, 2114')
+ expect(page).to have_content('expires on Aug 20, 2114')
end
step 'I should see group milestone with all issues and MRs assigned to that milestone' do
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index a3141fe3be1..daf6cdaaac8 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -124,4 +124,13 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
expect(page).to have_content "build: pending"
expect(page).to have_content "1 build"
end
+
+ step 'I search "submodules" commits' do
+ fill_in 'commits-search', with: 'submodules'
+ end
+
+ step 'I should see only "submodules" commits' do
+ expect(page).to have_content "More submodules"
+ expect(page).not_to have_content "Change some files"
+ end
end
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index b0230add34f..e98bd51ca89 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -30,4 +30,23 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
click_link current_user.name
end
end
+
+ step 'I should see "New merge request"' do
+ expect(page).to have_content(/new merge request/i)
+ end
+
+ step 'I goto the Merge Requests page' do
+ page.within '.page-sidebar-expanded' do
+ click_link "Merge Requests"
+ end
+ end
+
+ step 'I click link "New merge request"' do
+ expect(page).to have_content(/new merge request/i)
+ click_link "New Merge Request"
+ end
+
+ step 'I should see the new merge request page for my namespace' do
+ current_path.should have_content(/#{current_user.namespace.name}/i)
+ end
end
diff --git a/features/steps/project/project_find_file.rb b/features/steps/project/project_find_file.rb
new file mode 100644
index 00000000000..8c1d09d6cc6
--- /dev/null
+++ b/features/steps/project/project_find_file.rb
@@ -0,0 +1,73 @@
+class Spinach::Features::ProjectFindFile < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedProject
+ include SharedProjectTab
+
+ step 'I press "t"' do
+ find('body').native.send_key('t')
+ end
+
+ step 'I click Find File button' do
+ click_link 'Find File'
+ end
+
+ step 'I should see "find file" page' do
+ ensure_active_main_tab('Files')
+ expect(page).to have_selector('.file-finder-holder', count: 1)
+ end
+
+ step 'I fill in Find by path with "git"' do
+ ensure_active_main_tab('Files')
+ expect(page).to have_selector('.file-finder-holder', count: 1)
+ end
+
+ step 'I fill in file find with "git"' do
+ find_file "git"
+ end
+
+ step 'I fill in file find with "change"' do
+ find_file "change"
+ end
+
+ step 'I fill in file find with "asdfghjklqwertyuizxcvbnm"' do
+ find_file "asdfghjklqwertyuizxcvbnm"
+ end
+
+ step 'I should see "VERSION" in files' do
+ expect(page).to have_content("VERSION")
+ end
+
+ step 'I should not see "VERSION" in files' do
+ expect(page).not_to have_content("VERSION")
+ end
+
+ step 'I should see "CHANGELOG" in files' do
+ expect(page).to have_content("CHANGELOG")
+ end
+
+ step 'I should not see "CHANGELOG" in files' do
+ expect(page).not_to have_content("CHANGELOG")
+ end
+
+ step 'I should see ".gitmodules" in files' do
+ expect(page).to have_content(".gitmodules")
+ end
+
+ step 'I should not see ".gitmodules" in files' do
+ expect(page).not_to have_content(".gitmodules")
+ end
+
+ step 'I should see ".gitignore" in files' do
+ expect(page).to have_content(".gitignore")
+ end
+
+ step 'I should not see ".gitignore" in files' do
+ expect(page).not_to have_content(".gitignore")
+ end
+
+
+ def find_file(text)
+ fill_in 'file_find', with: text
+ end
+end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index b33bd332655..4264c9c6f1a 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -259,6 +259,10 @@ module SharedPaths
visit namespace_project_deploy_keys_path(@project.namespace, @project)
end
+ step 'I visit project find file page' do
+ visit namespace_project_find_file_path(@project.namespace, @project, root_ref)
+ end
+
# ----------------------------------------
# "Shop" Project
# ----------------------------------------
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 3c1c6bda260..5c97fe1c88c 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -211,7 +211,7 @@ module API
unauthorized! unless merge_request.can_be_merged_by?(current_user)
not_allowed! if !merge_request.open? || merge_request.work_in_progress?
- merge_request.check_if_can_be_merged if merge_request.unchecked?
+ merge_request.check_if_can_be_merged
render_api_error!('Branch cannot be merged', 406) unless merge_request.can_be_merged?
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index a9e0960872a..8b1390e3289 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -3,7 +3,7 @@ module API
class Projects < Grape::API
before { authenticate! }
- resource :projects do
+ resource :projects, requirements: { id: /[^\/]+/ } do
helpers do
def map_public_to_visibility_level(attrs)
publik = attrs.delete(:public)
@@ -269,7 +269,7 @@ module API
# Remove a forked_from relationship
#
# Parameters:
- # id: (required) - The ID of the project being marked as a fork
+ # id: (required) - The ID of the project being marked as a fork
# Example Request:
# DELETE /projects/:id/fork
delete ":id/fork" do
@@ -278,6 +278,16 @@ module API
user_project.forked_project_link.destroy
end
end
+
+ # Upload a file
+ #
+ # Parameters:
+ # id: (required) - The ID of the project
+ # file: (required) - The file to be uploaded
+ post ":id/uploads" do
+ ::Projects::UploadService.new(user_project, params[:file]).execute
+ end
+
# search for projects current_user has access to
#
# Parameters:
diff --git a/lib/api/tags.rb b/lib/api/tags.rb
index 47621f443e6..2d8a9e51bb9 100644
--- a/lib/api/tags.rb
+++ b/lib/api/tags.rb
@@ -40,6 +40,27 @@ module API
end
end
+ # Delete tag
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # tag_name (required) - The name of the tag
+ # Example Request:
+ # DELETE /projects/:id/repository/tags/:tag
+ delete ":id/repository/tags/:tag_name", requirements: { tag_name: /.*/ } do
+ authorize_push_project
+ result = DeleteTagService.new(user_project, current_user).
+ execute(params[:tag_name])
+
+ if result[:status] == :success
+ {
+ tag_name: params[:tag_name]
+ }
+ else
+ render_api_error!(result[:message], result[:return_code])
+ end
+ end
+
# Add release notes to tag
#
# Parameters:
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 3400f0713ef..0d7813428e2 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -39,7 +39,7 @@ module API
if current_user.is_admin?
present @user, with: Entities::UserFull
else
- present @user, with: Entities::UserBasic
+ present @user, with: Entities::User
end
end
diff --git a/lib/banzai/filter/abstract_reference_filter.rb b/lib/banzai/filter/abstract_reference_filter.rb
index 63ad8910c0f..b2db10e6864 100644
--- a/lib/banzai/filter/abstract_reference_filter.rb
+++ b/lib/banzai/filter/abstract_reference_filter.rb
@@ -47,7 +47,17 @@ module Banzai
{ object_sym => LazyReference.new(object_class, node.attr(data_reference)) }
end
- delegate :object_class, :object_sym, :references_in, to: :class
+ def object_class
+ self.class.object_class
+ end
+
+ def object_sym
+ self.class.object_sym
+ end
+
+ def references_in(*args, &block)
+ self.class.references_in(*args, &block)
+ end
def find_object(project, id)
# Implement in child class
@@ -60,27 +70,31 @@ module Banzai
end
def call
- # `#123`
- replace_text_nodes_matching(object_class.reference_pattern) do |content|
- object_link_filter(content, object_class.reference_pattern)
- end
+ if object_class.reference_pattern
+ # `#123`
+ replace_text_nodes_matching(object_class.reference_pattern) do |content|
+ object_link_filter(content, object_class.reference_pattern)
+ end
- # `[Issue](#123)`, which is turned into
- # `<a href="#123">Issue</a>`
- replace_link_nodes_with_href(object_class.reference_pattern) do |link, text|
- object_link_filter(link, object_class.reference_pattern, link_text: text)
+ # `[Issue](#123)`, which is turned into
+ # `<a href="#123">Issue</a>`
+ replace_link_nodes_with_href(object_class.reference_pattern) do |link, text|
+ object_link_filter(link, object_class.reference_pattern, link_text: text)
+ end
end
- # `http://gitlab.example.com/namespace/project/issues/123`, which is turned into
- # `<a href="http://gitlab.example.com/namespace/project/issues/123">http://gitlab.example.com/namespace/project/issues/123</a>`
- replace_link_nodes_with_text(object_class.link_reference_pattern) do |text|
- object_link_filter(text, object_class.link_reference_pattern)
- end
+ if object_class.link_reference_pattern
+ # `http://gitlab.example.com/namespace/project/issues/123`, which is turned into
+ # `<a href="http://gitlab.example.com/namespace/project/issues/123">http://gitlab.example.com/namespace/project/issues/123</a>`
+ replace_link_nodes_with_text(object_class.link_reference_pattern) do |text|
+ object_link_filter(text, object_class.link_reference_pattern)
+ end
- # `[Issue](http://gitlab.example.com/namespace/project/issues/123)`, which is turned into
- # `<a href="http://gitlab.example.com/namespace/project/issues/123">Issue</a>`
- replace_link_nodes_with_href(object_class.link_reference_pattern) do |link, text|
- object_link_filter(link, object_class.link_reference_pattern, link_text: text)
+ # `[Issue](http://gitlab.example.com/namespace/project/issues/123)`, which is turned into
+ # `<a href="http://gitlab.example.com/namespace/project/issues/123">Issue</a>`
+ replace_link_nodes_with_href(object_class.link_reference_pattern) do |link, text|
+ object_link_filter(link, object_class.link_reference_pattern, link_text: text)
+ end
end
end
diff --git a/lib/banzai/filter/milestone_reference_filter.rb b/lib/banzai/filter/milestone_reference_filter.rb
new file mode 100644
index 00000000000..e88b27c1fae
--- /dev/null
+++ b/lib/banzai/filter/milestone_reference_filter.rb
@@ -0,0 +1,22 @@
+require 'banzai'
+
+module Banzai
+ module Filter
+ # HTML filter that replaces milestone references with links.
+ class MilestoneReferenceFilter < AbstractReferenceFilter
+ def self.object_class
+ Milestone
+ end
+
+ def find_object(project, id)
+ project.milestones.find_by(iid: id)
+ end
+
+ def url_for_object(issue, project)
+ h = Gitlab::Application.routes.url_helpers
+ h.namespace_project_milestone_url(project.namespace, project, milestone,
+ only_path: context[:only_path])
+ end
+ end
+ end
+end
diff --git a/lib/banzai/filter/redactor_filter.rb b/lib/banzai/filter/redactor_filter.rb
index f01a32b5ae5..66f77902319 100644
--- a/lib/banzai/filter/redactor_filter.rb
+++ b/lib/banzai/filter/redactor_filter.rb
@@ -10,7 +10,7 @@ module Banzai
#
class RedactorFilter < HTML::Pipeline::Filter
def call
- doc.css('a.gfm').each do |node|
+ Querying.css(doc, 'a.gfm').each do |node|
unless user_can_see_reference?(node)
# The reference should be replaced by the original text,
# which is not always the same as the rendered text.
diff --git a/lib/banzai/filter/reference_filter.rb b/lib/banzai/filter/reference_filter.rb
index 8ca05ace88c..7198a8b03e2 100644
--- a/lib/banzai/filter/reference_filter.rb
+++ b/lib/banzai/filter/reference_filter.rb
@@ -124,7 +124,7 @@ module Banzai
def replace_link_nodes_with_text(pattern)
return doc if project.nil?
- doc.search('a').each do |node|
+ doc.xpath('descendant-or-self::a').each do |node|
klass = node.attr('class')
next if klass && klass.include?('gfm')
@@ -162,7 +162,7 @@ module Banzai
def replace_link_nodes_with_href(pattern)
return doc if project.nil?
- doc.search('a').each do |node|
+ doc.xpath('descendant-or-self::a').each do |node|
klass = node.attr('class')
next if klass && klass.include?('gfm')
diff --git a/lib/banzai/filter/reference_gatherer_filter.rb b/lib/banzai/filter/reference_gatherer_filter.rb
index 12412ff7ea9..bef04112919 100644
--- a/lib/banzai/filter/reference_gatherer_filter.rb
+++ b/lib/banzai/filter/reference_gatherer_filter.rb
@@ -16,7 +16,7 @@ module Banzai
end
def call
- doc.css('a.gfm').each do |node|
+ Querying.css(doc, 'a.gfm').each do |node|
gather_references(node)
end
diff --git a/lib/banzai/filter/relative_link_filter.rb b/lib/banzai/filter/relative_link_filter.rb
index 5a081125f21..66f166939e4 100644
--- a/lib/banzai/filter/relative_link_filter.rb
+++ b/lib/banzai/filter/relative_link_filter.rb
@@ -91,7 +91,7 @@ module Banzai
parts = request_path.split('/')
parts.pop if path_type(request_path) != 'tree'
- while parts.length > 1 && path.start_with?('../')
+ while path.start_with?('../')
parts.pop
path.sub!('../', '')
end
diff --git a/lib/banzai/filter/task_list_filter.rb b/lib/banzai/filter/task_list_filter.rb
index bdf7c2ebdfc..d0ce13003a5 100644
--- a/lib/banzai/filter/task_list_filter.rb
+++ b/lib/banzai/filter/task_list_filter.rb
@@ -12,13 +12,18 @@ module Banzai
#
# See https://github.com/github/task_list/pull/60
class TaskListFilter < TaskList::Filter
- def add_css_class(node, *new_class_names)
+ def add_css_class_with_fix(node, *new_class_names)
if new_class_names.include?('task-list')
- super if node.children.any? { |c| c['class'] == 'task-list-item' }
- else
- super
+ # Don't add class to all lists
+ return
+ elsif new_class_names.include?('task-list-item')
+ add_css_class_without_fix(node.parent, 'task-list')
end
+
+ add_css_class_without_fix(node, *new_class_names)
end
+
+ alias_method_chain :add_css_class, :fix
end
end
end
diff --git a/lib/banzai/pipeline/gfm_pipeline.rb b/lib/banzai/pipeline/gfm_pipeline.rb
index 38750b55ec7..838155e8831 100644
--- a/lib/banzai/pipeline/gfm_pipeline.rb
+++ b/lib/banzai/pipeline/gfm_pipeline.rb
@@ -22,6 +22,7 @@ module Banzai
Filter::CommitRangeReferenceFilter,
Filter::CommitReferenceFilter,
Filter::LabelReferenceFilter,
+ Filter::MilestoneReferenceFilter,
Filter::TaskListFilter
]
diff --git a/lib/banzai/pipeline/single_line_pipeline.rb b/lib/banzai/pipeline/single_line_pipeline.rb
index 6725c9039a9..a3c9d4f43aa 100644
--- a/lib/banzai/pipeline/single_line_pipeline.rb
+++ b/lib/banzai/pipeline/single_line_pipeline.rb
@@ -3,7 +3,23 @@ require 'banzai'
module Banzai
module Pipeline
class SingleLinePipeline < GfmPipeline
+ def self.filters
+ @filters ||= [
+ Filter::SanitizationFilter,
+ Filter::EmojiFilter,
+ Filter::AutolinkFilter,
+ Filter::ExternalLinkFilter,
+
+ Filter::UserReferenceFilter,
+ Filter::IssueReferenceFilter,
+ Filter::ExternalIssueReferenceFilter,
+ Filter::MergeRequestReferenceFilter,
+ Filter::SnippetReferenceFilter,
+ Filter::CommitRangeReferenceFilter,
+ Filter::CommitReferenceFilter,
+ ]
+ end
end
end
end
diff --git a/lib/banzai/querying.rb b/lib/banzai/querying.rb
new file mode 100644
index 00000000000..1e1b51e683e
--- /dev/null
+++ b/lib/banzai/querying.rb
@@ -0,0 +1,18 @@
+module Banzai
+ module Querying
+ # Searches a Nokogiri document using a CSS query, optionally optimizing it
+ # whenever possible.
+ #
+ # document - A document/element to search.
+ # query - The CSS query to use.
+ #
+ # Returns a Nokogiri::XML::NodeSet.
+ def self.css(document, query)
+ # When using "a.foo" Nokogiri compiles this to "//a[...]" but
+ # "descendant::a[...]" is quite a bit faster and achieves the same result.
+ xpath = Nokogiri::CSS.xpath_for(query)[0].gsub(%r{^//}, 'descendant::')
+
+ document.xpath(xpath)
+ end
+ end
+end
diff --git a/lib/banzai/renderer.rb b/lib/banzai/renderer.rb
index 115ae914524..891c0fd7749 100644
--- a/lib/banzai/renderer.rb
+++ b/lib/banzai/renderer.rb
@@ -1,7 +1,5 @@
module Banzai
module Renderer
- CACHE_ENABLED = false
-
# Convert a Markdown String into an HTML-safe String of HTML
#
# Note that while the returned HTML will have been sanitized of dangerous
@@ -20,7 +18,7 @@ module Banzai
cache_key = context.delete(:cache_key)
cache_key = full_cache_key(cache_key, context[:pipeline])
- if cache_key && CACHE_ENABLED
+ if cache_key
Rails.cache.fetch(cache_key) do
cacheless_render(text, context)
end
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 459e3d6bcdb..4c15d58d680 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -150,6 +150,18 @@ module Gitlab
"#{path}.git", tag_name])
end
+ # Gc repository
+ #
+ # path - project path with namespace
+ #
+ # Ex.
+ # gc("gitlab/gitlab-ci")
+ #
+ def gc(path)
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'gc',
+ "#{path}.git"])
+ end
+
# Add new key to gitlab-shell
#
# Ex.
diff --git a/lib/gitlab/build_data_builder.rb b/lib/gitlab/build_data_builder.rb
index 86bfa0a4378..34e949130da 100644
--- a/lib/gitlab/build_data_builder.rb
+++ b/lib/gitlab/build_data_builder.rb
@@ -23,6 +23,7 @@ module Gitlab
build_started_at: build.started_at,
build_finished_at: build.finished_at,
build_duration: build.duration,
+ build_allow_failure: build.allow_failure,
# TODO: do we still need it?
project_id: project.id,
diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb
index 8a7f8dc5003..85583dce9ee 100644
--- a/lib/gitlab/contributions_calendar.rb
+++ b/lib/gitlab/contributions_calendar.rb
@@ -45,11 +45,11 @@ module Gitlab
end
def starting_year
- (Time.now - 1.year).strftime("%Y")
+ 1.year.ago.year
end
def starting_month
- Date.today.strftime("%m").to_i
+ Date.today.month
end
end
end
diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb
index 7a86c09158e..7f938780ab1 100644
--- a/lib/gitlab/current_settings.rb
+++ b/lib/gitlab/current_settings.rb
@@ -41,6 +41,9 @@ module Gitlab
use_db && ActiveRecord::Base.connection.active? &&
!ActiveRecord::Migrator.needs_migration? &&
ActiveRecord::Base.connection.table_exists?('application_settings')
+
+ rescue ActiveRecord::NoDatabaseError
+ false
end
end
end
diff --git a/lib/gitlab/email/receiver.rb b/lib/gitlab/email/receiver.rb
index 2b252b32887..2ca21af5bc8 100644
--- a/lib/gitlab/email/receiver.rb
+++ b/lib/gitlab/email/receiver.rb
@@ -74,7 +74,7 @@ module Gitlab
def sent_notification
return nil unless reply_key
-
+
SentNotification.for(reply_key)
end
@@ -82,10 +82,7 @@ module Gitlab
attachments = Email::AttachmentUploader.new(message).execute(sent_notification.project)
attachments.each do |link|
- text = "[#{link[:alt]}](#{link[:url]})"
- text.prepend("!") if link[:is_image]
-
- reply << "\n\n#{text}"
+ reply << "\n\n#{link[:markdown]}"
end
reply
diff --git a/lib/gitlab/fogbugz_import/importer.rb b/lib/gitlab/fogbugz_import/importer.rb
index 403ebeec474..db580b5e578 100644
--- a/lib/gitlab/fogbugz_import/importer.rb
+++ b/lib/gitlab/fogbugz_import/importer.rb
@@ -232,9 +232,7 @@ module Gitlab
return nil if res.nil?
- text = "[#{res['alt']}](#{res['url']})"
- text = "!#{text}" if res['is_image']
- text
+ res[:markdown]
end
def build_attachment_url(rel_url)
diff --git a/lib/gitlab/github_import/base_formatter.rb b/lib/gitlab/github_import/base_formatter.rb
new file mode 100644
index 00000000000..202263c6742
--- /dev/null
+++ b/lib/gitlab/github_import/base_formatter.rb
@@ -0,0 +1,21 @@
+module Gitlab
+ module GithubImport
+ class BaseFormatter
+ attr_reader :formatter, :project, :raw_data
+
+ def initialize(project, raw_data)
+ @project = project
+ @raw_data = raw_data
+ @formatter = Gitlab::ImportFormatter.new
+ end
+
+ private
+
+ def gl_user_id(github_id)
+ User.joins(:identities).
+ find_by("identities.extern_uid = ? AND identities.provider = 'github'", github_id.to_s).
+ try(:id)
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/comment_formatter.rb b/lib/gitlab/github_import/comment_formatter.rb
new file mode 100644
index 00000000000..7d58e53991a
--- /dev/null
+++ b/lib/gitlab/github_import/comment_formatter.rb
@@ -0,0 +1,45 @@
+module Gitlab
+ module GithubImport
+ class CommentFormatter < BaseFormatter
+ def attributes
+ {
+ project: project,
+ note: note,
+ commit_id: raw_data.commit_id,
+ line_code: line_code,
+ author_id: author_id,
+ created_at: raw_data.created_at,
+ updated_at: raw_data.updated_at
+ }
+ end
+
+ private
+
+ def author
+ raw_data.user.login
+ end
+
+ def author_id
+ gl_user_id(raw_data.user.id) || project.creator_id
+ end
+
+ def body
+ raw_data.body || ""
+ end
+
+ def line_code
+ if on_diff?
+ Gitlab::Diff::LineCode.generate(raw_data.path, raw_data.position, 0)
+ end
+ end
+
+ def on_diff?
+ raw_data.path && raw_data.position
+ end
+
+ def note
+ formatter.author_line(author) + body
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index b5720f6e2cb..2b0afbc7b39 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -12,39 +12,59 @@ module Gitlab
end
def execute
- #Issues && Comments
+ import_issues
+ import_pull_requests
+
+ true
+ end
+
+ private
+
+ def import_issues
client.list_issues(project.import_source, state: :all,
sort: :created,
- direction: :asc).each do |issue|
- if issue.pull_request.nil?
-
- body = @formatter.author_line(issue.user.login)
- body += issue.body || ""
+ direction: :asc).each do |raw_data|
+ gh_issue = IssueFormatter.new(project, raw_data)
- if issue.comments > 0
- body += @formatter.comments_header
+ if gh_issue.valid?
+ issue = Issue.create!(gh_issue.attributes)
- client.issue_comments(project.import_source, issue.number).each do |c|
- body += @formatter.comment(c.user.login, c.created_at, c.body)
- end
+ if gh_issue.has_comments?
+ import_comments(gh_issue.number, issue)
end
+ end
+ end
+ end
+
+ def import_pull_requests
+ client.pull_requests(project.import_source, state: :all,
+ sort: :created,
+ direction: :asc).each do |raw_data|
+ pull_request = PullRequestFormatter.new(project, raw_data)
- project.issues.create!(
- description: body,
- title: issue.title,
- state: issue.state == 'closed' ? 'closed' : 'opened',
- author_id: gl_user_id(project, issue.user.id)
- )
+ if !pull_request.cross_project? && pull_request.valid?
+ merge_request = MergeRequest.create!(pull_request.attributes)
+ import_comments(pull_request.number, merge_request)
+ import_comments_on_diff(pull_request.number, merge_request)
end
end
end
- private
+ def import_comments(issue_number, noteable)
+ comments = client.issue_comments(project.import_source, issue_number)
+ create_comments(comments, noteable)
+ end
- def gl_user_id(project, github_id)
- user = User.joins(:identities).
- find_by("identities.extern_uid = ? AND identities.provider = 'github'", github_id.to_s)
- (user && user.id) || project.creator_id
+ def import_comments_on_diff(pull_request_number, merge_request)
+ comments = client.pull_request_comments(project.import_source, pull_request_number)
+ create_comments(comments, merge_request)
+ end
+
+ def create_comments(comments, noteable)
+ comments.each do |raw_data|
+ comment = CommentFormatter.new(project, raw_data)
+ noteable.notes.create!(comment.attributes)
+ end
end
end
end
diff --git a/lib/gitlab/github_import/issue_formatter.rb b/lib/gitlab/github_import/issue_formatter.rb
new file mode 100644
index 00000000000..1e3ba44f27c
--- /dev/null
+++ b/lib/gitlab/github_import/issue_formatter.rb
@@ -0,0 +1,66 @@
+module Gitlab
+ module GithubImport
+ class IssueFormatter < BaseFormatter
+ def attributes
+ {
+ project: project,
+ title: raw_data.title,
+ description: description,
+ state: state,
+ author_id: author_id,
+ assignee_id: assignee_id,
+ created_at: raw_data.created_at,
+ updated_at: updated_at
+ }
+ end
+
+ def has_comments?
+ raw_data.comments > 0
+ end
+
+ def number
+ raw_data.number
+ end
+
+ def valid?
+ raw_data.pull_request.nil?
+ end
+
+ private
+
+ def assigned?
+ raw_data.assignee.present?
+ end
+
+ def assignee_id
+ if assigned?
+ gl_user_id(raw_data.assignee.id)
+ end
+ end
+
+ def author
+ raw_data.user.login
+ end
+
+ def author_id
+ gl_user_id(raw_data.user.id) || project.creator_id
+ end
+
+ def body
+ raw_data.body || ""
+ end
+
+ def description
+ @formatter.author_line(author) + body
+ end
+
+ def state
+ raw_data.state == 'closed' ? 'closed' : 'opened'
+ end
+
+ def updated_at
+ state == 'closed' ? raw_data.closed_at : raw_data.updated_at
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/github_import/pull_request_formatter.rb b/lib/gitlab/github_import/pull_request_formatter.rb
new file mode 100644
index 00000000000..b7c47958cc7
--- /dev/null
+++ b/lib/gitlab/github_import/pull_request_formatter.rb
@@ -0,0 +1,101 @@
+module Gitlab
+ module GithubImport
+ class PullRequestFormatter < BaseFormatter
+ def attributes
+ {
+ title: raw_data.title,
+ description: description,
+ source_project: source_project,
+ source_branch: source_branch.name,
+ target_project: target_project,
+ target_branch: target_branch.name,
+ state: state,
+ author_id: author_id,
+ assignee_id: assignee_id,
+ created_at: raw_data.created_at,
+ updated_at: updated_at
+ }
+ end
+
+ def cross_project?
+ source_repo.fork == true
+ end
+
+ def number
+ raw_data.number
+ end
+
+ def valid?
+ source_branch.present? && target_branch.present?
+ end
+
+ private
+
+ def assigned?
+ raw_data.assignee.present?
+ end
+
+ def assignee_id
+ if assigned?
+ gl_user_id(raw_data.assignee.id)
+ end
+ end
+
+ def author
+ raw_data.user.login
+ end
+
+ def author_id
+ gl_user_id(raw_data.user.id) || project.creator_id
+ end
+
+ def body
+ raw_data.body || ""
+ end
+
+ def description
+ formatter.author_line(author) + body
+ end
+
+ def source_project
+ project
+ end
+
+ def source_repo
+ raw_data.head.repo
+ end
+
+ def source_branch
+ source_project.repository.find_branch(raw_data.head.ref)
+ end
+
+ def target_project
+ project
+ end
+
+ def target_branch
+ target_project.repository.find_branch(raw_data.base.ref)
+ end
+
+ def state
+ @state ||= case true
+ when raw_data.state == 'closed' && raw_data.merged_at.present?
+ 'merged'
+ when raw_data.state == 'closed'
+ 'closed'
+ else
+ 'opened'
+ end
+ end
+
+ def updated_at
+ case state
+ when 'merged' then raw_data.merged_at
+ when 'closed' then raw_data.closed_at
+ else
+ raw_data.updated_at
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics.rb b/lib/gitlab/metrics.rb
index 2d266ccfe9e..44356a0e42c 100644
--- a/lib/gitlab/metrics.rb
+++ b/lib/gitlab/metrics.rb
@@ -6,16 +6,19 @@ module Gitlab
METRICS_ROOT = Rails.root.join('lib', 'gitlab', 'metrics').to_s
PATH_REGEX = /^#{RAILS_ROOT}\/?/
- def self.pool_size
- current_application_settings[:metrics_pool_size] || 16
- end
-
- def self.timeout
- current_application_settings[:metrics_timeout] || 10
+ def self.settings
+ @settings ||= {
+ enabled: current_application_settings[:metrics_enabled],
+ pool_size: current_application_settings[:metrics_pool_size],
+ timeout: current_application_settings[:metrics_timeout],
+ method_call_threshold: current_application_settings[:metrics_method_call_threshold],
+ host: current_application_settings[:metrics_host],
+ port: current_application_settings[:metrics_port]
+ }
end
def self.enabled?
- current_application_settings[:metrics_enabled] || false
+ settings[:enabled] || false
end
def self.mri?
@@ -26,18 +29,13 @@ module Gitlab
# This is memoized since this method is called for every instrumented
# method. Loading data from an external cache on every method call slows
# things down too much.
- @method_call_threshold ||=
- (current_application_settings[:metrics_method_call_threshold] || 10)
+ @method_call_threshold ||= settings[:method_call_threshold]
end
def self.pool
@pool
end
- def self.hostname
- @hostname
- end
-
# Returns a relative path and line number based on the last application call
# frame.
def self.last_relative_application_frame
@@ -85,19 +83,15 @@ module Gitlab
value.to_s.gsub('=', '\\=')
end
- @hostname = Socket.gethostname
-
# When enabled this should be set before being used as the usual pattern
# "@foo ||= bar" is _not_ thread-safe.
if enabled?
- @pool = ConnectionPool.new(size: pool_size, timeout: timeout) do
- host = current_application_settings[:metrics_host]
- user = current_application_settings[:metrics_username]
- pw = current_application_settings[:metrics_password]
- port = current_application_settings[:metrics_port]
+ @pool = ConnectionPool.new(size: settings[:pool_size], timeout: settings[:timeout]) do
+ host = settings[:host]
+ port = settings[:port]
InfluxDB::Client.
- new(udp: { host: host, port: port }, username: user, password: pw)
+ new(udp: { host: host, port: port })
end
end
end
diff --git a/lib/gitlab/metrics/instrumentation.rb b/lib/gitlab/metrics/instrumentation.rb
index 06fc2f25948..d9fce2e6758 100644
--- a/lib/gitlab/metrics/instrumentation.rb
+++ b/lib/gitlab/metrics/instrumentation.rb
@@ -123,6 +123,8 @@ module Gitlab
duration = (Time.now - start) * 1000.0
if duration >= Gitlab::Metrics.method_call_threshold
+ trans.increment(:method_duration, duration)
+
trans.add_metric(Gitlab::Metrics::Instrumentation::SERIES,
{ duration: duration },
method: #{label.inspect})
diff --git a/lib/gitlab/metrics/metric.rb b/lib/gitlab/metrics/metric.rb
index 753008df99a..7ea9555cc8c 100644
--- a/lib/gitlab/metrics/metric.rb
+++ b/lib/gitlab/metrics/metric.rb
@@ -17,11 +17,8 @@ module Gitlab
# Returns a Hash in a format that can be directly written to InfluxDB.
def to_hash
{
- series: @series,
- tags: @tags.merge(
- hostname: Metrics.hostname,
- process_type: Sidekiq.server? ? 'sidekiq' : 'rails'
- ),
+ series: @series,
+ tags: @tags,
values: @values,
timestamp: @created_at.to_i * 1_000_000_000
}
diff --git a/lib/gitlab/metrics/obfuscated_sql.rb b/lib/gitlab/metrics/obfuscated_sql.rb
deleted file mode 100644
index fe97d7a0534..00000000000
--- a/lib/gitlab/metrics/obfuscated_sql.rb
+++ /dev/null
@@ -1,47 +0,0 @@
-module Gitlab
- module Metrics
- # Class for producing SQL queries with sensitive data stripped out.
- class ObfuscatedSQL
- REPLACEMENT = /
- \d+(\.\d+)? # integers, floats
- | '.+?' # single quoted strings
- | \/.+?(?<!\\)\/ # regexps (including escaped slashes)
- /x
-
- MYSQL_REPLACEMENTS = /
- ".+?" # double quoted strings
- /x
-
- # Regex to replace consecutive placeholders with a single one indicating
- # the length. This can be useful when a "IN" statement uses thousands of
- # IDs (storing this would just be a waste of space).
- CONSECUTIVE = /(\?(\s*,\s*)?){2,}/
-
- # sql - The raw SQL query as a String.
- def initialize(sql)
- @sql = sql
- end
-
- # Returns a new, obfuscated SQL query.
- def to_s
- regex = REPLACEMENT
-
- if Gitlab::Database.mysql?
- regex = Regexp.union(regex, MYSQL_REPLACEMENTS)
- end
-
- sql = @sql.gsub(regex, '?').gsub(CONSECUTIVE) do |match|
- "#{match.count(',') + 1} values"
- end
-
- # InfluxDB escapes double quotes upon output, so lets get rid of them
- # whenever we can.
- if Gitlab::Database.postgresql?
- sql = sql.delete('"')
- end
-
- sql.tr("\n", ' ')
- end
- end
- end
-end
diff --git a/lib/gitlab/metrics/rack_middleware.rb b/lib/gitlab/metrics/rack_middleware.rb
index 5c0587c4c51..e7a2f26d48b 100644
--- a/lib/gitlab/metrics/rack_middleware.rb
+++ b/lib/gitlab/metrics/rack_middleware.rb
@@ -32,8 +32,8 @@ module Gitlab
def transaction_from_env(env)
trans = Transaction.new
- trans.add_tag(:request_method, env['REQUEST_METHOD'])
- trans.add_tag(:request_uri, env['REQUEST_URI'])
+ trans.set(:request_uri, env['REQUEST_URI'])
+ trans.set(:request_method, env['REQUEST_METHOD'])
trans
end
diff --git a/lib/gitlab/metrics/sampler.rb b/lib/gitlab/metrics/sampler.rb
index 998578e1c0a..1ea425bc904 100644
--- a/lib/gitlab/metrics/sampler.rb
+++ b/lib/gitlab/metrics/sampler.rb
@@ -50,12 +50,11 @@ module Gitlab
end
def sample_memory_usage
- @metrics << Metric.new('memory_usage', value: System.memory_usage)
+ add_metric('memory_usage', value: System.memory_usage)
end
def sample_file_descriptors
- @metrics << Metric.
- new('file_descriptors', value: System.file_descriptor_count)
+ add_metric('file_descriptors', value: System.file_descriptor_count)
end
if Metrics.mri?
@@ -69,7 +68,7 @@ module Gitlab
counts['Symbol'] = Symbol.all_symbols.length
counts.each do |name, count|
- @metrics << Metric.new('object_counts', { count: count }, type: name)
+ add_metric('object_counts', { count: count }, type: name)
end
end
else
@@ -91,7 +90,17 @@ module Gitlab
stats[:count] = stats[:minor_gc_count] + stats[:major_gc_count]
- @metrics << Metric.new('gc_statistics', stats)
+ add_metric('gc_statistics', stats)
+ end
+
+ def add_metric(series, values, tags = {})
+ prefix = sidekiq? ? 'sidekiq_' : 'rails_'
+
+ @metrics << Metric.new("#{prefix}#{series}", values, tags)
+ end
+
+ def sidekiq?
+ Sidekiq.server?
end
end
end
diff --git a/lib/gitlab/metrics/subscribers/action_view.rb b/lib/gitlab/metrics/subscribers/action_view.rb
index 7e0dcf99d92..7c0105d543a 100644
--- a/lib/gitlab/metrics/subscribers/action_view.rb
+++ b/lib/gitlab/metrics/subscribers/action_view.rb
@@ -19,6 +19,7 @@ module Gitlab
values = values_for(event)
tags = tags_for(event)
+ current_transaction.increment(:view_duration, event.duration)
current_transaction.add_metric(SERIES, values, tags)
end
diff --git a/lib/gitlab/metrics/subscribers/active_record.rb b/lib/gitlab/metrics/subscribers/active_record.rb
index d947c128ce2..8008b3bc895 100644
--- a/lib/gitlab/metrics/subscribers/active_record.rb
+++ b/lib/gitlab/metrics/subscribers/active_record.rb
@@ -1,44 +1,18 @@
module Gitlab
module Metrics
module Subscribers
- # Class for tracking raw SQL queries.
- #
- # Queries are obfuscated before being logged to ensure no private data is
- # exposed via InfluxDB/Grafana.
+ # Class for tracking the total query duration of a transaction.
class ActiveRecord < ActiveSupport::Subscriber
attach_to :active_record
- SERIES = 'sql_queries'
-
def sql(event)
return unless current_transaction
- values = values_for(event)
- tags = tags_for(event)
-
- current_transaction.add_metric(SERIES, values, tags)
+ current_transaction.increment(:sql_duration, event.duration)
end
private
- def values_for(event)
- { duration: event.duration }
- end
-
- def tags_for(event)
- sql = ObfuscatedSQL.new(event.payload[:sql]).to_s
- tags = { sql: sql }
-
- file, line = Metrics.last_relative_application_frame
-
- if file and line
- tags[:file] = file
- tags[:line] = line
- end
-
- tags
- end
-
def current_transaction
Transaction.current
end
diff --git a/lib/gitlab/metrics/transaction.rb b/lib/gitlab/metrics/transaction.rb
index a61dbd989e7..73131cc6ef2 100644
--- a/lib/gitlab/metrics/transaction.rb
+++ b/lib/gitlab/metrics/transaction.rb
@@ -4,23 +4,20 @@ module Gitlab
class Transaction
THREAD_KEY = :_gitlab_metrics_transaction
- SERIES = 'transactions'
-
- attr_reader :uuid, :tags
+ attr_reader :tags, :values
def self.current
Thread.current[THREAD_KEY]
end
- # name - The name of this transaction as a String.
def initialize
@metrics = []
- @uuid = SecureRandom.uuid
@started_at = nil
@finished_at = nil
- @tags = {}
+ @values = Hash.new(0)
+ @tags = {}
end
def duration
@@ -40,9 +37,17 @@ module Gitlab
end
def add_metric(series, values, tags = {})
- tags = tags.merge(transaction_id: @uuid)
+ prefix = sidekiq? ? 'sidekiq_' : 'rails_'
+
+ @metrics << Metric.new("#{prefix}#{series}", values, tags)
+ end
+
+ def increment(name, value)
+ @values[name] += value
+ end
- @metrics << Metric.new(series, values, tags)
+ def set(name, value)
+ @values[name] = value
end
def add_tag(key, value)
@@ -55,12 +60,22 @@ module Gitlab
end
def track_self
- add_metric(SERIES, { duration: duration }, @tags)
+ values = { duration: duration }
+
+ @values.each do |name, value|
+ values[name] = value
+ end
+
+ add_metric('transactions', values, @tags)
end
def submit
Metrics.submit_metrics(@metrics.map(&:to_hash))
end
+
+ def sidekiq?
+ Sidekiq.server?
+ end
end
end
end
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index be795649e59..4164e998dd1 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -19,7 +19,7 @@ module Gitlab
super(text, context.merge(project: project))
end
- %i(user label merge_request snippet commit commit_range).each do |type|
+ %i(user label milestone merge_request snippet commit commit_range).each do |type|
define_method("#{type}s") do
@references[type] ||= references(type, reference_context)
end
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 0469c5a61c3..2dc2953e328 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -431,7 +431,7 @@ namespace :gitlab do
try_fixing_it(
"sudo chmod -R ug+rwX,o-rwx #{repo_base_path}",
"sudo chmod -R ug-s #{repo_base_path}",
- "find #{repo_base_path} -type d -print0 | sudo xargs -0 chmod g+s"
+ "sudo find #{repo_base_path} -type d -print0 | sudo xargs -0 chmod g+s"
)
for_more_information(
see_installation_guide_section "GitLab Shell"
diff --git a/lib/version_check.rb b/lib/version_check.rb
index ea23344948c..91ad07feee5 100644
--- a/lib/version_check.rb
+++ b/lib/version_check.rb
@@ -13,6 +13,6 @@ class VersionCheck
end
def host
- 'https://version.gitlab.com/check.png'
+ 'https://version.gitlab.com/check.svg'
end
end
diff --git a/spec/controllers/abuse_reports_controller_spec.rb b/spec/controllers/abuse_reports_controller_spec.rb
index 15824a1c67f..80a418feb3e 100644
--- a/spec/controllers/abuse_reports_controller_spec.rb
+++ b/spec/controllers/abuse_reports_controller_spec.rb
@@ -1,76 +1,46 @@
require 'spec_helper'
describe AbuseReportsController do
- let(:reporter) { create(:user) }
- let(:user) { create(:user) }
- let(:message) { "This user is a spammer" }
+ let(:reporter) { create(:user) }
+ let(:user) { create(:user) }
+ let(:attrs) do
+ attributes_for(:abuse_report) do |hash|
+ hash[:user_id] = user.id
+ end
+ end
before do
sign_in(reporter)
end
- describe "POST create" do
- context "with admin notification email set" do
- let(:admin_email) { "admin@example.com"}
-
- before(:each) do
- stub_application_setting(admin_notification_email: admin_email)
+ describe 'POST create' do
+ context 'with valid attributes' do
+ it 'saves the abuse report' do
+ expect do
+ post :create, abuse_report: attrs
+ end.to change { AbuseReport.count }.by(1)
end
- it "sends a notification email" do
- perform_enqueued_jobs do
- post :create,
- abuse_report: {
- user_id: user.id,
- message: message
- }
-
- email = ActionMailer::Base.deliveries.last
+ it 'calls notify' do
+ expect_any_instance_of(AbuseReport).to receive(:notify)
- expect(email.to).to eq([admin_email])
- expect(email.subject).to include(user.username)
- expect(email.text_part.body).to include(message)
- end
+ post :create, abuse_report: attrs
end
- it "saves the abuse report" do
- perform_enqueued_jobs do
- expect do
- post :create,
- abuse_report: {
- user_id: user.id,
- message: message
- }
- end.to change { AbuseReport.count }.by(1)
- end
- end
- end
+ it 'redirects back to the reported user' do
+ post :create, abuse_report: attrs
- context "without admin notification email set" do
- before(:each) do
- stub_application_setting(admin_notification_email: nil)
+ expect(response).to redirect_to user
end
+ end
- it "does not send a notification email" do
- expect do
- post :create,
- abuse_report: {
- user_id: user.id,
- message: message
- }
- end.not_to change { ActionMailer::Base.deliveries.count }
- end
+ context 'with invalid attributes' do
+ it 'renders new' do
+ attrs.delete(:user_id)
+ post :create, abuse_report: attrs
- it "saves the abuse report" do
- expect do
- post :create,
- abuse_report: {
- user_id: user.id,
- message: message
- }
- end.to change { AbuseReport.count }.by(1)
+ expect(response).to render_template(:new)
end
end
end
-
end
diff --git a/spec/controllers/projects/find_file_controller_spec.rb b/spec/controllers/projects/find_file_controller_spec.rb
new file mode 100644
index 00000000000..038dfeb8466
--- /dev/null
+++ b/spec/controllers/projects/find_file_controller_spec.rb
@@ -0,0 +1,66 @@
+require 'spec_helper'
+
+describe Projects::FindFileController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+
+ project.team << [user, :master]
+ controller.instance_variable_set(:@project, project)
+ end
+
+ describe "GET #show" do
+ # Make sure any errors accessing the tree in our views bubble up to this spec
+ render_views
+
+ before do
+ get(:show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id)
+ end
+
+ context "valid branch" do
+ let(:id) { 'master' }
+ it { is_expected.to respond_with(:success) }
+ end
+
+ context "invalid branch" do
+ let(:id) { 'invalid-branch' }
+ it { is_expected.to respond_with(:not_found) }
+ end
+ end
+
+ describe "GET #list" do
+ def go(format: 'json')
+ get :list,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id,
+ format: format
+ end
+
+ context "valid branch" do
+ let(:id) { 'master' }
+ it 'returns an array of file path list' do
+ go
+
+ json = JSON.parse(response.body)
+ is_expected.to respond_with(:success)
+ expect(json).not_to eq(nil)
+ expect(json.length).to be >= 0
+ end
+ end
+
+ context "invalid branch" do
+ let(:id) { 'invalid-branch' }
+
+ it 'responds with status 404' do
+ go
+ is_expected.to respond_with(:not_found)
+ end
+ end
+ end
+end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 5b4d7f41bc4..0c6a881f868 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -2,25 +2,28 @@
#
# Table name: merge_requests
#
-# id :integer not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
-# source_project_id :integer not null
-# author_id :integer
-# assignee_id :integer
-# title :string(255)
-# created_at :datetime
-# updated_at :datetime
-# milestone_id :integer
-# state :string(255)
-# merge_status :string(255)
-# target_project_id :integer not null
-# iid :integer
-# description :text
-# position :integer default(0)
-# locked_at :datetime
-# updated_by_id :integer
-# merge_error :string(255)
+# id :integer not null, primary key
+# target_branch :string(255) not null
+# source_branch :string(255) not null
+# source_project_id :integer not null
+# author_id :integer
+# assignee_id :integer
+# title :string(255)
+# created_at :datetime
+# updated_at :datetime
+# milestone_id :integer
+# state :string(255)
+# merge_status :string(255)
+# target_project_id :integer not null
+# iid :integer
+# description :text
+# position :integer default(0)
+# locked_at :datetime
+# updated_by_id :integer
+# merge_error :string(255)
+# merge_params :text
+# merge_when_build_succeeds :boolean default(FALSE), not null
+# merge_user_id :integer
#
FactoryGirl.define do
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 112213377ff..c14b99606ba 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -29,6 +29,13 @@
# import_source :string(255)
# commit_count :integer default(0)
# import_error :text
+# ci_id :integer
+# builds_enabled :boolean default(TRUE), not null
+# shared_runners_enabled :boolean default(TRUE), not null
+# runners_token :string
+# build_coverage_regex :string
+# build_allow_git_fetch :boolean default(TRUE), not null
+# build_timeout :integer default(3600), not null
#
FactoryGirl.define do
diff --git a/spec/features/admin/admin_builds_spec.rb b/spec/features/admin/admin_builds_spec.rb
index 72764b1629d..b955d0b0c46 100644
--- a/spec/features/admin/admin_builds_spec.rb
+++ b/spec/features/admin/admin_builds_spec.rb
@@ -1,69 +1,98 @@
require 'spec_helper'
-describe "Admin Builds" do
- let(:commit) { FactoryGirl.create :ci_commit }
- let(:build) { FactoryGirl.create :ci_build, commit: commit }
-
+describe 'Admin Builds' do
before do
login_as :admin
end
- describe "GET /admin/builds" do
- before do
- build
- visit admin_builds_path
- end
-
- it { expect(page).to have_content "Running" }
- it { expect(page).to have_content build.short_sha }
- end
+ describe 'GET /admin/builds' do
+ let(:commit) { create(:ci_commit) }
- describe "Tabs" do
- it "shows all builds" do
- FactoryGirl.create :ci_build, commit: commit, status: "pending"
- FactoryGirl.create :ci_build, commit: commit, status: "running"
- FactoryGirl.create :ci_build, commit: commit, status: "success"
- FactoryGirl.create :ci_build, commit: commit, status: "failed"
+ context 'All tab' do
+ context 'when have builds' do
+ it 'shows all builds' do
+ create(:ci_build, commit: commit, status: :pending)
+ create(:ci_build, commit: commit, status: :running)
+ create(:ci_build, commit: commit, status: :success)
+ create(:ci_build, commit: commit, status: :failed)
- visit admin_builds_path
+ visit admin_builds_path
- within ".center-top-menu" do
- click_on "All"
+ expect(page).to have_selector('.project-issuable-filter li.active', text: 'All')
+ expect(page.all('.build-link').size).to eq(4)
+ expect(page).to have_link 'Cancel all'
+ end
end
- expect(page.all(".build-link").size).to eq(4)
+ context 'when have no builds' do
+ it 'shows a message' do
+ visit admin_builds_path
+
+ expect(page).to have_selector('.project-issuable-filter li.active', text: 'All')
+ expect(page).to have_content 'No builds to show'
+ expect(page).not_to have_link 'Cancel all'
+ end
+ end
end
- it "shows finished builds" do
- build = FactoryGirl.create :ci_build, commit: commit, status: "pending"
- build1 = FactoryGirl.create :ci_build, commit: commit, status: "running"
- build2 = FactoryGirl.create :ci_build, commit: commit, status: "success"
+ context 'Running tab' do
+ context 'when have running builds' do
+ it 'shows running builds' do
+ build1 = create(:ci_build, commit: commit, status: :pending)
+ build2 = create(:ci_build, commit: commit, status: :success)
+ build3 = create(:ci_build, commit: commit, status: :failed)
+
+ visit admin_builds_path(scope: :running)
+
+ expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running')
+ expect(page.find('.build-link')).to have_content(build1.id)
+ expect(page.find('.build-link')).not_to have_content(build2.id)
+ expect(page.find('.build-link')).not_to have_content(build3.id)
+ expect(page).to have_link 'Cancel all'
+ end
+ end
- visit admin_builds_path
+ context 'when have no builds running' do
+ it 'shows a message' do
+ create(:ci_build, commit: commit, status: :success)
- within ".center-top-menu" do
- click_on "Finished"
- end
+ visit admin_builds_path(scope: :running)
- expect(page.find(".build-link")).not_to have_content(build.id)
- expect(page.find(".build-link")).not_to have_content(build1.id)
- expect(page.find(".build-link")).to have_content(build2.id)
+ expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running')
+ expect(page).to have_content 'No builds to show'
+ expect(page).not_to have_link 'Cancel all'
+ end
+ end
end
- it "shows running builds" do
- build = FactoryGirl.create :ci_build, commit: commit, status: "pending"
- build2 = FactoryGirl.create :ci_build, commit: commit, status: "success"
- build3 = FactoryGirl.create :ci_build, commit: commit, status: "failed"
+ context 'Finished tab' do
+ context 'when have finished builds' do
+ it 'shows finished builds' do
+ build1 = create(:ci_build, commit: commit, status: :pending)
+ build2 = create(:ci_build, commit: commit, status: :running)
+ build3 = create(:ci_build, commit: commit, status: :success)
+
+ visit admin_builds_path(scope: :finished)
+
+ expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished')
+ expect(page.find('.build-link')).not_to have_content(build1.id)
+ expect(page.find('.build-link')).not_to have_content(build2.id)
+ expect(page.find('.build-link')).to have_content(build3.id)
+ expect(page).to have_link 'Cancel all'
+ end
+ end
- visit admin_builds_path
+ context 'when have no builds finished' do
+ it 'shows a message' do
+ create(:ci_build, commit: commit, status: :running)
- within ".center-top-menu" do
- click_on "Running"
- end
+ visit admin_builds_path(scope: :finished)
- expect(page.find(".build-link")).to have_content(build.id)
- expect(page.find(".build-link")).not_to have_content(build2.id)
- expect(page.find(".build-link")).not_to have_content(build3.id)
+ expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished')
+ expect(page).to have_content 'No builds to show'
+ expect(page).to have_link 'Cancel all'
+ end
+ end
end
end
end
diff --git a/spec/features/builds_spec.rb b/spec/features/builds_spec.rb
index f0031a0a247..240e56839df 100644
--- a/spec/features/builds_spec.rb
+++ b/spec/features/builds_spec.rb
@@ -15,11 +15,11 @@ describe "Builds" do
context "Running scope" do
before do
@build.run!
- visit namespace_project_builds_path(@project.namespace, @project)
+ visit namespace_project_builds_path(@project.namespace, @project, scope: :running)
end
- it { expect(page).to have_content 'Running' }
- it { expect(page).to have_content 'Cancel running' }
+ it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running') }
+ it { expect(page).to have_link 'Cancel running' }
it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name }
@@ -31,21 +31,22 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project, scope: :finished)
end
+ it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished') }
it { expect(page).to have_content 'No builds to show' }
- it { expect(page).to have_content 'Cancel running' }
+ it { expect(page).to have_link 'Cancel running' }
end
context "All builds" do
before do
@project.builds.running_or_pending.each(&:success)
- visit namespace_project_builds_path(@project.namespace, @project, scope: :all)
+ visit namespace_project_builds_path(@project.namespace, @project)
end
- it { expect(page).to have_content 'All' }
+ it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') }
it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name }
- it { expect(page).to_not have_content 'Cancel running' }
+ it { expect(page).to_not have_link 'Cancel running' }
end
end
@@ -56,8 +57,12 @@ describe "Builds" do
click_link "Cancel running"
end
- it { expect(page).to have_content 'No builds to show' }
- it { expect(page).to_not have_content 'Cancel running' }
+ it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') }
+ it { expect(page).to have_content 'canceled' }
+ it { expect(page).to have_content @build.short_sha }
+ it { expect(page).to have_content @build.ref }
+ it { expect(page).to have_content @build.name }
+ it { expect(page).to_not have_link 'Cancel running' }
end
describe "GET /:project/builds/:id" do
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index fdd8cf07b12..e836d81c40b 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -212,6 +212,7 @@ describe 'GitLab Markdown', feature: true do
expect(doc).to reference_commit_ranges
expect(doc).to reference_commits
expect(doc).to reference_labels
+ expect(doc).to reference_milestones
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 74b148f5d17..9a01c89ae2a 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -80,8 +80,10 @@ feature 'Project', feature: true do
visit namespace_project_path(project.namespace, project)
end
- it { expect(page).to have_content('You have Master access to this project.') }
- it { expect(page).to have_link('Leave this project') }
+ it 'click project-settings and find leave project' do
+ find('#project-settings-button').click
+ expect(page).to have_link('Leave Project')
+ end
end
def remove_with_confirm(button_text, confirm_with)
diff --git a/spec/finders/groups_finder_spec.rb b/spec/finders/groups_finder_spec.rb
deleted file mode 100644
index 4f6a000822e..00000000000
--- a/spec/finders/groups_finder_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-require 'spec_helper'
-
-describe GroupsFinder do
- describe '#execute' do
- let(:user) { create(:user) }
-
- let(:group1) { create(:group) }
- let(:group2) { create(:group) }
- let(:group3) { create(:group) }
- let(:group4) { create(:group, public: true) }
-
- let!(:public_project) { create(:project, :public, group: group1) }
- let!(:internal_project) { create(:project, :internal, group: group2) }
- let!(:private_project) { create(:project, :private, group: group3) }
-
- let(:finder) { described_class.new }
-
- describe 'with a user' do
- subject { finder.execute(user) }
-
- describe 'when the user is not a member of any groups' do
- it { is_expected.to eq([group4, group2, group1]) }
- end
-
- describe 'when the user is a member of a group' do
- before do
- group3.add_user(user, Gitlab::Access::DEVELOPER)
- end
-
- it { is_expected.to eq([group4, group3, group2, group1]) }
- end
-
- describe 'when the user is a member of a private project' do
- before do
- private_project.team.add_user(user, Gitlab::Access::DEVELOPER)
- end
-
- it { is_expected.to eq([group4, group3, group2, group1]) }
- end
- end
-
- describe 'without a user' do
- subject { finder.execute }
-
- it { is_expected.to eq([group4, group1]) }
- end
- end
-end
diff --git a/spec/finders/joined_groups_finder_spec.rb b/spec/finders/joined_groups_finder_spec.rb
deleted file mode 100644
index 2d9068cc720..00000000000
--- a/spec/finders/joined_groups_finder_spec.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-require 'spec_helper'
-
-describe JoinedGroupsFinder do
- describe '#execute' do
- let(:source_user) { create(:user) }
- let(:current_user) { create(:user) }
-
- let(:group1) { create(:group) }
- let(:group2) { create(:group) }
- let(:group3) { create(:group) }
- let(:group4) { create(:group, public: true) }
-
- let!(:public_project) { create(:project, :public, group: group1) }
- let!(:internal_project) { create(:project, :internal, group: group2) }
- let!(:private_project) { create(:project, :private, group: group3) }
-
- let(:finder) { described_class.new(source_user) }
-
- before do
- [group1, group2, group3, group4].each do |group|
- group.add_user(source_user, Gitlab::Access::MASTER)
- end
- end
-
- describe 'with a current user' do
- describe 'when the current user has access to the projects of the source user' do
- before do
- private_project.team.add_user(current_user, Gitlab::Access::DEVELOPER)
- end
-
- subject { finder.execute(current_user) }
-
- it { is_expected.to eq([group4, group3, group2, group1]) }
- end
-
- describe 'when the current user does not have access to the projects of the source user' do
- subject { finder.execute(current_user) }
-
- it { is_expected.to eq([group4, group2, group1]) }
- end
- end
-
- describe 'without a current user' do
- subject { finder.execute }
-
- it { is_expected.to eq([group4, group1]) }
- end
- end
-end
diff --git a/spec/fixtures/markdown.md.erb b/spec/fixtures/markdown.md.erb
index e8dfc5c0eb1..0620096d689 100644
--- a/spec/fixtures/markdown.md.erb
+++ b/spec/fixtures/markdown.md.erb
@@ -214,6 +214,13 @@ References should be parseable even inside _<%= merge_request.to_reference %>_ e
- Ignored in links: [Link to <%= simple_label.to_reference %>](#label-link)
- Link to label by reference: [Label](<%= label.to_reference %>)
+#### MilestoneReferenceFilter
+
+- Milestone: <%= milestone.to_reference %>
+- Milestone in another project: <%= xmilestone.to_reference(project) %>
+- Ignored in code: `<%= milestone.to_reference %>`
+- Link to milestone by URL: [Milestone](<%= urls.namespace_project_milestone_url(milestone.project.namespace, milestone.project, milestone) %>)
+
### Task Lists
- [ ] Incomplete task 1
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 68527c3a4f8..efc850eb705 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -240,7 +240,7 @@ describe ApplicationHelper do
describe 'time_ago_with_tooltip' do
def element(*arguments)
Time.zone = 'UTC'
- time = Time.zone.parse('2015-07-02 08:00')
+ time = Time.zone.parse('2015-07-02 08:23')
element = helper.time_ago_with_tooltip(time, *arguments)
Nokogiri::HTML::DocumentFragment.parse(element).first_element_child
@@ -251,15 +251,15 @@ describe ApplicationHelper do
end
it 'includes the date string' do
- expect(element.text).to eq '2015-07-02 08:00:00 UTC'
+ expect(element.text).to eq '2015-07-02 08:23:00 UTC'
end
it 'has a datetime attribute' do
- expect(element.attr('datetime')).to eq '2015-07-02T08:00:00Z'
+ expect(element.attr('datetime')).to eq '2015-07-02T08:23:00Z'
end
it 'has a formatted title attribute' do
- expect(element.attr('title')).to eq 'Jul 02, 2015 8:00am'
+ expect(element.attr('title')).to eq 'Jul 2, 2015 8:23am'
end
it 'includes a default js-timeago class' do
diff --git a/spec/helpers/page_layout_helper_spec.rb b/spec/helpers/page_layout_helper_spec.rb
index fd7107779f6..cf632f594c7 100644
--- a/spec/helpers/page_layout_helper_spec.rb
+++ b/spec/helpers/page_layout_helper_spec.rb
@@ -2,10 +2,8 @@ require 'rails_helper'
describe PageLayoutHelper do
describe 'page_description' do
- it 'defaults to value returned by page_description_default helper' do
- allow(helper).to receive(:page_description_default).and_return('Foo')
-
- expect(helper.page_description).to eq 'Foo'
+ it 'defaults to nil' do
+ expect(helper.page_description).to eq nil
end
it 'returns the last-pushed description' do
@@ -42,58 +40,32 @@ describe PageLayoutHelper do
end
end
- describe 'page_description_default' do
- it 'uses Project description when available' do
- project = double(description: 'Project Description')
- helper.instance_variable_set(:@project, project)
-
- expect(helper.page_description_default).to eq 'Project Description'
- end
-
- it 'uses brand_title when Project description is nil' do
- project = double(description: nil)
- helper.instance_variable_set(:@project, project)
-
- expect(helper).to receive(:brand_title).and_return('Brand Title')
- expect(helper.page_description_default).to eq 'Brand Title'
- end
-
- it 'falls back to brand_title' do
- allow(helper).to receive(:brand_title).and_return('Brand Title')
-
- expect(helper.page_description_default).to eq 'Brand Title'
- end
- end
-
describe 'page_image' do
it 'defaults to the GitLab logo' do
expect(helper.page_image).to end_with 'assets/gitlab_logo.png'
end
- context 'with @project' do
- it 'uses Project avatar if available' do
- project = double(avatar_url: 'http://example.com/uploads/avatar.png')
- helper.instance_variable_set(:@project, project)
+ %w(project user group).each do |type|
+ context "with @#{type} assigned" do
+ it "uses #{type.titlecase} avatar if available" do
+ object = double(avatar_url: 'http://example.com/uploads/avatar.png')
+ assign(type, object)
- expect(helper.page_image).to eq project.avatar_url
- end
+ expect(helper.page_image).to eq object.avatar_url
+ end
- it 'falls back to the default' do
- project = double(avatar_url: nil)
- helper.instance_variable_set(:@project, project)
+ it 'falls back to the default when avatar_url is nil' do
+ object = double(avatar_url: nil)
+ assign(type, object)
- expect(helper.page_image).to end_with 'assets/gitlab_logo.png'
+ expect(helper.page_image).to end_with 'assets/gitlab_logo.png'
+ end
end
- end
-
- context 'with @user' do
- it 'delegates to avatar_icon helper' do
- user = double('User')
- helper.instance_variable_set(:@user, user)
-
- expect(helper).to receive(:avatar_icon).with(user)
- helper.page_image
+ context "with no assignments" do
+ it 'falls back to the default' do
+ expect(helper.page_image).to end_with 'assets/gitlab_logo.png'
+ end
end
end
end
diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb
index ebe9c29d91c..f0d553f5f1d 100644
--- a/spec/helpers/search_helper_spec.rb
+++ b/spec/helpers/search_helper_spec.rb
@@ -43,7 +43,7 @@ describe SearchHelper do
end
it "includes the public group" do
- group = create(:group, public: true)
+ group = create(:group)
expect(search_autocomplete_opts(group.name).size).to eq(1)
end
diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee
index 7e67c778861..b85fadcbe82 100644
--- a/spec/javascripts/issue_spec.js.coffee
+++ b/spec/javascripts/issue_spec.js.coffee
@@ -26,10 +26,10 @@ describe 'reopen/close issue', ->
fixture.load('issues_show.html')
@issue = new Issue()
it 'closes an issue', ->
- $.ajax = (obj) ->
- expect(obj.type).toBe('PUT')
- expect(obj.url).toBe('http://gitlab.com/issues/6/close')
- obj.success saved: true
+ spyOn(jQuery, 'ajax').and.callFake (req) ->
+ expect(req.type).toBe('PUT')
+ expect(req.url).toBe('http://gitlab.com/issues/6/close')
+ req.success saved: true
$btnClose = $('a.btn-close')
$btnReopen = $('a.btn-reopen')
@@ -46,10 +46,10 @@ describe 'reopen/close issue', ->
it 'fails to closes an issue with success:false', ->
- $.ajax = (obj) ->
- expect(obj.type).toBe('PUT')
- expect(obj.url).toBe('http://goesnowhere.nothing/whereami')
- obj.success saved: false
+ spyOn(jQuery, 'ajax').and.callFake (req) ->
+ expect(req.type).toBe('PUT')
+ expect(req.url).toBe('http://goesnowhere.nothing/whereami')
+ req.success saved: false
$btnClose = $('a.btn-close')
$btnReopen = $('a.btn-reopen')
@@ -69,10 +69,10 @@ describe 'reopen/close issue', ->
it 'fails to closes an issue with HTTP error', ->
- $.ajax = (obj) ->
- expect(obj.type).toBe('PUT')
- expect(obj.url).toBe('http://goesnowhere.nothing/whereami')
- obj.error()
+ spyOn(jQuery, 'ajax').and.callFake (req) ->
+ expect(req.type).toBe('PUT')
+ expect(req.url).toBe('http://goesnowhere.nothing/whereami')
+ req.error()
$btnClose = $('a.btn-close')
$btnReopen = $('a.btn-reopen')
@@ -91,10 +91,10 @@ describe 'reopen/close issue', ->
expect($('div.flash-alert').text()).toBe('Unable to update this issue at this time.')
it 'reopens an issue', ->
- $.ajax = (obj) ->
- expect(obj.type).toBe('PUT')
- expect(obj.url).toBe('http://gitlab.com/issues/6/reopen')
- obj.success saved: true
+ spyOn(jQuery, 'ajax').and.callFake (req) ->
+ expect(req.type).toBe('PUT')
+ expect(req.url).toBe('http://gitlab.com/issues/6/reopen')
+ req.success saved: true
$btnClose = $('a.btn-close')
$btnReopen = $('a.btn-reopen')
diff --git a/spec/lib/banzai/filter/milestone_reference_filter_spec.rb b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
new file mode 100644
index 00000000000..ebf3d7489b5
--- /dev/null
+++ b/spec/lib/banzai/filter/milestone_reference_filter_spec.rb
@@ -0,0 +1,75 @@
+require 'spec_helper'
+
+describe Banzai::Filter::MilestoneReferenceFilter, lib: true do
+ include FilterSpecHelper
+
+ let(:project) { create(:project, :public) }
+ let(:milestone) { create(:milestone, project: project) }
+
+ it 'requires project context' do
+ expect { described_class.call('') }.to raise_error(ArgumentError, /:project/)
+ end
+
+ %w(pre code a style).each do |elem|
+ it "ignores valid references contained inside '#{elem}' element" do
+ exp = act = "<#{elem}>milestone #{milestone.to_reference}</#{elem}>"
+ expect(reference_filter(act).to_html).to eq exp
+ end
+ end
+
+ context 'internal reference' do
+ # Convert the Markdown link to only the URL, since these tests aren't run through the regular Markdown pipeline.
+ # Milestone reference behavior in the full Markdown pipeline is tested elsewhere.
+ let(:reference) { milestone.to_reference.gsub(/\[([^\]]+)\]\(([^)]+)\)/, '\2') }
+
+ it 'links to a valid reference' do
+ doc = reference_filter("See #{reference}")
+
+ expect(doc.css('a').first.attr('href')).to eq urls.
+ namespace_project_milestone_url(project.namespace, project, milestone)
+ end
+
+ it 'links with adjacent text' do
+ doc = reference_filter("milestone (#{reference}.)")
+ expect(doc.to_html).to match(/\(<a.+>#{Regexp.escape(milestone.title)}<\/a>\.\)/)
+ end
+
+ it 'includes a title attribute' do
+ doc = reference_filter("milestone #{reference}")
+ expect(doc.css('a').first.attr('title')).to eq "Milestone: #{milestone.title}"
+ end
+
+ it 'escapes the title attribute' do
+ milestone.update_attribute(:title, %{"></a>whatever<a title="})
+
+ doc = reference_filter("milestone #{reference}")
+ expect(doc.text).to eq "milestone #{milestone.title}"
+ end
+
+ it 'includes default classes' do
+ doc = reference_filter("milestone #{reference}")
+ expect(doc.css('a').first.attr('class')).to eq 'gfm gfm-milestone'
+ end
+
+ it 'includes a data-project attribute' do
+ doc = reference_filter("milestone #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-project')
+ expect(link.attr('data-project')).to eq project.id.to_s
+ end
+
+ it 'includes a data-milestone attribute' do
+ doc = reference_filter("See #{reference}")
+ link = doc.css('a').first
+
+ expect(link).to have_attribute('data-milestone')
+ expect(link.attr('data-milestone')).to eq milestone.id.to_s
+ end
+
+ it 'adds to the results hash' do
+ result = reference_pipeline_result("milestone #{reference}")
+ expect(result[:references][:milestone]).to eq [milestone]
+ end
+ end
+end
diff --git a/spec/lib/banzai/filter/relative_link_filter_spec.rb b/spec/lib/banzai/filter/relative_link_filter_spec.rb
index 0b3e5ecbc9f..0e6685f0ffb 100644
--- a/spec/lib/banzai/filter/relative_link_filter_spec.rb
+++ b/spec/lib/banzai/filter/relative_link_filter_spec.rb
@@ -92,6 +92,14 @@ describe Banzai::Filter::RelativeLinkFilter, lib: true do
to eq "/#{project_path}/blob/#{ref}/doc/api/README.md"
end
+ it 'rebuilds relative URL for a file in the repository root' do
+ relative_link = link('../README.md')
+ doc = filter(relative_link, requested_path: 'doc/some-file.md')
+
+ expect(doc.at_css('a')['href']).
+ to eq "/#{project_path}/blob/#{ref}/README.md"
+ end
+
it 'rebuilds relative URL for a file in the repo with an anchor' do
doc = filter(link('README.md#section'))
expect(doc.at_css('a')['href']).
diff --git a/spec/lib/banzai/filter/task_list_filter_spec.rb b/spec/lib/banzai/filter/task_list_filter_spec.rb
index f2e3a44478d..569cbc885c7 100644
--- a/spec/lib/banzai/filter/task_list_filter_spec.rb
+++ b/spec/lib/banzai/filter/task_list_filter_spec.rb
@@ -7,4 +7,10 @@ describe Banzai::Filter::TaskListFilter, lib: true do
exp = act = %(<ul><li>Item</li></ul>)
expect(filter(act).to_html).to eq exp
end
+
+ it 'applies `task-list` to single-item task lists' do
+ act = filter('<ul><li>[ ] Task 1</li></ul>')
+
+ expect(act.to_html).to start_with '<ul class="task-list">'
+ end
end
diff --git a/spec/lib/banzai/querying_spec.rb b/spec/lib/banzai/querying_spec.rb
new file mode 100644
index 00000000000..27da2a7439c
--- /dev/null
+++ b/spec/lib/banzai/querying_spec.rb
@@ -0,0 +1,13 @@
+require 'spec_helper'
+
+describe Banzai::Querying do
+ describe '.css' do
+ it 'optimizes queries for elements with classes' do
+ document = double(:document)
+
+ expect(document).to receive(:xpath).with(/^descendant::a/)
+
+ described_class.css(document, 'a.gfm')
+ end
+ end
+end
diff --git a/spec/lib/gitlab/build_data_builder_spec.rb b/spec/lib/gitlab/build_data_builder_spec.rb
index 839b30f1ff4..38be9448794 100644
--- a/spec/lib/gitlab/build_data_builder_spec.rb
+++ b/spec/lib/gitlab/build_data_builder_spec.rb
@@ -14,6 +14,7 @@ describe 'Gitlab::BuildDataBuilder' do
it { expect(data[:tag]).to eq(build.tag) }
it { expect(data[:build_id]).to eq(build.id) }
it { expect(data[:build_status]).to eq(build.status) }
+ it { expect(data[:build_allow_failure]).to eq(false) }
it { expect(data[:project_id]).to eq(build.project.id) }
it { expect(data[:project_name]).to eq(build.project.name_with_namespace) }
end
diff --git a/spec/lib/gitlab/email/receiver_spec.rb b/spec/lib/gitlab/email/receiver_spec.rb
index b535413bbd4..abe179cd4af 100644
--- a/spec/lib/gitlab/email/receiver_spec.rb
+++ b/spec/lib/gitlab/email/receiver_spec.rb
@@ -42,7 +42,7 @@ describe Gitlab::Email::Receiver, lib: true do
context "when the email was auto generated" do
let!(:reply_key) { '636ca428858779856c226bb145ef4fad' }
let!(:email_raw) { fixture_file("emails/auto_reply.eml") }
-
+
it "raises an AutoGeneratedEmailError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::AutoGeneratedEmailError)
end
@@ -90,7 +90,7 @@ describe Gitlab::Email::Receiver, lib: true do
context "when the reply is blank" do
let!(:email_raw) { fixture_file("emails/no_content_reply.eml") }
-
+
it "raises an EmptyEmailError" do
expect { receiver.execute }.to raise_error(Gitlab::Email::Receiver::EmptyEmailError)
end
@@ -107,13 +107,16 @@ describe Gitlab::Email::Receiver, lib: true do
end
context "when everything is fine" do
+ let(:markdown) { "![image](uploads/image.png)" }
+
before do
allow_any_instance_of(Gitlab::Email::AttachmentUploader).to receive(:execute).and_return(
[
{
url: "uploads/image.png",
is_image: true,
- alt: "image"
+ alt: "image",
+ markdown: markdown
}
]
)
@@ -132,7 +135,7 @@ describe Gitlab::Email::Receiver, lib: true do
note = noteable.notes.last
- expect(note.note).to include("![image](uploads/image.png)")
+ expect(note.note).to include(markdown)
end
end
end
diff --git a/spec/lib/gitlab/github_import/comment_formatter_spec.rb b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
new file mode 100644
index 00000000000..a324a82e69f
--- /dev/null
+++ b/spec/lib/gitlab/github_import/comment_formatter_spec.rb
@@ -0,0 +1,80 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::CommentFormatter, lib: true do
+ let(:project) { create(:project) }
+ let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') }
+ let(:created_at) { DateTime.strptime('2013-04-10T20:09:31Z') }
+ let(:updated_at) { DateTime.strptime('2014-03-03T18:58:10Z') }
+ let(:base_data) do
+ {
+ body: "I'm having a problem with this.",
+ user: octocat,
+ created_at: created_at,
+ updated_at: updated_at
+ }
+ end
+
+ subject(:comment) { described_class.new(project, raw_data)}
+
+ describe '#attributes' do
+ context 'when do not reference a portion of the diff' do
+ let(:raw_data) { OpenStruct.new(base_data) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ project: project,
+ note: "*Created by: octocat*\n\nI'm having a problem with this.",
+ commit_id: nil,
+ line_code: nil,
+ author_id: project.creator_id,
+ created_at: created_at,
+ updated_at: updated_at
+ }
+
+ expect(comment.attributes).to eq(expected)
+ end
+ end
+
+ context 'when on a portion of the diff' do
+ let(:diff_data) do
+ {
+ body: 'Great stuff',
+ commit_id: '6dcb09b5b57875f334f61aebed695e2e4193db5e',
+ diff_hunk: '@@ -16,33 +16,40 @@ public class Connection : IConnection...',
+ path: 'file1.txt',
+ position: 1
+ }
+ end
+
+ let(:raw_data) { OpenStruct.new(base_data.merge(diff_data)) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ project: project,
+ note: "*Created by: octocat*\n\nGreat stuff",
+ commit_id: '6dcb09b5b57875f334f61aebed695e2e4193db5e',
+ line_code: 'ce1be0ff4065a6e9415095c95f25f47a633cef2b_0_1',
+ author_id: project.creator_id,
+ created_at: created_at,
+ updated_at: updated_at
+ }
+
+ expect(comment.attributes).to eq(expected)
+ end
+ end
+
+ context 'when author is a GitLab user' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(user: octocat)) }
+
+ it 'returns project#creator_id as author_id when is not a GitLab user' do
+ expect(comment.attributes.fetch(:author_id)).to eq project.creator_id
+ end
+
+ it 'returns GitLab user id as author_id when is a GitLab user' do
+ gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+ expect(comment.attributes.fetch(:author_id)).to eq gl_user.id
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/issue_formatter_spec.rb b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
new file mode 100644
index 00000000000..fd05428b322
--- /dev/null
+++ b/spec/lib/gitlab/github_import/issue_formatter_spec.rb
@@ -0,0 +1,139 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::IssueFormatter, lib: true do
+ let!(:project) { create(:project, namespace: create(:namespace, path: 'octocat')) }
+ let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') }
+ let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
+ let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
+
+ let(:base_data) do
+ {
+ number: 1347,
+ state: 'open',
+ title: 'Found a bug',
+ body: "I'm having a problem with this.",
+ assignee: nil,
+ user: octocat,
+ comments: 0,
+ pull_request: nil,
+ created_at: created_at,
+ updated_at: updated_at,
+ closed_at: nil
+ }
+ end
+
+ subject(:issue) { described_class.new(project, raw_data)}
+
+ describe '#attributes' do
+ context 'when issue is open' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(state: 'open')) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ project: project,
+ title: 'Found a bug',
+ description: "*Created by: octocat*\n\nI'm having a problem with this.",
+ state: 'opened',
+ author_id: project.creator_id,
+ assignee_id: nil,
+ created_at: created_at,
+ updated_at: updated_at
+ }
+
+ expect(issue.attributes).to eq(expected)
+ end
+ end
+
+ context 'when issue is closed' do
+ let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
+ let(:raw_data) { OpenStruct.new(base_data.merge(state: 'closed', closed_at: closed_at)) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ project: project,
+ title: 'Found a bug',
+ description: "*Created by: octocat*\n\nI'm having a problem with this.",
+ state: 'closed',
+ author_id: project.creator_id,
+ assignee_id: nil,
+ created_at: created_at,
+ updated_at: closed_at
+ }
+
+ expect(issue.attributes).to eq(expected)
+ end
+ end
+
+ context 'when it is assigned to someone' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(assignee: octocat)) }
+
+ it 'returns nil as assignee_id when is not a GitLab user' do
+ expect(issue.attributes.fetch(:assignee_id)).to be_nil
+ end
+
+ it 'returns GitLab user id as assignee_id when is a GitLab user' do
+ gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+ expect(issue.attributes.fetch(:assignee_id)).to eq gl_user.id
+ end
+ end
+
+ context 'when author is a GitLab user' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(user: octocat)) }
+
+ it 'returns project#creator_id as author_id when is not a GitLab user' do
+ expect(issue.attributes.fetch(:author_id)).to eq project.creator_id
+ end
+
+ it 'returns GitLab user id as author_id when is a GitLab user' do
+ gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+ expect(issue.attributes.fetch(:author_id)).to eq gl_user.id
+ end
+ end
+ end
+
+ describe '#has_comments?' do
+ context 'when number of comments is greater than zero' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(comments: 1)) }
+
+ it 'returns true' do
+ expect(issue.has_comments?).to eq true
+ end
+ end
+
+ context 'when number of comments is equal to zero' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(comments: 0)) }
+
+ it 'returns false' do
+ expect(issue.has_comments?).to eq false
+ end
+ end
+ end
+
+ describe '#number' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(number: 1347)) }
+
+ it 'returns pull request number' do
+ expect(issue.number).to eq 1347
+ end
+ end
+
+ describe '#valid?' do
+ context 'when mention a pull request' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(pull_request: OpenStruct.new)) }
+
+ it 'returns false' do
+ expect(issue.valid?).to eq false
+ end
+ end
+
+ context 'when does not mention a pull request' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(pull_request: nil)) }
+
+ it 'returns true' do
+ expect(issue.valid?).to eq true
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
new file mode 100644
index 00000000000..9aefec77f6d
--- /dev/null
+++ b/spec/lib/gitlab/github_import/pull_request_formatter_spec.rb
@@ -0,0 +1,184 @@
+require 'spec_helper'
+
+describe Gitlab::GithubImport::PullRequestFormatter, lib: true do
+ let(:project) { create(:project) }
+ let(:source_branch) { OpenStruct.new(ref: 'feature') }
+ let(:target_branch) { OpenStruct.new(ref: 'master') }
+ let(:octocat) { OpenStruct.new(id: 123456, login: 'octocat') }
+ let(:created_at) { DateTime.strptime('2011-01-26T19:01:12Z') }
+ let(:updated_at) { DateTime.strptime('2011-01-27T19:01:12Z') }
+ let(:base_data) do
+ {
+ number: 1347,
+ state: 'open',
+ title: 'New feature',
+ body: 'Please pull these awesome changes',
+ head: source_branch,
+ base: target_branch,
+ assignee: nil,
+ user: octocat,
+ created_at: created_at,
+ updated_at: updated_at,
+ closed_at: nil,
+ merged_at: nil
+ }
+ end
+
+ subject(:pull_request) { described_class.new(project, raw_data)}
+
+ describe '#attributes' do
+ context 'when pull request is open' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(state: 'open')) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ title: 'New feature',
+ description: "*Created by: octocat*\n\nPlease pull these awesome changes",
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master',
+ state: 'opened',
+ author_id: project.creator_id,
+ assignee_id: nil,
+ created_at: created_at,
+ updated_at: updated_at
+ }
+
+ expect(pull_request.attributes).to eq(expected)
+ end
+ end
+
+ context 'when pull request is closed' do
+ let(:closed_at) { DateTime.strptime('2011-01-28T19:01:12Z') }
+ let(:raw_data) { OpenStruct.new(base_data.merge(state: 'closed', closed_at: closed_at)) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ title: 'New feature',
+ description: "*Created by: octocat*\n\nPlease pull these awesome changes",
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master',
+ state: 'closed',
+ author_id: project.creator_id,
+ assignee_id: nil,
+ created_at: created_at,
+ updated_at: closed_at
+ }
+
+ expect(pull_request.attributes).to eq(expected)
+ end
+ end
+
+ context 'when pull request is merged' do
+ let(:merged_at) { DateTime.strptime('2011-01-28T13:01:12Z') }
+ let(:raw_data) { OpenStruct.new(base_data.merge(state: 'closed', merged_at: merged_at)) }
+
+ it 'returns formatted attributes' do
+ expected = {
+ title: 'New feature',
+ description: "*Created by: octocat*\n\nPlease pull these awesome changes",
+ source_project: project,
+ source_branch: 'feature',
+ target_project: project,
+ target_branch: 'master',
+ state: 'merged',
+ author_id: project.creator_id,
+ assignee_id: nil,
+ created_at: created_at,
+ updated_at: merged_at
+ }
+
+ expect(pull_request.attributes).to eq(expected)
+ end
+ end
+
+ context 'when it is assigned to someone' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(assignee: octocat)) }
+
+ it 'returns nil as assignee_id when is not a GitLab user' do
+ expect(pull_request.attributes.fetch(:assignee_id)).to be_nil
+ end
+
+ it 'returns GitLab user id as assignee_id when is a GitLab user' do
+ gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+ expect(pull_request.attributes.fetch(:assignee_id)).to eq gl_user.id
+ end
+ end
+
+ context 'when author is a GitLab user' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(user: octocat)) }
+
+ it 'returns project#creator_id as author_id when is not a GitLab user' do
+ expect(pull_request.attributes.fetch(:author_id)).to eq project.creator_id
+ end
+
+ it 'returns GitLab user id as author_id when is a GitLab user' do
+ gl_user = create(:omniauth_user, extern_uid: octocat.id, provider: 'github')
+
+ expect(pull_request.attributes.fetch(:author_id)).to eq gl_user.id
+ end
+ end
+ end
+
+ describe '#cross_project?' do
+ context 'when source repo is not a fork' do
+ let(:local_repo) { OpenStruct.new(fork: false) }
+ let(:source_branch) { OpenStruct.new(ref: 'feature', repo: local_repo) }
+ let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch)) }
+
+ it 'returns false' do
+ expect(pull_request.cross_project?).to eq false
+ end
+ end
+
+ context 'when source repo is a fork' do
+ let(:forked_repo) { OpenStruct.new(fork: true) }
+ let(:source_branch) { OpenStruct.new(ref: 'feature', repo: forked_repo) }
+ let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch)) }
+
+ it 'returns true' do
+ expect(pull_request.cross_project?).to eq true
+ end
+ end
+ end
+
+ describe '#number' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(number: 1347)) }
+
+ it 'returns pull request number' do
+ expect(pull_request.number).to eq 1347
+ end
+ end
+
+ describe '#valid?' do
+ let(:invalid_branch) { OpenStruct.new(ref: 'invalid-branch') }
+
+ context 'when source and target branches exists' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: target_branch)) }
+
+ it 'returns true' do
+ expect(pull_request.valid?).to eq true
+ end
+ end
+
+ context 'when source branch doesn not exists' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(head: invalid_branch, base: target_branch)) }
+
+ it 'returns false' do
+ expect(pull_request.valid?).to eq false
+ end
+ end
+
+ context 'when target branch doesn not exists' do
+ let(:raw_data) { OpenStruct.new(base_data.merge(head: source_branch, base: invalid_branch)) }
+
+ it 'returns false' do
+ expect(pull_request.valid?).to eq false
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/instrumentation_spec.rb b/spec/lib/gitlab/metrics/instrumentation_spec.rb
index a7eab9d11cc..2a37cd40dde 100644
--- a/spec/lib/gitlab/metrics/instrumentation_spec.rb
+++ b/spec/lib/gitlab/metrics/instrumentation_spec.rb
@@ -48,6 +48,9 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction).
and_return(transaction)
+ expect(transaction).to receive(:increment).
+ with(:method_duration, a_kind_of(Numeric))
+
expect(transaction).to receive(:add_metric).
with(described_class::SERIES, an_instance_of(Hash),
method: 'Dummy.foo')
@@ -102,6 +105,9 @@ describe Gitlab::Metrics::Instrumentation do
allow(described_class).to receive(:transaction).
and_return(transaction)
+ expect(transaction).to receive(:increment).
+ with(:method_duration, a_kind_of(Numeric))
+
expect(transaction).to receive(:add_metric).
with(described_class::SERIES, an_instance_of(Hash),
method: 'Dummy#bar')
diff --git a/spec/lib/gitlab/metrics/metric_spec.rb b/spec/lib/gitlab/metrics/metric_spec.rb
index aa76315c79c..f718d536130 100644
--- a/spec/lib/gitlab/metrics/metric_spec.rb
+++ b/spec/lib/gitlab/metrics/metric_spec.rb
@@ -37,9 +37,6 @@ describe Gitlab::Metrics::Metric do
it 'includes the tags' do
expect(hash[:tags]).to be_an_instance_of(Hash)
-
- expect(hash[:tags][:hostname]).to be_an_instance_of(String)
- expect(hash[:tags][:process_type]).to be_an_instance_of(String)
end
it 'includes the values' do
diff --git a/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb b/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb
deleted file mode 100644
index 2b681c9fe34..00000000000
--- a/spec/lib/gitlab/metrics/obfuscated_sql_spec.rb
+++ /dev/null
@@ -1,93 +0,0 @@
-require 'spec_helper'
-
-describe Gitlab::Metrics::ObfuscatedSQL do
- describe '#to_s' do
- it 'replaces newlines with a space' do
- sql = described_class.new("SELECT x\nFROM y")
-
- expect(sql.to_s).to eq('SELECT x FROM y')
- end
-
- describe 'using single values' do
- it 'replaces a single integer' do
- sql = described_class.new('SELECT x FROM y WHERE a = 10')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
- end
-
- it 'replaces a single float' do
- sql = described_class.new('SELECT x FROM y WHERE a = 10.5')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
- end
-
- it 'replaces a single quoted string' do
- sql = described_class.new("SELECT x FROM y WHERE a = 'foo'")
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
- end
-
- if Gitlab::Database.mysql?
- it 'replaces a double quoted string' do
- sql = described_class.new('SELECT x FROM y WHERE a = "foo"')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
- end
- end
-
- it 'replaces a single regular expression' do
- sql = described_class.new('SELECT x FROM y WHERE a = /foo/')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
- end
-
- it 'replaces regular expressions using escaped slashes' do
- sql = described_class.new('SELECT x FROM y WHERE a = /foo\/bar/')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE a = ?')
- end
- end
-
- describe 'using consecutive values' do
- it 'replaces multiple integers' do
- sql = described_class.new('SELECT x FROM y WHERE z IN (10, 20, 30)')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (3 values)')
- end
-
- it 'replaces multiple floats' do
- sql = described_class.new('SELECT x FROM y WHERE z IN (1.5, 2.5, 3.5)')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (3 values)')
- end
-
- it 'replaces multiple single quoted strings' do
- sql = described_class.new("SELECT x FROM y WHERE z IN ('foo', 'bar')")
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (2 values)')
- end
-
- if Gitlab::Database.mysql?
- it 'replaces multiple double quoted strings' do
- sql = described_class.new('SELECT x FROM y WHERE z IN ("foo", "bar")')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (2 values)')
- end
- end
-
- it 'replaces multiple regular expressions' do
- sql = described_class.new('SELECT x FROM y WHERE z IN (/foo/, /bar/)')
-
- expect(sql.to_s).to eq('SELECT x FROM y WHERE z IN (2 values)')
- end
- end
-
- if Gitlab::Database.postgresql?
- it 'replaces double quotes' do
- sql = described_class.new('SELECT "x" FROM "y"')
-
- expect(sql.to_s).to eq('SELECT x FROM y')
- end
- end
- end
-end
diff --git a/spec/lib/gitlab/metrics/rack_middleware_spec.rb b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
index a143fe4cfcd..4e6dfc73df2 100644
--- a/spec/lib/gitlab/metrics/rack_middleware_spec.rb
+++ b/spec/lib/gitlab/metrics/rack_middleware_spec.rb
@@ -40,9 +40,9 @@ describe Gitlab::Metrics::RackMiddleware do
expect(transaction).to be_an_instance_of(Gitlab::Metrics::Transaction)
end
- it 'tags the transaction with the request method and URI' do
- expect(transaction.tags[:request_method]).to eq('GET')
- expect(transaction.tags[:request_uri]).to eq('/foo')
+ it 'stores the request method and URI in the transaction as values' do
+ expect(transaction.values[:request_method]).to eq('GET')
+ expect(transaction.values[:request_uri]).to eq('/foo')
end
end
diff --git a/spec/lib/gitlab/metrics/sampler_spec.rb b/spec/lib/gitlab/metrics/sampler_spec.rb
index 51a941c48cd..27211350fbe 100644
--- a/spec/lib/gitlab/metrics/sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/sampler_spec.rb
@@ -51,8 +51,8 @@ describe Gitlab::Metrics::Sampler do
expect(Gitlab::Metrics::System).to receive(:memory_usage).
and_return(9000)
- expect(Gitlab::Metrics::Metric).to receive(:new).
- with('memory_usage', value: 9000).
+ expect(sampler).to receive(:add_metric).
+ with(/memory_usage/, value: 9000).
and_call_original
sampler.sample_memory_usage
@@ -64,8 +64,8 @@ describe Gitlab::Metrics::Sampler do
expect(Gitlab::Metrics::System).to receive(:file_descriptor_count).
and_return(4)
- expect(Gitlab::Metrics::Metric).to receive(:new).
- with('file_descriptors', value: 4).
+ expect(sampler).to receive(:add_metric).
+ with(/file_descriptors/, value: 4).
and_call_original
sampler.sample_file_descriptors
@@ -74,8 +74,8 @@ describe Gitlab::Metrics::Sampler do
describe '#sample_objects' do
it 'adds a metric containing the amount of allocated objects' do
- expect(Gitlab::Metrics::Metric).to receive(:new).
- with('object_counts', an_instance_of(Hash), an_instance_of(Hash)).
+ expect(sampler).to receive(:add_metric).
+ with(/object_counts/, an_instance_of(Hash), an_instance_of(Hash)).
at_least(:once).
and_call_original
@@ -87,11 +87,33 @@ describe Gitlab::Metrics::Sampler do
it 'adds a metric containing garbage collection statistics' do
expect(GC::Profiler).to receive(:total_time).and_return(0.24)
- expect(Gitlab::Metrics::Metric).to receive(:new).
- with('gc_statistics', an_instance_of(Hash)).
+ expect(sampler).to receive(:add_metric).
+ with(/gc_statistics/, an_instance_of(Hash)).
and_call_original
sampler.sample_gc
end
end
+
+ describe '#add_metric' do
+ it 'prefixes the series name for a Rails process' do
+ expect(sampler).to receive(:sidekiq?).and_return(false)
+
+ expect(Gitlab::Metrics::Metric).to receive(:new).
+ with('rails_cats', { value: 10 }, {}).
+ and_call_original
+
+ sampler.add_metric('cats', value: 10)
+ end
+
+ it 'prefixes the series name for a Sidekiq process' do
+ expect(sampler).to receive(:sidekiq?).and_return(true)
+
+ expect(Gitlab::Metrics::Metric).to receive(:new).
+ with('sidekiq_cats', { value: 10 }, {}).
+ and_call_original
+
+ sampler.add_metric('cats', value: 10)
+ end
+ end
end
diff --git a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
index c6cd584663f..05e4fbbeb51 100644
--- a/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/action_view_spec.rb
@@ -28,6 +28,9 @@ describe Gitlab::Metrics::Subscribers::ActionView do
line: 4
}
+ expect(transaction).to receive(:increment).
+ with(:view_duration, 2.1)
+
expect(transaction).to receive(:add_metric).
with(described_class::SERIES, values, tags)
diff --git a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
index 05b6cc14716..7bc070a4d09 100644
--- a/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
+++ b/spec/lib/gitlab/metrics/subscribers/active_record_spec.rb
@@ -2,31 +2,34 @@ require 'spec_helper'
describe Gitlab::Metrics::Subscribers::ActiveRecord do
let(:transaction) { Gitlab::Metrics::Transaction.new }
-
- let(:subscriber) { described_class.new }
+ let(:subscriber) { described_class.new }
let(:event) do
double(:event, duration: 0.2,
payload: { sql: 'SELECT * FROM users WHERE id = 10' })
end
- before do
- allow(subscriber).to receive(:current_transaction).and_return(transaction)
+ describe '#sql' do
+ describe 'without a current transaction' do
+ it 'simply returns' do
+ expect_any_instance_of(Gitlab::Metrics::Transaction).
+ to_not receive(:increment)
- allow(Gitlab::Metrics).to receive(:last_relative_application_frame).
- and_return(['app/models/foo.rb', 4])
- end
+ subscriber.sql(event)
+ end
+ end
- describe '#sql' do
- it 'tracks the execution of a SQL query' do
- sql = 'SELECT * FROM users WHERE id = ?'
- values = { duration: 0.2 }
- tags = { sql: sql, file: 'app/models/foo.rb', line: 4 }
+ describe 'with a current transaction' do
+ it 'increments the :sql_duration value' do
+ expect(subscriber).to receive(:current_transaction).
+ at_least(:once).
+ and_return(transaction)
- expect(transaction).to receive(:add_metric).
- with(described_class::SERIES, values, tags)
+ expect(transaction).to receive(:increment).
+ with(:sql_duration, 0.2)
- subscriber.sql(event)
+ subscriber.sql(event)
+ end
end
end
end
diff --git a/spec/lib/gitlab/metrics/transaction_spec.rb b/spec/lib/gitlab/metrics/transaction_spec.rb
index 6862fc9e2d1..3a27f897735 100644
--- a/spec/lib/gitlab/metrics/transaction_spec.rb
+++ b/spec/lib/gitlab/metrics/transaction_spec.rb
@@ -30,14 +30,37 @@ describe Gitlab::Metrics::Transaction do
end
describe '#add_metric' do
- it 'adds a metric tagged with the transaction UUID' do
+ it 'adds a metric to the transaction' do
expect(Gitlab::Metrics::Metric).to receive(:new).
- with('foo', { number: 10 }, { transaction_id: transaction.uuid })
+ with('rails_foo', { number: 10 }, {})
transaction.add_metric('foo', number: 10)
end
end
+ describe '#increment' do
+ it 'increments a counter' do
+ transaction.increment(:time, 1)
+ transaction.increment(:time, 2)
+
+ expect(transaction).to receive(:add_metric).
+ with('transactions', { duration: 0.0, time: 3 }, {})
+
+ transaction.track_self
+ end
+ end
+
+ describe '#set' do
+ it 'sets a value' do
+ transaction.set(:number, 10)
+
+ expect(transaction).to receive(:add_metric).
+ with('transactions', { duration: 0.0, number: 10 }, {})
+
+ transaction.track_self
+ end
+ end
+
describe '#add_tag' do
it 'adds a tag' do
transaction.add_tag(:foo, 'bar')
@@ -58,7 +81,7 @@ describe Gitlab::Metrics::Transaction do
describe '#track_self' do
it 'adds a metric for the transaction itself' do
expect(transaction).to receive(:add_metric).
- with(described_class::SERIES, { duration: transaction.duration }, {})
+ with('transactions', { duration: transaction.duration }, {})
transaction.track_self
end
diff --git a/spec/lib/gitlab/metrics_spec.rb b/spec/lib/gitlab/metrics_spec.rb
index 6c0682cac4d..c2782f95c8e 100644
--- a/spec/lib/gitlab/metrics_spec.rb
+++ b/spec/lib/gitlab/metrics_spec.rb
@@ -1,15 +1,9 @@
require 'spec_helper'
describe Gitlab::Metrics do
- describe '.pool_size' do
- it 'returns a Fixnum' do
- expect(described_class.pool_size).to be_an_instance_of(Fixnum)
- end
- end
-
- describe '.timeout' do
- it 'returns a Fixnum' do
- expect(described_class.timeout).to be_an_instance_of(Fixnum)
+ describe '.settings' do
+ it 'returns a Hash' do
+ expect(described_class.settings).to be_an_instance_of(Hash)
end
end
@@ -19,12 +13,6 @@ describe Gitlab::Metrics do
end
end
- describe '.hostname' do
- it 'returns a String containing the hostname' do
- expect(described_class.hostname).to eq(Socket.gethostname)
- end
- end
-
describe '.last_relative_application_frame' do
it 'returns an Array containing a file path and line number' do
file, line = described_class.last_relative_application_frame
diff --git a/spec/mailers/abuse_report_mailer_spec.rb b/spec/mailers/abuse_report_mailer_spec.rb
new file mode 100644
index 00000000000..eb433c38873
--- /dev/null
+++ b/spec/mailers/abuse_report_mailer_spec.rb
@@ -0,0 +1,38 @@
+require 'rails_helper'
+
+describe AbuseReportMailer do
+ include EmailSpec::Matchers
+
+ describe '.notify' do
+ context 'with admin_notification_email set' do
+ before do
+ stub_application_setting(admin_notification_email: 'admin@example.com')
+ end
+
+ it 'sends to the admin_notification_email' do
+ report = create(:abuse_report)
+
+ mail = described_class.notify(report.id)
+
+ expect(mail).to deliver_to 'admin@example.com'
+ end
+
+ it 'includes the user in the subject' do
+ report = create(:abuse_report)
+
+ mail = described_class.notify(report.id)
+
+ expect(mail).to have_subject "#{report.user.name} (#{report.user.username}) was reported for abuse"
+ end
+ end
+
+ context 'with no admin_notification_email set' do
+ it 'returns early' do
+ stub_application_setting(admin_notification_email: nil)
+
+ expect { described_class.notify(spy).deliver_now }.
+ not_to change { ActionMailer::Base.deliveries.count }
+ end
+ end
+ end
+end
diff --git a/spec/models/abuse_report_spec.rb b/spec/models/abuse_report_spec.rb
index d45319b25d4..46cab1644c7 100644
--- a/spec/models/abuse_report_spec.rb
+++ b/spec/models/abuse_report_spec.rb
@@ -28,4 +28,21 @@ RSpec.describe AbuseReport, type: :model do
it { is_expected.to validate_presence_of(:message) }
it { is_expected.to validate_uniqueness_of(:user_id) }
end
+
+ describe '#notify' do
+ it 'delivers' do
+ expect(AbuseReportMailer).to receive(:notify).with(subject.id).
+ and_return(spy)
+
+ subject.notify
+ end
+
+ it 'returns early when not persisted' do
+ report = build(:abuse_report)
+
+ expect(AbuseReportMailer).not_to receive(:notify)
+
+ report.notify
+ end
+ end
end
diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb
index 35d8220ae54..91b250265e6 100644
--- a/spec/models/application_setting_spec.rb
+++ b/spec/models/application_setting_spec.rb
@@ -2,32 +2,45 @@
#
# Table name: application_settings
#
-# id :integer not null, primary key
-# default_projects_limit :integer
-# signup_enabled :boolean
-# signin_enabled :boolean
-# gravatar_enabled :boolean
-# sign_in_text :text
-# created_at :datetime
-# updated_at :datetime
-# home_page_url :string(255)
-# default_branch_protection :integer default(2)
-# twitter_sharing_enabled :boolean default(TRUE)
-# restricted_visibility_levels :text
-# version_check_enabled :boolean default(TRUE)
-# max_attachment_size :integer default(10), not null
-# default_project_visibility :integer
-# default_snippet_visibility :integer
-# restricted_signup_domains :text
-# user_oauth_applications :boolean default(TRUE)
-# after_sign_out_path :string(255)
-# session_expire_delay :integer default(10080), not null
-# import_sources :text
-# help_page_text :text
-# admin_notification_email :string(255)
-# shared_runners_enabled :boolean default(TRUE), not null
-# max_artifacts_size :integer default(100), not null
-# runners_registration_token :string(255)
+# id :integer not null, primary key
+# default_projects_limit :integer
+# signup_enabled :boolean
+# signin_enabled :boolean
+# gravatar_enabled :boolean
+# sign_in_text :text
+# created_at :datetime
+# updated_at :datetime
+# home_page_url :string(255)
+# default_branch_protection :integer default(2)
+# twitter_sharing_enabled :boolean default(TRUE)
+# restricted_visibility_levels :text
+# version_check_enabled :boolean default(TRUE)
+# max_attachment_size :integer default(10), not null
+# default_project_visibility :integer
+# default_snippet_visibility :integer
+# restricted_signup_domains :text
+# user_oauth_applications :boolean default(TRUE)
+# after_sign_out_path :string(255)
+# session_expire_delay :integer default(10080), not null
+# import_sources :text
+# help_page_text :text
+# admin_notification_email :string(255)
+# shared_runners_enabled :boolean default(TRUE), not null
+# max_artifacts_size :integer default(100), not null
+# runners_registration_token :string
+# require_two_factor_authentication :boolean default(FALSE)
+# two_factor_grace_period :integer default(48)
+# metrics_enabled :boolean default(FALSE)
+# metrics_host :string default("localhost")
+# metrics_username :string
+# metrics_password :string
+# metrics_pool_size :integer default(16)
+# metrics_timeout :integer default(10)
+# metrics_method_call_threshold :integer default(10)
+# recaptcha_enabled :boolean default(FALSE)
+# recaptcha_site_key :string
+# recaptcha_private_key :string
+# metrics_port :integer default(8089)
#
require 'spec_helper'
diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb
new file mode 100644
index 00000000000..36d10636ae9
--- /dev/null
+++ b/spec/models/ci/build_spec.rb
@@ -0,0 +1,22 @@
+require 'spec_helper'
+
+describe Ci::Build, models: true do
+ let(:build) { create(:ci_build) }
+ let(:test_trace) { 'This is a test' }
+
+ describe '#trace' do
+ it 'obfuscates project runners token' do
+ allow(build).to receive(:raw_trace).and_return("Test: #{build.project.runners_token}")
+
+ expect(build.trace).to eq("Test: xxxxxx")
+ end
+
+ it 'empty project runners token' do
+ allow(build).to receive(:raw_trace).and_return(test_trace)
+ # runners_token can't normally be set to nil
+ allow(build.project).to receive(:runners_token).and_return(nil)
+
+ expect(build.trace).to eq(test_trace)
+ end
+ end
+end
diff --git a/spec/models/ci/commit_spec.rb b/spec/models/ci/commit_spec.rb
index b193e16e7f8..dfc0cc3be1c 100644
--- a/spec/models/ci/commit_spec.rb
+++ b/spec/models/ci/commit_spec.rb
@@ -13,7 +13,7 @@
# tag :boolean default(FALSE)
# yaml_errors :text
# committed_at :datetime
-# project_id :integer
+# gl_project_id :integer
#
require 'spec_helper'
diff --git a/spec/models/ci/runner_project_spec.rb b/spec/models/ci/runner_project_spec.rb
index da8491357a5..000a732db77 100644
--- a/spec/models/ci/runner_project_spec.rb
+++ b/spec/models/ci/runner_project_spec.rb
@@ -2,11 +2,12 @@
#
# Table name: ci_runner_projects
#
-# id :integer not null, primary key
-# runner_id :integer not null
-# project_id :integer not null
-# created_at :datetime
-# updated_at :datetime
+# id :integer not null, primary key
+# runner_id :integer not null
+# project_id :integer
+# created_at :datetime
+# updated_at :datetime
+# gl_project_id :integer
#
require 'spec_helper'
diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb
index cb2f51e2011..159be939300 100644
--- a/spec/models/ci/trigger_spec.rb
+++ b/spec/models/ci/trigger_spec.rb
@@ -2,12 +2,13 @@
#
# Table name: ci_triggers
#
-# id :integer not null, primary key
-# token :string(255)
-# project_id :integer not null
-# deleted_at :datetime
-# created_at :datetime
-# updated_at :datetime
+# id :integer not null, primary key
+# token :string(255)
+# project_id :integer
+# deleted_at :datetime
+# created_at :datetime
+# updated_at :datetime
+# gl_project_id :integer
#
require 'spec_helper'
diff --git a/spec/models/ci/variable_spec.rb b/spec/models/ci/variable_spec.rb
index 31b56953a13..71e84091cb7 100644
--- a/spec/models/ci/variable_spec.rb
+++ b/spec/models/ci/variable_spec.rb
@@ -3,12 +3,13 @@
# Table name: ci_variables
#
# id :integer not null, primary key
-# project_id :integer not null
+# project_id :integer
# key :string(255)
# value :text
# encrypted_value :text
# encrypted_value_salt :string(255)
# encrypted_value_iv :string(255)
+# gl_project_id :integer
#
require 'spec_helper'
diff --git a/spec/models/commit_status_spec.rb b/spec/models/commit_status_spec.rb
index b8f901b3433..82c68ff6cb1 100644
--- a/spec/models/commit_status_spec.rb
+++ b/spec/models/commit_status_spec.rb
@@ -29,6 +29,7 @@
# target_url :string(255)
# description :string(255)
# artifacts_file :text
+# gl_project_id :integer
#
require 'spec_helper'
diff --git a/spec/models/external_wiki_service_spec.rb b/spec/models/external_wiki_service_spec.rb
index b198aa77526..d37978720bf 100644
--- a/spec/models/external_wiki_service_spec.rb
+++ b/spec/models/external_wiki_service_spec.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
require 'spec_helper'
diff --git a/spec/models/generic_commit_status_spec.rb b/spec/models/generic_commit_status_spec.rb
index d61c1c96bde..5b0883d8702 100644
--- a/spec/models/generic_commit_status_spec.rb
+++ b/spec/models/generic_commit_status_spec.rb
@@ -29,6 +29,7 @@
# target_url :string(255)
# description :string(255)
# artifacts_file :text
+# gl_project_id :integer
#
require 'spec_helper'
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 646f767e6fe..3c995053eec 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -11,7 +11,6 @@
# type :string(255)
# description :string(255) default(""), not null
# avatar :string(255)
-# public :boolean default(FALSE)
#
require 'spec_helper'
@@ -38,14 +37,6 @@ describe Group, models: true do
it { is_expected.not_to validate_presence_of :owner }
end
- describe '.public_and_given_groups' do
- let!(:public_group) { create(:group, public: true) }
-
- subject { described_class.public_and_given_groups([group.id]) }
-
- it { is_expected.to eq([public_group, group]) }
- end
-
describe '.visible_to_user' do
let!(:group) { create(:group) }
let!(:user) { create(:user) }
@@ -112,23 +103,4 @@ describe Group, models: true do
expect(group.avatar_type).to eq(["only images allowed"])
end
end
-
- describe "public_profile?" do
- it "returns true for public group" do
- group = create(:group, public: true)
- expect(group.public_profile?).to be_truthy
- end
-
- it "returns true for non-public group with public project" do
- group = create(:group)
- create(:project, :public, group: group)
- expect(group.public_profile?).to be_truthy
- end
-
- it "returns false for non-public group with no public projects" do
- group = create(:group)
- create(:project, group: group)
- expect(group.public_profile?).to be_falsy
- end
- end
end
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 2d90b0793cc..7070aa4ac62 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -77,5 +77,17 @@ describe ProjectHook, models: true do
expect(@project_hook.execute(@data, 'push_hooks')).to eq([false, 'SSL error'])
end
+
+ it "handles 200 status code" do
+ WebMock.stub_request(:post, @project_hook.url).to_return(status: 200, body: "Success")
+
+ expect(@project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
+ end
+
+ it "handles 2xx status codes" do
+ WebMock.stub_request(:post, @project_hook.url).to_return(status: 201, body: "Success")
+
+ expect(@project_hook.execute(@data, 'push_hooks')).to eq([true, 'Success'])
+ end
end
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index e0653a8327d..291e6200a5b 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -2,25 +2,28 @@
#
# Table name: merge_requests
#
-# id :integer not null, primary key
-# target_branch :string(255) not null
-# source_branch :string(255) not null
-# source_project_id :integer not null
-# author_id :integer
-# assignee_id :integer
-# title :string(255)
-# created_at :datetime
-# updated_at :datetime
-# milestone_id :integer
-# state :string(255)
-# merge_status :string(255)
-# target_project_id :integer not null
-# iid :integer
-# description :text
-# position :integer default(0)
-# locked_at :datetime
-# updated_by_id :integer
-# merge_error :string(255)
+# id :integer not null, primary key
+# target_branch :string(255) not null
+# source_branch :string(255) not null
+# source_project_id :integer not null
+# author_id :integer
+# assignee_id :integer
+# title :string(255)
+# created_at :datetime
+# updated_at :datetime
+# milestone_id :integer
+# state :string(255)
+# merge_status :string(255)
+# target_project_id :integer not null
+# iid :integer
+# description :text
+# position :integer default(0)
+# locked_at :datetime
+# updated_by_id :integer
+# merge_error :string(255)
+# merge_params :text
+# merge_when_build_succeeds :boolean default(FALSE), not null
+# merge_user_id :integer
#
require 'spec_helper'
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index 4fa2d2bc4d2..e0b3290e416 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -11,7 +11,6 @@
# type :string(255)
# description :string(255) default(""), not null
# avatar :string(255)
-# public :boolean default(FALSE)
#
require 'spec_helper'
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 593d8f76215..151a29e974b 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -125,6 +125,19 @@ describe Note, models: true do
let(:set_mentionable_text) { ->(txt) { subject.note = txt } }
end
+ describe "#all_references" do
+ let!(:note1) { create(:note) }
+ let!(:note2) { create(:note) }
+
+ it "reads the rendered note body from the cache" do
+ expect(Banzai::Renderer).to receive(:render).with(note1.note, pipeline: :note, cache_key: [note1, "note"], project: note1.project)
+ expect(Banzai::Renderer).to receive(:render).with(note2.note, pipeline: :note, cache_key: [note2, "note"], project: note2.project)
+
+ note1.all_references
+ note2.all_references
+ end
+ end
+
describe :search do
let!(:note) { create(:note, note: "WoW") }
@@ -164,7 +177,7 @@ describe Note, models: true do
expect(note.editable?).to be_falsy
end
end
-
+
describe "set_award!" do
let(:issue) { create :issue }
diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb
index 64bb92fba95..f3d15f3c1ea 100644
--- a/spec/models/project_services/asana_service_spec.rb
+++ b/spec/models/project_services/asana_service_spec.rb
@@ -40,6 +40,20 @@ describe AsanaService, models: true do
let(:user) { create(:user) }
let(:project) { create(:project) }
+ def create_data_for_commits(*messages)
+ {
+ object_kind: 'push',
+ ref: 'master',
+ user_name: user.name,
+ commits: messages.map do |m|
+ {
+ message: m,
+ url: 'https://gitlab.com/',
+ }
+ end
+ }
+ end
+
before do
@asana = AsanaService.new
allow(@asana).to receive_messages(
@@ -51,16 +65,67 @@ describe AsanaService, models: true do
)
end
- it 'should call Asana service to created a story' do
- expect(Asana::Task).to receive(:find).with('123456').once
+ it 'should call Asana service to create a story' do
+ data = create_data_for_commits('Message from commit. related to #123456')
+ expected_message = "#{data[:user_name]} pushed to branch #{data[:ref]} of #{project.name_with_namespace} ( #{data[:commits][0][:url]} ): #{data[:commits][0][:message]}"
- @asana.check_commit('related to #123456', 'pushed')
+ d1 = double('Asana::Task')
+ expect(d1).to receive(:add_comment).with(text: expected_message)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '123456').once.and_return(d1)
+
+ @asana.execute(data)
end
- it 'should call Asana service to created a story and close a task' do
- expect(Asana::Task).to receive(:find).with('456789').twice
+ it 'should call Asana service to create a story and close a task' do
+ data = create_data_for_commits('fix #456789')
+ d1 = double('Asana::Task')
+ expect(d1).to receive(:add_comment)
+ expect(d1).to receive(:update).with(completed: true)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '456789').once.and_return(d1)
+
+ @asana.execute(data)
+ end
+
+ it 'should be able to close via url' do
+ data = create_data_for_commits('closes https://app.asana.com/19292/956299/42')
+ d1 = double('Asana::Task')
+ expect(d1).to receive(:add_comment)
+ expect(d1).to receive(:update).with(completed: true)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '42').once.and_return(d1)
+
+ @asana.execute(data)
+ end
+
+ it 'should allow multiple matches per line' do
+ message = <<-EOF
+ minor bigfix, refactoring, fixed #123 and Closes #456 work on #789
+ ref https://app.asana.com/19292/956299/42 and closing https://app.asana.com/19292/956299/12
+ EOF
+ data = create_data_for_commits(message)
+ d1 = double('Asana::Task')
+ expect(d1).to receive(:add_comment)
+ expect(d1).to receive(:update).with(completed: true)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '123').once.and_return(d1)
+
+ d2 = double('Asana::Task')
+ expect(d2).to receive(:add_comment)
+ expect(d2).to receive(:update).with(completed: true)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '456').once.and_return(d2)
+
+ d3 = double('Asana::Task')
+ expect(d3).to receive(:add_comment)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '789').once.and_return(d3)
+
+ d4 = double('Asana::Task')
+ expect(d4).to receive(:add_comment)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '42').once.and_return(d4)
+
+ d5 = double('Asana::Task')
+ expect(d5).to receive(:add_comment)
+ expect(d5).to receive(:update).with(completed: true)
+ expect(Asana::Task).to receive(:find_by_id).with(anything, '12').once.and_return(d5)
- @asana.check_commit('fix #456789', 'pushed')
+ @asana.execute(data)
end
end
end
diff --git a/spec/models/project_services/builds_email_service_spec.rb b/spec/models/project_services/builds_email_service_spec.rb
new file mode 100644
index 00000000000..905379a64e3
--- /dev/null
+++ b/spec/models/project_services/builds_email_service_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe BuildsEmailService do
+ let(:build) { create(:ci_build) }
+ let(:data) { Gitlab::BuildDataBuilder.build(build) }
+ let(:service) { BuildsEmailService.new }
+
+ describe :execute do
+ it "sends email" do
+ service.recipients = 'test@gitlab.com'
+ data[:build_status] = 'failed'
+ expect(BuildEmailWorker).to receive(:perform_async)
+ service.execute(data)
+ end
+
+ it "does not sends email with failed build and allowed_failure on" do
+ data[:build_status] = 'failed'
+ data[:build_allow_failure] = true
+ expect(BuildEmailWorker).not_to receive(:perform_async)
+ service.execute(data)
+ end
+ end
+end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 400bdf2d962..a3de23369e1 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -29,6 +29,13 @@
# import_source :string(255)
# commit_count :integer default(0)
# import_error :text
+# ci_id :integer
+# builds_enabled :boolean default(TRUE), not null
+# shared_runners_enabled :boolean default(TRUE), not null
+# runners_token :string
+# build_coverage_regex :string
+# build_allow_git_fetch :boolean default(TRUE), not null
+# build_timeout :integer default(3600), not null
#
require 'spec_helper'
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 0ca82365b98..173628c08d0 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -16,6 +16,7 @@
# merge_requests_events :boolean default(TRUE)
# tag_push_events :boolean default(TRUE)
# note_events :boolean default(TRUE), not null
+# build_events :boolean default(FALSE), not null
#
require 'spec_helper'
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 2f184bbaf92..3cd63b2b0e8 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -2,62 +2,63 @@
#
# Table name: users
#
-# id :integer not null, primary key
-# email :string(255) default(""), not null
-# encrypted_password :string(255) default(""), not null
-# reset_password_token :string(255)
-# reset_password_sent_at :datetime
-# remember_created_at :datetime
-# sign_in_count :integer default(0)
-# current_sign_in_at :datetime
-# last_sign_in_at :datetime
-# current_sign_in_ip :string(255)
-# last_sign_in_ip :string(255)
-# created_at :datetime
-# updated_at :datetime
-# name :string(255)
-# admin :boolean default(FALSE), not null
-# projects_limit :integer default(10)
-# skype :string(255) default(""), not null
-# linkedin :string(255) default(""), not null
-# twitter :string(255) default(""), not null
-# authentication_token :string(255)
-# theme_id :integer default(1), not null
-# bio :string(255)
-# failed_attempts :integer default(0)
-# locked_at :datetime
-# unlock_token :string(255)
-# username :string(255)
-# can_create_group :boolean default(TRUE), not null
-# can_create_team :boolean default(TRUE), not null
-# state :string(255)
-# color_scheme_id :integer default(1), not null
-# notification_level :integer default(1), not null
-# password_expires_at :datetime
-# created_by_id :integer
-# last_credential_check_at :datetime
-# avatar :string(255)
-# confirmation_token :string(255)
-# confirmed_at :datetime
-# confirmation_sent_at :datetime
-# unconfirmed_email :string(255)
-# hide_no_ssh_key :boolean default(FALSE)
-# website_url :string(255) default(""), not null
-# notification_email :string(255)
-# hide_no_password :boolean default(FALSE)
-# password_automatically_set :boolean default(FALSE)
-# location :string(255)
-# encrypted_otp_secret :string(255)
-# encrypted_otp_secret_iv :string(255)
-# encrypted_otp_secret_salt :string(255)
-# otp_required_for_login :boolean default(FALSE), not null
-# otp_backup_codes :text
-# public_email :string(255) default(""), not null
-# dashboard :integer default(0)
-# project_view :integer default(0)
-# consumed_timestep :integer
-# layout :integer default(0)
-# hide_project_limit :boolean default(FALSE)
+# id :integer not null, primary key
+# email :string(255) default(""), not null
+# encrypted_password :string(255) default(""), not null
+# reset_password_token :string(255)
+# reset_password_sent_at :datetime
+# remember_created_at :datetime
+# sign_in_count :integer default(0)
+# current_sign_in_at :datetime
+# last_sign_in_at :datetime
+# current_sign_in_ip :string(255)
+# last_sign_in_ip :string(255)
+# created_at :datetime
+# updated_at :datetime
+# name :string(255)
+# admin :boolean default(FALSE), not null
+# projects_limit :integer default(10)
+# skype :string(255) default(""), not null
+# linkedin :string(255) default(""), not null
+# twitter :string(255) default(""), not null
+# authentication_token :string(255)
+# theme_id :integer default(1), not null
+# bio :string(255)
+# failed_attempts :integer default(0)
+# locked_at :datetime
+# username :string(255)
+# can_create_group :boolean default(TRUE), not null
+# can_create_team :boolean default(TRUE), not null
+# state :string(255)
+# color_scheme_id :integer default(1), not null
+# notification_level :integer default(1), not null
+# password_expires_at :datetime
+# created_by_id :integer
+# last_credential_check_at :datetime
+# avatar :string(255)
+# confirmation_token :string(255)
+# confirmed_at :datetime
+# confirmation_sent_at :datetime
+# unconfirmed_email :string(255)
+# hide_no_ssh_key :boolean default(FALSE)
+# website_url :string(255) default(""), not null
+# notification_email :string(255)
+# hide_no_password :boolean default(FALSE)
+# password_automatically_set :boolean default(FALSE)
+# location :string(255)
+# encrypted_otp_secret :string(255)
+# encrypted_otp_secret_iv :string(255)
+# encrypted_otp_secret_salt :string(255)
+# otp_required_for_login :boolean default(FALSE), not null
+# otp_backup_codes :text
+# public_email :string(255) default(""), not null
+# dashboard :integer default(0)
+# project_view :integer default(0)
+# consumed_timestep :integer
+# layout :integer default(0)
+# hide_project_limit :boolean default(FALSE)
+# unlock_token :string
+# otp_grace_period_started_at :datetime
#
require 'spec_helper'
@@ -106,7 +107,7 @@ describe User, models: true do
end
it 'validates uniqueness' do
- expect(subject).to validate_uniqueness_of(:username)
+ expect(subject).to validate_uniqueness_of(:username).case_insensitive
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 7f0f9454b10..6f4c336b66c 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -353,6 +353,20 @@ describe API::API, api: true do
end
end
+ describe "POST /projects/:id/uploads" do
+ before { project }
+
+ it "uploads the file and returns its info" do
+ post api("/projects/#{project.id}/uploads", user), file: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")
+
+ expect(response.status).to be(201)
+ expect(json_response['alt']).to eq("dk")
+ expect(json_response['url']).to start_with("/uploads/")
+ expect(json_response['url']).to end_with("/dk.png")
+ expect(json_response['is_image']).to eq(true)
+ end
+ end
+
describe 'GET /projects/:id' do
before { project }
before { project_member }
@@ -382,6 +396,15 @@ describe API::API, api: true do
expect(response.status).to eq(404)
end
+ it 'should handle users with dots' do
+ dot_user = create(:user, username: 'dot.user')
+ project = create(:project, creator_id: dot_user.id, namespace: dot_user.namespace)
+
+ get api("/projects/#{dot_user.namespace.name}%2F#{project.path}", dot_user)
+ expect(response.status).to eq(200)
+ expect(json_response['name']).to eq(project.name)
+ end
+
describe 'permissions' do
context 'all projects' do
it 'Contains permission information' do
diff --git a/spec/requests/api/tags_spec.rb b/spec/requests/api/tags_spec.rb
index 17f2643fd45..f966e38cd3e 100644
--- a/spec/requests/api/tags_spec.rb
+++ b/spec/requests/api/tags_spec.rb
@@ -65,6 +65,27 @@ describe API::API, api: true do
end
end
+ describe 'DELETE /projects/:id/repository/tags/:tag_name' do
+ let(:tag_name) { project.repository.tag_names.sort.reverse.first }
+
+ before do
+ allow_any_instance_of(Repository).to receive(:rm_tag).and_return(true)
+ end
+
+ context 'delete tag' do
+ it 'should delete an existing tag' do
+ delete api("/projects/#{project.id}/repository/tags/#{tag_name}", user)
+ expect(response.status).to eq(200)
+ expect(json_response['tag_name']).to eq(tag_name)
+ end
+
+ it 'should raise 404 if the tag does not exist' do
+ delete api("/projects/#{project.id}/repository/tags/foobar", user)
+ expect(response.status).to eq(404)
+ end
+ end
+ end
+
context 'annotated tag' do
it 'should create a new annotated tag' do
# Identity must be set in .gitconfig to create annotated tag.
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 82f62a8709c..22ba25217f0 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -80,6 +80,7 @@ describe ProjectsController, 'routing' do
it 'to #show' do
expect(get('/gitlab/gitlabhq')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq')
+ expect(get('/gitlab/gitlabhq.keys')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq.keys')
end
it 'to #update' do
@@ -434,6 +435,18 @@ describe Projects::TreeController, 'routing' do
end
end
+# project_find_file GET /:namespace_id/:project_id/find_file/*id(.:format) projects/find_file#show {:id=>/.+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?<!\.atom)/, :format=>/html/}
+# project_files GET /:namespace_id/:project_id/files/*id(.:format) projects/find_file#list {:id=>/(?:[^.]|\.(?!json$))+/, :namespace_id=>/[a-zA-Z.0-9_\-]+/, :project_id=>/[a-zA-Z.0-9_\-]+(?<!\.atom)/, :format=>/json/}
+describe Projects::FindFileController, 'routing' do
+ it 'to #show' do
+ expect(get('/gitlab/gitlabhq/find_file/master')).to route_to('projects/find_file#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master')
+ end
+
+ it 'to #list' do
+ expect(get('/gitlab/gitlabhq/files/master.json')).to route_to('projects/find_file#list', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json')
+ end
+end
+
describe Projects::BlobController, 'routing' do
it 'to #edit' do
expect(get('/gitlab/gitlabhq/edit/master/app/models/project.rb')).to(
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index c103752198d..6d219f35895 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -52,6 +52,9 @@ describe NotificationService, services: true do
it do
add_users_with_subscription(note.project, issue)
+ # Ensure create SentNotification by noteable = issue 6 times, not noteable = note
+ expect(SentNotification).to receive(:record).with(issue, any_args).exactly(7).times
+
ActionMailer::Base.deliveries.clear
notification.new_note(note)
@@ -61,6 +64,7 @@ describe NotificationService, services: true do
should_email(note.noteable.assignee)
should_email(@u_mentioned)
should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
should_email(@subscribed_participant)
should_not_email(note.author)
should_not_email(@u_participating)
@@ -245,6 +249,7 @@ describe NotificationService, services: true do
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
@@ -260,6 +265,7 @@ describe NotificationService, services: true do
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
end
@@ -282,6 +288,7 @@ describe NotificationService, services: true do
should_email(merge_request.assignee)
should_email(@u_watcher)
+ should_email(@watcher_and_subscriber)
should_email(@u_participant_mentioned)
should_not_email(@u_participating)
should_not_email(@u_disabled)
@@ -296,6 +303,7 @@ describe NotificationService, services: true do
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
@@ -310,6 +318,7 @@ describe NotificationService, services: true do
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
@@ -324,6 +333,7 @@ describe NotificationService, services: true do
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
@@ -338,6 +348,7 @@ describe NotificationService, services: true do
should_email(@u_watcher)
should_email(@u_participant_mentioned)
should_email(@subscriber)
+ should_email(@watcher_and_subscriber)
should_not_email(@unsubscriber)
should_not_email(@u_participating)
should_not_email(@u_disabled)
@@ -387,14 +398,18 @@ describe NotificationService, services: true do
@subscriber = create :user
@unsubscriber = create :user
@subscribed_participant = create(:user, username: 'subscribed_participant', notification_level: Notification::N_PARTICIPATING)
+ @watcher_and_subscriber = create(:user, notification_level: Notification::N_WATCH)
project.team << [@subscribed_participant, :master]
project.team << [@subscriber, :master]
project.team << [@unsubscriber, :master]
+ project.team << [@watcher_and_subscriber, :master]
issuable.subscriptions.create(user: @subscriber, subscribed: true)
issuable.subscriptions.create(user: @subscribed_participant, subscribed: true)
issuable.subscriptions.create(user: @unsubscriber, subscribed: false)
+ # Make the watcher a subscriber to detect dupes
+ issuable.subscriptions.create(user: @watcher_and_subscriber, subscribed: true)
end
def sent_to_user?(user)
diff --git a/spec/services/projects/download_service_spec.rb b/spec/services/projects/download_service_spec.rb
index 5ceed5af9a5..f252e2c5902 100644
--- a/spec/services/projects/download_service_spec.rb
+++ b/spec/services/projects/download_service_spec.rb
@@ -33,12 +33,12 @@ describe Projects::DownloadService, services: true do
@link_to_file = download_file(@project, url)
end
- it { expect(@link_to_file).to have_key('alt') }
- it { expect(@link_to_file).to have_key('url') }
- it { expect(@link_to_file).to have_key('is_image') }
- it { expect(@link_to_file['is_image']).to be true }
- it { expect(@link_to_file['url']).to match('rails_sample.jpg') }
- it { expect(@link_to_file['alt']).to eq('rails_sample') }
+ it { expect(@link_to_file).to have_key(:alt) }
+ it { expect(@link_to_file).to have_key(:url) }
+ it { expect(@link_to_file).to have_key(:is_image) }
+ it { expect(@link_to_file[:is_image]).to be true }
+ it { expect(@link_to_file[:url]).to match('rails_sample.jpg') }
+ it { expect(@link_to_file[:alt]).to eq('rails_sample') }
end
context 'a txt file' do
@@ -47,12 +47,12 @@ describe Projects::DownloadService, services: true do
@link_to_file = download_file(@project, url)
end
- it { expect(@link_to_file).to have_key('alt') }
- it { expect(@link_to_file).to have_key('url') }
- it { expect(@link_to_file).to have_key('is_image') }
- it { expect(@link_to_file['is_image']).to be false }
- it { expect(@link_to_file['url']).to match('doc_sample.txt') }
- it { expect(@link_to_file['alt']).to eq('doc_sample.txt') }
+ it { expect(@link_to_file).to have_key(:alt) }
+ it { expect(@link_to_file).to have_key(:url) }
+ it { expect(@link_to_file).to have_key(:is_image) }
+ it { expect(@link_to_file[:is_image]).to be false }
+ it { expect(@link_to_file[:url]).to match('doc_sample.txt') }
+ it { expect(@link_to_file[:alt]).to eq('doc_sample.txt') }
end
end
end
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index febc78d2784..4455ae7b321 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -9,37 +9,54 @@ describe SystemHooksService, services: true do
let(:group_member) { create(:group_member) }
context 'event data' do
- it { expect(event_data(user, :create)).to include(:event_name, :name, :created_at, :email, :user_id) }
- it { expect(event_data(user, :destroy)).to include(:event_name, :name, :created_at, :email, :user_id) }
- it { expect(event_data(project, :create)).to include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
- it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
- it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_email, :access_level, :project_visibility) }
- it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_email, :access_level, :project_visibility) }
+ it { expect(event_data(user, :create)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id) }
+ it { expect(event_data(user, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :email, :user_id) }
+ it { expect(event_data(project, :create)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
+ it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :updated_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) }
+ it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_email, :access_level, :project_visibility) }
+ it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :updated_at, :project_name, :project_path, :project_path_with_namespace, :project_id, :user_name, :user_email, :access_level, :project_visibility) }
it { expect(event_data(key, :create)).to include(:username, :key, :id) }
it { expect(event_data(key, :destroy)).to include(:username, :key, :id) }
it do
+ project.old_path_with_namespace = 'renamed_from_path'
+ expect(event_data(project, :rename)).to include(
+ :event_name, :name, :created_at, :updated_at, :path, :project_id,
+ :owner_name, :owner_email, :project_visibility,
+ :old_path_with_namespace
+ )
+ end
+ it do
+ project.old_path_with_namespace = 'transfered_from_path'
+ expect(event_data(project, :transfer)).to include(
+ :event_name, :name, :created_at, :updated_at, :path, :project_id,
+ :owner_name, :owner_email, :project_visibility,
+ :old_path_with_namespace
+ )
+ end
+
+ it do
expect(event_data(group, :create)).to include(
- :event_name, :name, :created_at, :path, :group_id, :owner_name,
- :owner_email
+ :event_name, :name, :created_at, :updated_at, :path, :group_id,
+ :owner_name, :owner_email
)
end
it do
expect(event_data(group, :destroy)).to include(
- :event_name, :name, :created_at, :path, :group_id, :owner_name,
- :owner_email
+ :event_name, :name, :created_at, :updated_at, :path, :group_id,
+ :owner_name, :owner_email
)
end
it do
expect(event_data(group_member, :create)).to include(
- :event_name, :created_at, :group_name, :group_path, :group_id, :user_id,
- :user_name, :user_email, :group_access
+ :event_name, :created_at, :updated_at, :group_name, :group_path,
+ :group_id, :user_id, :user_name, :user_email, :group_access
)
end
it do
expect(event_data(group_member, :destroy)).to include(
- :event_name, :created_at, :group_name, :group_path, :group_id, :user_id,
- :user_name, :user_email, :group_access
+ :event_name, :created_at, :updated_at, :group_name, :group_path,
+ :group_id, :user_id, :user_name, :user_email, :group_access
)
end
end
@@ -49,6 +66,8 @@ describe SystemHooksService, services: true do
it { expect(event_name(user, :destroy)).to eq "user_destroy" }
it { expect(event_name(project, :create)).to eq "project_create" }
it { expect(event_name(project, :destroy)).to eq "project_destroy" }
+ it { expect(event_name(project, :rename)).to eq "project_rename" }
+ it { expect(event_name(project, :transfer)).to eq "project_transfer" }
it { expect(event_name(project_member, :create)).to eq "user_add_to_team" }
it { expect(event_name(project_member, :destroy)).to eq "user_remove_from_team" }
it { expect(event_name(key, :create)).to eq 'key_create' }
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index c9f828ae2f7..d3364a71022 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -171,7 +171,7 @@ describe SystemNoteService, services: true do
context 'when milestone added' do
it 'sets the note text' do
- expect(subject.note).to eq "Milestone changed to #{milestone.title}"
+ expect(subject.note).to eq "Milestone changed to #{milestone.to_reference}"
end
end
diff --git a/spec/support/markdown_feature.rb b/spec/support/markdown_feature.rb
index d6d3062a197..5d97fdd4882 100644
--- a/spec/support/markdown_feature.rb
+++ b/spec/support/markdown_feature.rb
@@ -59,6 +59,10 @@ class MarkdownFeature
@label ||= create(:label, name: 'awaiting feedback', project: project)
end
+ def milestone
+ @milestone ||= create(:milestone, project: project)
+ end
+
# Cross-references -----------------------------------------------------------
def xproject
@@ -93,6 +97,10 @@ class MarkdownFeature
end
end
+ def xmilestone
+ @xmilestone ||= create(:milestone, project: xproject)
+ end
+
def urls
Gitlab::Application.routes.url_helpers
end
diff --git a/spec/support/matchers/markdown_matchers.rb b/spec/support/matchers/markdown_matchers.rb
index 7eadcd58c1f..b251e7f8f23 100644
--- a/spec/support/matchers/markdown_matchers.rb
+++ b/spec/support/matchers/markdown_matchers.rb
@@ -130,6 +130,15 @@ module MarkdownMatchers
end
end
+ # MilestoneReferenceFilter
+ matcher :reference_milestones do
+ set_default_markdown_messages
+
+ match do |actual|
+ expect(actual).to have_selector('a.gfm.gfm-milestone', count: 3)
+ end
+ end
+
# TaskListFilter
matcher :parse_task_lists do
set_default_markdown_messages
diff --git a/vendor/assets/javascripts/fuzzaldrin-plus.min.js b/vendor/assets/javascripts/fuzzaldrin-plus.min.js
new file mode 100644
index 00000000000..3f25c2d8373
--- /dev/null
+++ b/vendor/assets/javascripts/fuzzaldrin-plus.min.js
@@ -0,0 +1 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){(function(){var PathSeparator,legacy_scorer,pluckCandidates,scorer,sortCandidates;scorer=require('./scorer');legacy_scorer=require('./legacy');pluckCandidates=function(a){return a.candidate};sortCandidates=function(a,b){return b.score-a.score};PathSeparator=require('path').sep;module.exports=function(candidates,query,arg){var allowErrors,bAllowErrors,bKey,candidate,coreQuery,i,j,key,legacy,len,len1,maxInners,maxResults,prepQuery,queryHasSlashes,ref,score,scoredCandidates,spotLeft,string;ref=arg!=null?arg:{},key=ref.key,maxResults=ref.maxResults,maxInners=ref.maxInners,allowErrors=ref.allowErrors,legacy=ref.legacy;scoredCandidates=[];spotLeft=(maxInners!=null)&&maxInners>0?maxInners:candidates.length;bAllowErrors=!!allowErrors;bKey=key!=null;prepQuery=scorer.prepQuery(query);if(!legacy){for(i=0,len=candidates.length;i<len;i++){candidate=candidates[i];string=bKey?candidate[key]:candidate;if(!string){continue}score=scorer.score(string,query,prepQuery,bAllowErrors);if(score>0){scoredCandidates.push({candidate:candidate,score:score});if(!--spotLeft){break}}}}else{queryHasSlashes=prepQuery.depth>0;coreQuery=prepQuery.core;for(j=0,len1=candidates.length;j<len1;j++){candidate=candidates[j];string=key!=null?candidate[key]:candidate;if(!string){continue}score=legacy_scorer.score(string,coreQuery,queryHasSlashes);if(!queryHasSlashes){score=legacy_scorer.basenameScore(string,coreQuery,score)}if(score>0){scoredCandidates.push({candidate:candidate,score:score})}}}scoredCandidates.sort(sortCandidates);candidates=scoredCandidates.map(pluckCandidates);if(maxResults!=null){candidates=candidates.slice(0,maxResults)}return candidates}}).call(this)},{"./legacy":4,"./scorer":6,"path":7}],2:[function(require,module,exports){(function(){var PathSeparator,filter,legacy_scorer,matcher,prepQueryCache,scorer;scorer=require('./scorer');legacy_scorer=require('./legacy');filter=require('./filter');matcher=require('./matcher');PathSeparator=require('path').sep;prepQueryCache=null;module.exports={filter:function(candidates,query,options){if(!((query!=null?query.length:void 0)&&(candidates!=null?candidates.length:void 0))){return[]}return filter(candidates,query,options)},prepQuery:function(query){return scorer.prepQuery(query)},score:function(string,query,prepQuery,arg){var allowErrors,coreQuery,legacy,queryHasSlashes,ref,score;ref=arg!=null?arg:{},allowErrors=ref.allowErrors,legacy=ref.legacy;if(!((string!=null?string.length:void 0)&&(query!=null?query.length:void 0))){return 0}if(prepQuery==null){prepQuery=prepQueryCache&&prepQueryCache.query===query?prepQueryCache:(prepQueryCache=scorer.prepQuery(query))}if(!legacy){score=scorer.score(string,query,prepQuery,!!allowErrors)}else{queryHasSlashes=prepQuery.depth>0;coreQuery=prepQuery.core;score=legacy_scorer.score(string,coreQuery,queryHasSlashes);if(!queryHasSlashes){score=legacy_scorer.basenameScore(string,coreQuery,score)}}return score},match:function(string,query,prepQuery,arg){var allowErrors,baseMatches,i,matches,query_lw,ref,results,string_lw;allowErrors=(arg!=null?arg:{}).allowErrors;if(!string){return[]}if(!query){return[]}if(string===query){return(function(){results=[];for(var i=0,ref=string.length;0<=ref?i<ref:i>ref;0<=ref?i++:i--){results.push(i)}return results}).apply(this)}if(prepQuery==null){prepQuery=prepQueryCache&&prepQueryCache.query===query?prepQueryCache:(prepQueryCache=scorer.prepQuery(query))}if(!(allowErrors||scorer.isMatch(string,prepQuery.core_lw,prepQuery.core_up))){return[]}string_lw=string.toLowerCase();query_lw=prepQuery.query_lw;matches=matcher.match(string,string_lw,prepQuery);if(matches.length===0){return matches}if(string.indexOf(PathSeparator)>-1){baseMatches=matcher.basenameMatch(string,string_lw,prepQuery);matches=matcher.mergeMatches(matches,baseMatches)}return matches}}}).call(this)},{"./filter":1,"./legacy":4,"./matcher":5,"./scorer":6,"path":7}],3:[function(require,module,exports){fuzzaldrinPlus=require('./fuzzaldrin')},{"./fuzzaldrin":2}],4:[function(require,module,exports){(function(){var PathSeparator,queryIsLastPathSegment;PathSeparator=require('path').sep;exports.basenameScore=function(string,query,score){var base,depth,index,lastCharacter,segmentCount,slashCount;index=string.length-1;while(string[index]===PathSeparator){index--}slashCount=0;lastCharacter=index;base=null;while(index>=0){if(string[index]===PathSeparator){slashCount++;if(base==null){base=string.substring(index+1,lastCharacter+1)}}else if(index===0){if(lastCharacter<string.length-1){if(base==null){base=string.substring(0,lastCharacter+1)}}else{if(base==null){base=string}}}index--}if(base===string){score*=2}else if(base){score+=exports.score(base,query)}segmentCount=slashCount+1;depth=Math.max(1,10-segmentCount);score*=depth*0.01;return score};exports.score=function(string,query){var character,characterScore,indexInQuery,indexInString,lowerCaseIndex,minIndex,queryLength,queryScore,ref,stringLength,totalCharacterScore,upperCaseIndex;if(string===query){return 1}if(queryIsLastPathSegment(string,query)){return 1}totalCharacterScore=0;queryLength=query.length;stringLength=string.length;indexInQuery=0;indexInString=0;while(indexInQuery<queryLength){character=query[indexInQuery++];lowerCaseIndex=string.indexOf(character.toLowerCase());upperCaseIndex=string.indexOf(character.toUpperCase());minIndex=Math.min(lowerCaseIndex,upperCaseIndex);if(minIndex===-1){minIndex=Math.max(lowerCaseIndex,upperCaseIndex)}indexInString=minIndex;if(indexInString===-1){return 0}characterScore=0.1;if(string[indexInString]===character){characterScore+=0.1}if(indexInString===0||string[indexInString-1]===PathSeparator){characterScore+=0.8}else if((ref=string[indexInString-1])==='-'||ref==='_'||ref===' '){characterScore+=0.7}string=string.substring(indexInString+1,stringLength);totalCharacterScore+=characterScore}queryScore=totalCharacterScore/queryLength;return((queryScore*(queryLength/stringLength))+queryScore)/2};queryIsLastPathSegment=function(string,query){if(string[string.length-query.length-1]===PathSeparator){return string.lastIndexOf(query)===string.length-query.length}};exports.match=function(string,query,stringOffset){var character,i,indexInQuery,indexInString,lowerCaseIndex,matches,minIndex,queryLength,ref,results,stringLength,upperCaseIndex;if(stringOffset==null){stringOffset=0}if(string===query){return(function(){results=[];for(var i=stringOffset,ref=stringOffset+string.length;stringOffset<=ref?i<ref:i>ref;stringOffset<=ref?i++:i--){results.push(i)}return results}).apply(this)}queryLength=query.length;stringLength=string.length;indexInQuery=0;indexInString=0;matches=[];while(indexInQuery<queryLength){character=query[indexInQuery++];lowerCaseIndex=string.indexOf(character.toLowerCase());upperCaseIndex=string.indexOf(character.toUpperCase());minIndex=Math.min(lowerCaseIndex,upperCaseIndex);if(minIndex===-1){minIndex=Math.max(lowerCaseIndex,upperCaseIndex)}indexInString=minIndex;if(indexInString===-1){return[]}matches.push(stringOffset+indexInString);stringOffset+=indexInString+1;string=string.substring(indexInString+1,stringLength)}return matches}}).call(this)},{"path":7}],5:[function(require,module,exports){(function(){var PathSeparator,scorer;PathSeparator=require('path').sep;scorer=require('./scorer');exports.basenameMatch=function(subject,subject_lw,prepQuery){var basePos,depth,end;end=subject.length-1;while(subject[end]===PathSeparator){end--}basePos=subject.lastIndexOf(PathSeparator,end);if(basePos===-1){return[]}depth=prepQuery.depth;while(depth-->0){basePos=subject.lastIndexOf(PathSeparator,basePos-1);if(basePos===-1){return[]}}basePos++;end++;return exports.match(subject.slice(basePos,end),subject_lw.slice(basePos,end),prepQuery,basePos)};exports.mergeMatches=function(a,b){var ai,bj,i,j,m,n,out;m=a.length;n=b.length;if(n===0){return a.slice()}if(m===0){return b.slice()}i=-1;j=0;bj=b[j];out=[];while(++i<m){ai=a[i];while(bj<=ai&&++j<n){if(bj<ai){out.push(bj)}bj=b[j]}out.push(ai)}while(j<n){out.push(b[j++])}return out};exports.match=function(subject,subject_lw,prepQuery,offset){var DIAGONAL,LEFT,STOP,UP,acro_score,align,backtrack,csc_diag,csc_row,csc_score,i,j,m,matches,move,n,pos,query,query_lw,score,score_diag,score_row,score_up,si_lw,start,trace;if(offset==null){offset=0}query=prepQuery.query;query_lw=prepQuery.query_lw;m=subject.length;n=query.length;acro_score=scorer.scoreAcronyms(subject,subject_lw,query,query_lw).score;score_row=new Array(n);csc_row=new Array(n);STOP=0;UP=1;LEFT=2;DIAGONAL=3;trace=new Array(m*n);pos=-1;j=-1;while(++j<n){score_row[j]=0;csc_row[j]=0}i=-1;while(++i<m){score=0;score_up=0;csc_diag=0;si_lw=subject_lw[i];j=-1;while(++j<n){csc_score=0;align=0;score_diag=score_up;if(query_lw[j]===si_lw){start=scorer.isWordStart(i,subject,subject_lw);csc_score=csc_diag>0?csc_diag:scorer.scoreConsecutives(subject,subject_lw,query,query_lw,i,j,start);align=score_diag+scorer.scoreCharacter(i,j,start,acro_score,csc_score)}score_up=score_row[j];csc_diag=csc_row[j];if(score>score_up){move=LEFT}else{score=score_up;move=UP}if(align>score){score=align;move=DIAGONAL}else{csc_score=0}score_row[j]=score;csc_row[j]=csc_score;trace[++pos]=score>0?move:STOP}}i=m-1;j=n-1;pos=i*n+j;backtrack=true;matches=[];while(backtrack&&i>=0&&j>=0){switch(trace[pos]){case UP:i--;pos-=n;break;case LEFT:j--;pos--;break;case DIAGONAL:matches.push(i+offset);j--;i--;pos-=n+1;break;default:backtrack=false}}matches.reverse();return matches}}).call(this)},{"./scorer":6,"path":7}],6:[function(require,module,exports){(function(){var AcronymResult,PathSeparator,Query,basenameScore,coreChars,countDir,doScore,emptyAcronymResult,file_coeff,isMatch,isSeparator,isWordEnd,isWordStart,miss_coeff,opt_char_re,pos_bonus,scoreAcronyms,scoreCharacter,scoreConsecutives,scoreExact,scoreExactMatch,scorePattern,scorePosition,scoreSize,tau_depth,tau_size,truncatedUpperCase,wm;PathSeparator=require('path').sep;wm=150;pos_bonus=20;tau_depth=13;tau_size=85;file_coeff=1.2;miss_coeff=0.75;opt_char_re=/[ _\-:\/\\]/g;exports.coreChars=coreChars=function(query){return query.replace(opt_char_re,'')};exports.score=function(string,query,prepQuery,allowErrors){var score,string_lw;if(prepQuery==null){prepQuery=new Query(query)}if(allowErrors==null){allowErrors=false}if(!(allowErrors||isMatch(string,prepQuery.core_lw,prepQuery.core_up))){return 0}string_lw=string.toLowerCase();score=doScore(string,string_lw,prepQuery);return Math.ceil(basenameScore(string,string_lw,prepQuery,score))};Query=(function(){function Query(query){if(!(query!=null?query.length:void 0)){return null}this.query=query;this.query_lw=query.toLowerCase();this.core=coreChars(query);this.core_lw=this.core.toLowerCase();this.core_up=truncatedUpperCase(this.core);this.depth=countDir(query,query.length)}return Query})();exports.prepQuery=function(query){return new Query(query)};exports.isMatch=isMatch=function(subject,query_lw,query_up){var i,j,m,n,qj_lw,qj_up,si;m=subject.length;n=query_lw.length;if(!m||!n||n>m){return false}i=-1;j=-1;while(++j<n){qj_lw=query_lw[j];qj_up=query_up[j];while(++i<m){si=subject[i];if(si===qj_lw||si===qj_up){break}}if(i===m){return false}}return true};doScore=function(subject,subject_lw,prepQuery){var acro,acro_score,align,csc_diag,csc_row,csc_score,i,j,m,miss_budget,miss_left,mm,n,pos,query,query_lw,record_miss,score,score_diag,score_row,score_up,si_lw,start,sz;query=prepQuery.query;query_lw=prepQuery.query_lw;m=subject.length;n=query.length;acro=scoreAcronyms(subject,subject_lw,query,query_lw);acro_score=acro.score;if(acro.count===n){return scoreExact(n,m,acro_score,acro.pos)}pos=subject_lw.indexOf(query_lw);if(pos>-1){return scoreExactMatch(subject,subject_lw,query,query_lw,pos,n,m)}score_row=new Array(n);csc_row=new Array(n);sz=scoreSize(n,m);miss_budget=Math.ceil(miss_coeff*n)+5;miss_left=miss_budget;j=-1;while(++j<n){score_row[j]=0;csc_row[j]=0}i=subject_lw.indexOf(query_lw[0]);if(i>-1){i--}mm=subject_lw.lastIndexOf(query_lw[n-1],m);if(mm>i){m=mm+1}while(++i<m){score=0;score_diag=0;csc_diag=0;si_lw=subject_lw[i];record_miss=true;j=-1;while(++j<n){score_up=score_row[j];if(score_up>score){score=score_up}csc_score=0;if(query_lw[j]===si_lw){start=isWordStart(i,subject,subject_lw);csc_score=csc_diag>0?csc_diag:scoreConsecutives(subject,subject_lw,query,query_lw,i,j,start);align=score_diag+scoreCharacter(i,j,start,acro_score,csc_score);if(align>score){score=align;miss_left=miss_budget}else{if(record_miss&&--miss_left<=0){return score_row[n-1]*sz}record_miss=false}}score_diag=score_up;csc_diag=csc_row[j];csc_row[j]=csc_score;score_row[j]=score}}return score*sz};exports.isWordStart=isWordStart=function(pos,subject,subject_lw){var curr_s,prev_s;if(pos===0){return true}curr_s=subject[pos];prev_s=subject[pos-1];return isSeparator(curr_s)||isSeparator(prev_s)||(curr_s!==subject_lw[pos]&&prev_s===subject_lw[pos-1])};exports.isWordEnd=isWordEnd=function(pos,subject,subject_lw,len){var curr_s,next_s;if(pos===len-1){return true}curr_s=subject[pos];next_s=subject[pos+1];return isSeparator(curr_s)||isSeparator(next_s)||(curr_s===subject_lw[pos]&&next_s!==subject_lw[pos+1])};isSeparator=function(c){return c===' '||c==='.'||c==='-'||c==='_'||c==='/'||c==='\\'};scorePosition=function(pos){var sc;if(pos<pos_bonus){sc=pos_bonus-pos;return 100+sc*sc}else{return Math.max(100+pos_bonus-pos,0)}};scoreSize=function(n,m){return tau_size/(tau_size+Math.abs(m-n))};scoreExact=function(n,m,quality,pos){return 2*n*(wm*quality+scorePosition(pos))*scoreSize(n,m)};exports.scorePattern=scorePattern=function(count,len,sameCase,start,end){var bonus,sz;sz=count;bonus=6;if(sameCase===count){bonus+=2}if(start){bonus+=3}if(end){bonus+=1}if(count===len){if(start){if(sameCase===len){sz+=2}else{sz+=1}}if(end){bonus+=1}}return sameCase+sz*(sz+bonus)};exports.scoreCharacter=scoreCharacter=function(i,j,start,acro_score,csc_score){var posBonus;posBonus=scorePosition(i);if(start){return posBonus+wm*((acro_score>csc_score?acro_score:csc_score)+10)}return posBonus+wm*csc_score};exports.scoreConsecutives=scoreConsecutives=function(subject,subject_lw,query,query_lw,i,j,start){var k,m,mi,n,nj,sameCase,startPos,sz;m=subject.length;n=query.length;mi=m-i;nj=n-j;k=mi<nj?mi:nj;startPos=i;sameCase=0;sz=0;if(query[j]===subject[i]){sameCase++}while(++sz<k&&query_lw[++j]===subject_lw[++i]){if(query[j]===subject[i]){sameCase++}}if(sz===1){return 1+2*sameCase}return scorePattern(sz,n,sameCase,start,isWordEnd(i,subject,subject_lw,m))};exports.scoreExactMatch=scoreExactMatch=function(subject,subject_lw,query,query_lw,pos,n,m){var end,i,pos2,sameCase,start;start=isWordStart(pos,subject,subject_lw);if(!start){pos2=subject_lw.indexOf(query_lw,pos+1);if(pos2>-1){start=isWordStart(pos2,subject,subject_lw);if(start){pos=pos2}}}i=-1;sameCase=0;while(++i<n){if(query[pos+i]===subject[i]){sameCase++}}end=isWordEnd(pos+n-1,subject,subject_lw,m);return scoreExact(n,m,scorePattern(n,n,sameCase,start,end),pos)};AcronymResult=(function(){function AcronymResult(score1,pos1,count1){this.score=score1;this.pos=pos1;this.count=count1}return AcronymResult})();emptyAcronymResult=new AcronymResult(0,0.1,0);exports.scoreAcronyms=scoreAcronyms=function(subject,subject_lw,query,query_lw){var count,i,j,m,n,pos,qj_lw,sameCase,score;m=subject.length;n=query.length;if(!(m>1&&n>1)){return emptyAcronymResult}count=0;pos=0;sameCase=0;i=-1;j=-1;while(++j<n){qj_lw=query_lw[j];while(++i<m){if(qj_lw===subject_lw[i]&&isWordStart(i,subject,subject_lw)){if(query[j]===subject[i]){sameCase++}pos+=i;count++;break}}if(i===m){break}}if(count<2){return emptyAcronymResult}score=scorePattern(count,n,sameCase,true,false);return new AcronymResult(score,pos/count,count)};basenameScore=function(subject,subject_lw,prepQuery,fullPathScore){var alpha,basePathScore,basePos,depth,end;if(fullPathScore===0){return 0}end=subject.length-1;while(subject[end]===PathSeparator){end--}basePos=subject.lastIndexOf(PathSeparator,end);if(basePos===-1){return fullPathScore}depth=prepQuery.depth;while(depth-->0){basePos=subject.lastIndexOf(PathSeparator,basePos-1);if(basePos===-1){return fullPathScore}}basePos++;end++;basePathScore=doScore(subject.slice(basePos,end),subject_lw.slice(basePos,end),prepQuery);alpha=0.5*tau_depth/(tau_depth+countDir(subject,end+1));return alpha*basePathScore+(1-alpha)*fullPathScore*scoreSize(0,file_coeff*(end-basePos))};exports.countDir=countDir=function(path,end){var count,i;if(end<1){return 0}count=0;i=-1;while(++i<end&&path[i]===PathSeparator){continue}while(++i<end){if(path[i]===PathSeparator){count++;while(++i<end&&path[i]===PathSeparator){continue}}}return count};truncatedUpperCase=function(str){var char,l,len1,upper;upper="";for(l=0,len1=str.length;l<len1;l++){char=str[l];upper+=char.toUpperCase()[0]}return upper}}).call(this)},{"path":7}],7:[function(require,module,exports){(function(process){function normalizeArray(parts,allowAboveRoot){var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==='.'){parts.splice(i,1)}else if(last==='..'){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up--;up){parts.unshift('..')}}return parts}var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;var splitPath=function(filename){return splitPathRe.exec(filename).slice(1)};exports.resolve=function(){var resolvedPath='',resolvedAbsolute=false;for(var i=arguments.length-1;i>=-1&&!resolvedAbsolute;i--){var path=(i>=0)?arguments[i]:process.cwd();if(typeof path!=='string'){throw new TypeError('Arguments to path.resolve must be strings');}else if(!path){continue}resolvedPath=path+'/'+resolvedPath;resolvedAbsolute=path.charAt(0)==='/'}resolvedPath=normalizeArray(filter(resolvedPath.split('/'),function(p){return!!p}),!resolvedAbsolute).join('/');return((resolvedAbsolute?'/':'')+resolvedPath)||'.'};exports.normalize=function(path){var isAbsolute=exports.isAbsolute(path),trailingSlash=substr(path,-1)==='/';path=normalizeArray(filter(path.split('/'),function(p){return!!p}),!isAbsolute).join('/');if(!path&&!isAbsolute){path='.'}if(path&&trailingSlash){path+='/'}return(isAbsolute?'/':'')+path};exports.isAbsolute=function(path){return path.charAt(0)==='/'};exports.join=function(){var paths=Array.prototype.slice.call(arguments,0);return exports.normalize(filter(paths,function(p,index){if(typeof p!=='string'){throw new TypeError('Arguments to path.join must be strings');}return p}).join('/'))};exports.relative=function(from,to){from=exports.resolve(from).substr(1);to=exports.resolve(to).substr(1);function trim(arr){var start=0;for(;start<arr.length;start++){if(arr[start]!=='')break}var end=arr.length-1;for(;end>=0;end--){if(arr[end]!=='')break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split('/'));var toParts=trim(to.split('/'));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i<length;i++){if(fromParts[i]!==toParts[i]){samePartsLength=i;break}}var outputParts=[];for(var i=samePartsLength;i<fromParts.length;i++){outputParts.push('..')}outputParts=outputParts.concat(toParts.slice(samePartsLength));return outputParts.join('/')};exports.sep='/';exports.delimiter=':';exports.dirname=function(path){var result=splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return'.'}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir};exports.basename=function(path,ext){var f=splitPath(path)[2];if(ext&&f.substr(-1*ext.length)===ext){f=f.substr(0,f.length-ext.length)}return f};exports.extname=function(path){return splitPath(path)[3]};function filter(xs,f){if(xs.filter)return xs.filter(f);var res=[];for(var i=0;i<xs.length;i++){if(f(xs[i],i,xs))res.push(xs[i])}return res}var substr='ab'.substr(-1)==='b'?function(str,start,len){return str.substr(start,len)}:function(str,start,len){if(start<0)start=str.length+start;return str.substr(start,len)}}).call(this,require('_process'))},{"_process":8}],8:[function(require,module,exports){var process=module.exports={};var queue=[];var draining=false;var currentQueue;var queueIndex=-1;function cleanUpNextTick(){draining=false;if(currentQueue.length){queue=currentQueue.concat(queue)}else{queueIndex=-1}if(queue.length){drainQueue()}}function drainQueue(){if(draining){return}var timeout=setTimeout(cleanUpNextTick);draining=true;var len=queue.length;while(len){currentQueue=queue;queue=[];while(++queueIndex<len){if(currentQueue){currentQueue[queueIndex].run()}}queueIndex=-1;len=queue.length}currentQueue=null;draining=false;clearTimeout(timeout)}process.nextTick=function(fun){var args=new Array(arguments.length-1);if(arguments.length>1){for(var i=1;i<arguments.length;i++){args[i-1]=arguments[i]}}queue.push(new Item(fun,args));if(queue.length===1&&!draining){setTimeout(drainQueue,0)}};function Item(fun,array){this.fun=fun;this.array=array}Item.prototype.run=function(){this.fun.apply(null,this.array)};process.title='browser';process.browser=true;process.env={};process.argv=[];process.version='';process.versions={};function noop(){}process.on=noop;process.addListener=noop;process.once=noop;process.off=noop;process.removeListener=noop;process.removeAllListeners=noop;process.emit=noop;process.binding=function(name){throw new Error('process.binding is not supported');};process.cwd=function(){return'/'};process.chdir=function(dir){throw new Error('process.chdir is not supported');};process.umask=function(){return 0}},{}]},{},[3]);
diff --git a/vendor/assets/javascripts/jquery.blockUI.js b/vendor/assets/javascripts/jquery.blockUI.js
deleted file mode 100644
index c8702d79b65..00000000000
--- a/vendor/assets/javascripts/jquery.blockUI.js
+++ /dev/null
@@ -1,590 +0,0 @@
-/*!
- * jQuery blockUI plugin
- * Version 2.60.0-2013.04.05
- * @requires jQuery v1.7 or later
- *
- * Examples at: http://malsup.com/jquery/block/
- * Copyright (c) 2007-2013 M. Alsup
- * Dual licensed under the MIT and GPL licenses:
- * http://www.opensource.org/licenses/mit-license.php
- * http://www.gnu.org/licenses/gpl.html
- *
- * Thanks to Amir-Hossein Sobhi for some excellent contributions!
- */
-
-;(function() {
-/*jshint eqeqeq:false curly:false latedef:false */
-"use strict";
-
- function setup($) {
- $.fn._fadeIn = $.fn.fadeIn;
-
- var noOp = $.noop || function() {};
-
- // this bit is to ensure we don't call setExpression when we shouldn't (with extra muscle to handle
- // retarded userAgent strings on Vista)
- var msie = /MSIE/.test(navigator.userAgent);
- var ie6 = /MSIE 6.0/.test(navigator.userAgent) && ! /MSIE 8.0/.test(navigator.userAgent);
- var mode = document.documentMode || 0;
- var setExpr = $.isFunction( document.createElement('div').style.setExpression );
-
- // global $ methods for blocking/unblocking the entire page
- $.blockUI = function(opts) { install(window, opts); };
- $.unblockUI = function(opts) { remove(window, opts); };
-
- // convenience method for quick growl-like notifications (http://www.google.com/search?q=growl)
- $.growlUI = function(title, message, timeout, onClose) {
- var $m = $('<div class="growlUI"></div>');
- if (title) $m.append('<h1>'+title+'</h1>');
- if (message) $m.append('<h2>'+message+'</h2>');
- if (timeout === undefined) timeout = 3000;
- $.blockUI({
- message: $m, fadeIn: 700, fadeOut: 1000, centerY: false,
- timeout: timeout, showOverlay: false,
- onUnblock: onClose,
- css: $.blockUI.defaults.growlCSS
- });
- };
-
- // plugin method for blocking element content
- $.fn.block = function(opts) {
- if ( this[0] === window ) {
- $.blockUI( opts );
- return this;
- }
- var fullOpts = $.extend({}, $.blockUI.defaults, opts || {});
- this.each(function() {
- var $el = $(this);
- if (fullOpts.ignoreIfBlocked && $el.data('blockUI.isBlocked'))
- return;
- $el.unblock({ fadeOut: 0 });
- });
-
- return this.each(function() {
- if ($.css(this,'position') == 'static') {
- this.style.position = 'relative';
- $(this).data('blockUI.static', true);
- }
- this.style.zoom = 1; // force 'hasLayout' in ie
- install(this, opts);
- });
- };
-
- // plugin method for unblocking element content
- $.fn.unblock = function(opts) {
- if ( this[0] === window ) {
- $.unblockUI( opts );
- return this;
- }
- return this.each(function() {
- remove(this, opts);
- });
- };
-
- $.blockUI.version = 2.60; // 2nd generation blocking at no extra cost!
-
- // override these in your code to change the default behavior and style
- $.blockUI.defaults = {
- // message displayed when blocking (use null for no message)
- message: '<h1>Please wait...</h1>',
-
- title: null, // title string; only used when theme == true
- draggable: true, // only used when theme == true (requires jquery-ui.js to be loaded)
-
- theme: false, // set to true to use with jQuery UI themes
-
- // styles for the message when blocking; if you wish to disable
- // these and use an external stylesheet then do this in your code:
- // $.blockUI.defaults.css = {};
- css: {
- padding: 0,
- margin: 0,
- width: '30%',
- top: '40%',
- left: '35%',
- textAlign: 'center',
- color: '#000',
- border: '3px solid #aaa',
- backgroundColor:'#fff',
- cursor: 'wait'
- },
-
- // minimal style set used when themes are used
- themedCSS: {
- width: '30%',
- top: '40%',
- left: '35%'
- },
-
- // styles for the overlay
- overlayCSS: {
- backgroundColor: '#000',
- opacity: 0.6,
- cursor: 'wait'
- },
-
- // style to replace wait cursor before unblocking to correct issue
- // of lingering wait cursor
- cursorReset: 'default',
-
- // styles applied when using $.growlUI
- growlCSS: {
- width: '350px',
- top: '10px',
- left: '',
- right: '10px',
- border: 'none',
- padding: '5px',
- opacity: 0.6,
- cursor: 'default',
- color: '#fff',
- backgroundColor: '#000',
- '-webkit-border-radius':'10px',
- '-moz-border-radius': '10px',
- 'border-radius': '10px'
- },
-
- // IE issues: 'about:blank' fails on HTTPS and javascript:false is s-l-o-w
- // (hat tip to Jorge H. N. de Vasconcelos)
- /*jshint scripturl:true */
- iframeSrc: /^https/i.test(window.location.href || '') ? 'javascript:false' : 'about:blank',
-
- // force usage of iframe in non-IE browsers (handy for blocking applets)
- forceIframe: false,
-
- // z-index for the blocking overlay
- baseZ: 1000,
-
- // set these to true to have the message automatically centered
- centerX: true, // <-- only effects element blocking (page block controlled via css above)
- centerY: true,
-
- // allow body element to be stetched in ie6; this makes blocking look better
- // on "short" pages. disable if you wish to prevent changes to the body height
- allowBodyStretch: true,
-
- // enable if you want key and mouse events to be disabled for content that is blocked
- bindEvents: true,
-
- // be default blockUI will supress tab navigation from leaving blocking content
- // (if bindEvents is true)
- constrainTabKey: true,
-
- // fadeIn time in millis; set to 0 to disable fadeIn on block
- fadeIn: 200,
-
- // fadeOut time in millis; set to 0 to disable fadeOut on unblock
- fadeOut: 400,
-
- // time in millis to wait before auto-unblocking; set to 0 to disable auto-unblock
- timeout: 0,
-
- // disable if you don't want to show the overlay
- showOverlay: true,
-
- // if true, focus will be placed in the first available input field when
- // page blocking
- focusInput: true,
-
- // elements that can receive focus
- focusableElements: ':input:enabled:visible',
-
- // suppresses the use of overlay styles on FF/Linux (due to performance issues with opacity)
- // no longer needed in 2012
- // applyPlatformOpacityRules: true,
-
- // callback method invoked when fadeIn has completed and blocking message is visible
- onBlock: null,
-
- // callback method invoked when unblocking has completed; the callback is
- // passed the element that has been unblocked (which is the window object for page
- // blocks) and the options that were passed to the unblock call:
- // onUnblock(element, options)
- onUnblock: null,
-
- // callback method invoked when the overlay area is clicked.
- // setting this will turn the cursor to a pointer, otherwise cursor defined in overlayCss will be used.
- onOverlayClick: null,
-
- // don't ask; if you really must know: http://groups.google.com/group/jquery-en/browse_thread/thread/36640a8730503595/2f6a79a77a78e493#2f6a79a77a78e493
- quirksmodeOffsetHack: 4,
-
- // class name of the message block
- blockMsgClass: 'blockMsg',
-
- // if it is already blocked, then ignore it (don't unblock and reblock)
- ignoreIfBlocked: false
- };
-
- // private data and functions follow...
-
- var pageBlock = null;
- var pageBlockEls = [];
-
- function install(el, opts) {
- var css, themedCSS;
- var full = (el == window);
- var msg = (opts && opts.message !== undefined ? opts.message : undefined);
- opts = $.extend({}, $.blockUI.defaults, opts || {});
-
- if (opts.ignoreIfBlocked && $(el).data('blockUI.isBlocked'))
- return;
-
- opts.overlayCSS = $.extend({}, $.blockUI.defaults.overlayCSS, opts.overlayCSS || {});
- css = $.extend({}, $.blockUI.defaults.css, opts.css || {});
- if (opts.onOverlayClick)
- opts.overlayCSS.cursor = 'pointer';
-
- themedCSS = $.extend({}, $.blockUI.defaults.themedCSS, opts.themedCSS || {});
- msg = msg === undefined ? opts.message : msg;
-
- // remove the current block (if there is one)
- if (full && pageBlock)
- remove(window, {fadeOut:0});
-
- // if an existing element is being used as the blocking content then we capture
- // its current place in the DOM (and current display style) so we can restore
- // it when we unblock
- if (msg && typeof msg != 'string' && (msg.parentNode || msg.jquery)) {
- var node = msg.jquery ? msg[0] : msg;
- var data = {};
- $(el).data('blockUI.history', data);
- data.el = node;
- data.parent = node.parentNode;
- data.display = node.style.display;
- data.position = node.style.position;
- if (data.parent)
- data.parent.removeChild(node);
- }
-
- $(el).data('blockUI.onUnblock', opts.onUnblock);
- var z = opts.baseZ;
-
- // blockUI uses 3 layers for blocking, for simplicity they are all used on every platform;
- // layer1 is the iframe layer which is used to supress bleed through of underlying content
- // layer2 is the overlay layer which has opacity and a wait cursor (by default)
- // layer3 is the message content that is displayed while blocking
- var lyr1, lyr2, lyr3, s;
- if (msie || opts.forceIframe)
- lyr1 = $('<iframe class="blockUI" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;position:absolute;width:100%;height:100%;top:0;left:0" src="'+opts.iframeSrc+'"></iframe>');
- else
- lyr1 = $('<div class="blockUI" style="display:none"></div>');
-
- if (opts.theme)
- lyr2 = $('<div class="blockUI blockOverlay ui-widget-overlay" style="z-index:'+ (z++) +';display:none"></div>');
- else
- lyr2 = $('<div class="blockUI blockOverlay" style="z-index:'+ (z++) +';display:none;border:none;margin:0;padding:0;width:100%;height:100%;top:0;left:0"></div>');
-
- if (opts.theme && full) {
- s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:fixed">';
- if ( opts.title ) {
- s += '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>';
- }
- s += '<div class="ui-widget-content ui-dialog-content"></div>';
- s += '</div>';
- }
- else if (opts.theme) {
- s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement ui-dialog ui-widget ui-corner-all" style="z-index:'+(z+10)+';display:none;position:absolute">';
- if ( opts.title ) {
- s += '<div class="ui-widget-header ui-dialog-titlebar ui-corner-all blockTitle">'+(opts.title || '&nbsp;')+'</div>';
- }
- s += '<div class="ui-widget-content ui-dialog-content"></div>';
- s += '</div>';
- }
- else if (full) {
- s = '<div class="blockUI ' + opts.blockMsgClass + ' blockPage" style="z-index:'+(z+10)+';display:none;position:fixed"></div>';
- }
- else {
- s = '<div class="blockUI ' + opts.blockMsgClass + ' blockElement" style="z-index:'+(z+10)+';display:none;position:absolute"></div>';
- }
- lyr3 = $(s);
-
- // if we have a message, style it
- if (msg) {
- if (opts.theme) {
- lyr3.css(themedCSS);
- lyr3.addClass('ui-widget-content');
- }
- else
- lyr3.css(css);
- }
-
- // style the overlay
- if (!opts.theme /*&& (!opts.applyPlatformOpacityRules)*/)
- lyr2.css(opts.overlayCSS);
- lyr2.css('position', full ? 'fixed' : 'absolute');
-
- // make iframe layer transparent in IE
- if (msie || opts.forceIframe)
- lyr1.css('opacity',0.0);
-
- //$([lyr1[0],lyr2[0],lyr3[0]]).appendTo(full ? 'body' : el);
- var layers = [lyr1,lyr2,lyr3], $par = full ? $('body') : $(el);
- $.each(layers, function() {
- this.appendTo($par);
- });
-
- if (opts.theme && opts.draggable && $.fn.draggable) {
- lyr3.draggable({
- handle: '.ui-dialog-titlebar',
- cancel: 'li'
- });
- }
-
- // ie7 must use absolute positioning in quirks mode and to account for activex issues (when scrolling)
- var expr = setExpr && (!$.support.boxModel || $('object,embed', full ? null : el).length > 0);
- if (ie6 || expr) {
- // give body 100% height
- if (full && opts.allowBodyStretch && $.support.boxModel)
- $('html,body').css('height','100%');
-
- // fix ie6 issue when blocked element has a border width
- if ((ie6 || !$.support.boxModel) && !full) {
- var t = sz(el,'borderTopWidth'), l = sz(el,'borderLeftWidth');
- var fixT = t ? '(0 - '+t+')' : 0;
- var fixL = l ? '(0 - '+l+')' : 0;
- }
-
- // simulate fixed position
- $.each(layers, function(i,o) {
- var s = o[0].style;
- s.position = 'absolute';
- if (i < 2) {
- if (full)
- s.setExpression('height','Math.max(document.body.scrollHeight, document.body.offsetHeight) - (jQuery.support.boxModel?0:'+opts.quirksmodeOffsetHack+') + "px"');
- else
- s.setExpression('height','this.parentNode.offsetHeight + "px"');
- if (full)
- s.setExpression('width','jQuery.support.boxModel && document.documentElement.clientWidth || document.body.clientWidth + "px"');
- else
- s.setExpression('width','this.parentNode.offsetWidth + "px"');
- if (fixL) s.setExpression('left', fixL);
- if (fixT) s.setExpression('top', fixT);
- }
- else if (opts.centerY) {
- if (full) s.setExpression('top','(document.documentElement.clientHeight || document.body.clientHeight) / 2 - (this.offsetHeight / 2) + (blah = document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + "px"');
- s.marginTop = 0;
- }
- else if (!opts.centerY && full) {
- var top = (opts.css && opts.css.top) ? parseInt(opts.css.top, 10) : 0;
- var expression = '((document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop) + '+top+') + "px"';
- s.setExpression('top',expression);
- }
- });
- }
-
- // show the message
- if (msg) {
- if (opts.theme)
- lyr3.find('.ui-widget-content').append(msg);
- else
- lyr3.append(msg);
- if (msg.jquery || msg.nodeType)
- $(msg).show();
- }
-
- if ((msie || opts.forceIframe) && opts.showOverlay)
- lyr1.show(); // opacity is zero
- if (opts.fadeIn) {
- var cb = opts.onBlock ? opts.onBlock : noOp;
- var cb1 = (opts.showOverlay && !msg) ? cb : noOp;
- var cb2 = msg ? cb : noOp;
- if (opts.showOverlay)
- lyr2._fadeIn(opts.fadeIn, cb1);
- if (msg)
- lyr3._fadeIn(opts.fadeIn, cb2);
- }
- else {
- if (opts.showOverlay)
- lyr2.show();
- if (msg)
- lyr3.show();
- if (opts.onBlock)
- opts.onBlock();
- }
-
- // bind key and mouse events
- bind(1, el, opts);
-
- if (full) {
- pageBlock = lyr3[0];
- pageBlockEls = $(opts.focusableElements,pageBlock);
- if (opts.focusInput)
- setTimeout(focus, 20);
- }
- else
- center(lyr3[0], opts.centerX, opts.centerY);
-
- if (opts.timeout) {
- // auto-unblock
- var to = setTimeout(function() {
- if (full)
- $.unblockUI(opts);
- else
- $(el).unblock(opts);
- }, opts.timeout);
- $(el).data('blockUI.timeout', to);
- }
- }
-
- // remove the block
- function remove(el, opts) {
- var count;
- var full = (el == window);
- var $el = $(el);
- var data = $el.data('blockUI.history');
- var to = $el.data('blockUI.timeout');
- if (to) {
- clearTimeout(to);
- $el.removeData('blockUI.timeout');
- }
- opts = $.extend({}, $.blockUI.defaults, opts || {});
- bind(0, el, opts); // unbind events
-
- if (opts.onUnblock === null) {
- opts.onUnblock = $el.data('blockUI.onUnblock');
- $el.removeData('blockUI.onUnblock');
- }
-
- var els;
- if (full) // crazy selector to handle odd field errors in ie6/7
- els = $('body').children().filter('.blockUI').add('body > .blockUI');
- else
- els = $el.find('>.blockUI');
-
- // fix cursor issue
- if ( opts.cursorReset ) {
- if ( els.length > 1 )
- els[1].style.cursor = opts.cursorReset;
- if ( els.length > 2 )
- els[2].style.cursor = opts.cursorReset;
- }
-
- if (full)
- pageBlock = pageBlockEls = null;
-
- if (opts.fadeOut) {
- count = els.length;
- els.fadeOut(opts.fadeOut, function() {
- if ( --count === 0)
- reset(els,data,opts,el);
- });
- }
- else
- reset(els, data, opts, el);
- }
-
- // move blocking element back into the DOM where it started
- function reset(els,data,opts,el) {
- var $el = $(el);
- els.each(function(i,o) {
- // remove via DOM calls so we don't lose event handlers
- if (this.parentNode)
- this.parentNode.removeChild(this);
- });
-
- if (data && data.el) {
- data.el.style.display = data.display;
- data.el.style.position = data.position;
- if (data.parent)
- data.parent.appendChild(data.el);
- $el.removeData('blockUI.history');
- }
-
- if ($el.data('blockUI.static')) {
- $el.css('position', 'static'); // #22
- }
-
- if (typeof opts.onUnblock == 'function')
- opts.onUnblock(el,opts);
-
- // fix issue in Safari 6 where block artifacts remain until reflow
- var body = $(document.body), w = body.width(), cssW = body[0].style.width;
- body.width(w-1).width(w);
- body[0].style.width = cssW;
- }
-
- // bind/unbind the handler
- function bind(b, el, opts) {
- var full = el == window, $el = $(el);
-
- // don't bother unbinding if there is nothing to unbind
- if (!b && (full && !pageBlock || !full && !$el.data('blockUI.isBlocked')))
- return;
-
- $el.data('blockUI.isBlocked', b);
-
- // don't bind events when overlay is not in use or if bindEvents is false
- if (!full || !opts.bindEvents || (b && !opts.showOverlay))
- return;
-
- // bind anchors and inputs for mouse and key events
- var events = 'mousedown mouseup keydown keypress keyup touchstart touchend touchmove';
- if (b)
- $(document).bind(events, opts, handler);
- else
- $(document).unbind(events, handler);
-
- // former impl...
- // var $e = $('a,:input');
- // b ? $e.bind(events, opts, handler) : $e.unbind(events, handler);
- }
-
- // event handler to suppress keyboard/mouse events when blocking
- function handler(e) {
- // allow tab navigation (conditionally)
- if (e.keyCode && e.keyCode == 9) {
- if (pageBlock && e.data.constrainTabKey) {
- var els = pageBlockEls;
- var fwd = !e.shiftKey && e.target === els[els.length-1];
- var back = e.shiftKey && e.target === els[0];
- if (fwd || back) {
- setTimeout(function(){focus(back);},10);
- return false;
- }
- }
- }
- var opts = e.data;
- var target = $(e.target);
- if (target.hasClass('blockOverlay') && opts.onOverlayClick)
- opts.onOverlayClick();
-
- // allow events within the message content
- if (target.parents('div.' + opts.blockMsgClass).length > 0)
- return true;
-
- // allow events for content that is not being blocked
- return target.parents().children().filter('div.blockUI').length === 0;
- }
-
- function focus(back) {
- if (!pageBlockEls)
- return;
- var e = pageBlockEls[back===true ? pageBlockEls.length-1 : 0];
- if (e)
- e.focus();
- }
-
- function center(el, x, y) {
- var p = el.parentNode, s = el.style;
- var l = ((p.offsetWidth - el.offsetWidth)/2) - sz(p,'borderLeftWidth');
- var t = ((p.offsetHeight - el.offsetHeight)/2) - sz(p,'borderTopWidth');
- if (x) s.left = l > 0 ? (l+'px') : '0';
- if (y) s.top = t > 0 ? (t+'px') : '0';
- }
-
- function sz(el, p) {
- return parseInt($.css(el,p),10)||0;
- }
-
- }
-
-
- /*global define:true */
- if (typeof define === 'function' && define.amd && define.amd.jQuery) {
- define(['jquery'], setup);
- } else {
- setup(jQuery);
- }
-
-})();
diff --git a/vendor/assets/javascripts/jquery.history.js b/vendor/assets/javascripts/jquery.history.js
deleted file mode 100644
index 8d4edcd210e..00000000000
--- a/vendor/assets/javascripts/jquery.history.js
+++ /dev/null
@@ -1 +0,0 @@
-window.JSON||(window.JSON={}),function(){function f(a){return a<10?"0"+a:a}function quote(a){return escapable.lastIndex=0,escapable.test(a)?'"'+a.replace(escapable,function(a){var b=meta[a];return typeof b=="string"?b:"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)})+'"':'"'+a+'"'}function str(a,b){var c,d,e,f,g=gap,h,i=b[a];i&&typeof i=="object"&&typeof i.toJSON=="function"&&(i=i.toJSON(a)),typeof rep=="function"&&(i=rep.call(b,a,i));switch(typeof i){case"string":return quote(i);case"number":return isFinite(i)?String(i):"null";case"boolean":case"null":return String(i);case"object":if(!i)return"null";gap+=indent,h=[];if(Object.prototype.toString.apply(i)==="[object Array]"){f=i.length;for(c=0;c<f;c+=1)h[c]=str(c,i)||"null";return e=h.length===0?"[]":gap?"[\n"+gap+h.join(",\n"+gap)+"\n"+g+"]":"["+h.join(",")+"]",gap=g,e}if(rep&&typeof rep=="object"){f=rep.length;for(c=0;c<f;c+=1)d=rep[c],typeof d=="string"&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e))}else for(d in i)Object.hasOwnProperty.call(i,d)&&(e=str(d,i),e&&h.push(quote(d)+(gap?": ":":")+e));return e=h.length===0?"{}":gap?"{\n"+gap+h.join(",\n"+gap)+"\n"+g+"}":"{"+h.join(",")+"}",gap=g,e}}"use strict",typeof Date.prototype.toJSON!="function"&&(Date.prototype.toJSON=function(a){return isFinite(this.valueOf())?this.getUTCFullYear()+"-"+f(this.getUTCMonth()+1)+"-"+f(this.getUTCDate())+"T"+f(this.getUTCHours())+":"+f(this.getUTCMinutes())+":"+f(this.getUTCSeconds())+"Z":null},String.prototype.toJSON=Number.prototype.toJSON=Boolean.prototype.toJSON=function(a){return this.valueOf()});var JSON=window.JSON,cx=/[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,escapable=/[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,gap,indent,meta={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},rep;typeof JSON.stringify!="function"&&(JSON.stringify=function(a,b,c){var d;gap="",indent="";if(typeof c=="number")for(d=0;d<c;d+=1)indent+=" ";else typeof c=="string"&&(indent=c);rep=b;if(!b||typeof b=="function"||typeof b=="object"&&typeof b.length=="number")return str("",{"":a});throw new Error("JSON.stringify")}),typeof JSON.parse!="function"&&(JSON.parse=function(text,reviver){function walk(a,b){var c,d,e=a[b];if(e&&typeof e=="object")for(c in e)Object.hasOwnProperty.call(e,c)&&(d=walk(e,c),d!==undefined?e[c]=d:delete e[c]);return reviver.call(a,b,e)}var j;text=String(text),cx.lastIndex=0,cx.test(text)&&(text=text.replace(cx,function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)}));if(/^[\],:{}\s]*$/.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,"@").replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,"]").replace(/(?:^|:|,)(?:\s*\[)+/g,"")))return j=eval("("+text+")"),typeof reviver=="function"?walk({"":j},""):j;throw new SyntaxError("JSON.parse")})}(),function(a,b){"use strict";var c=a.History=a.History||{},d=a.jQuery;if(typeof c.Adapter!="undefined")throw new Error("History.js Adapter has already been loaded...");c.Adapter={bind:function(a,b,c){d(a).bind(b,c)},trigger:function(a,b,c){d(a).trigger(b,c)},extractEventData:function(a,c,d){var e=c&&c.originalEvent&&c.originalEvent[a]||d&&d[a]||b;return e},onDomLoad:function(a){d(a)}},typeof c.init!="undefined"&&c.init()}(window),function(a,b){"use strict";var c=a.document,d=a.setTimeout||d,e=a.clearTimeout||e,f=a.setInterval||f,g=a.History=a.History||{};if(typeof g.initHtml4!="undefined")throw new Error("History.js HTML4 Support has already been loaded...");g.initHtml4=function(){if(typeof g.initHtml4.initialized!="undefined")return!1;g.initHtml4.initialized=!0,g.enabled=!0,g.savedHashes=[],g.isLastHash=function(a){var b=g.getHashByIndex(),c;return c=a===b,c},g.saveHash=function(a){return g.isLastHash(a)?!1:(g.savedHashes.push(a),!0)},g.getHashByIndex=function(a){var b=null;return typeof a=="undefined"?b=g.savedHashes[g.savedHashes.length-1]:a<0?b=g.savedHashes[g.savedHashes.length+a]:b=g.savedHashes[a],b},g.discardedHashes={},g.discardedStates={},g.discardState=function(a,b,c){var d=g.getHashByState(a),e;return e={discardedState:a,backState:c,forwardState:b},g.discardedStates[d]=e,!0},g.discardHash=function(a,b,c){var d={discardedHash:a,backState:c,forwardState:b};return g.discardedHashes[a]=d,!0},g.discardedState=function(a){var b=g.getHashByState(a),c;return c=g.discardedStates[b]||!1,c},g.discardedHash=function(a){var b=g.discardedHashes[a]||!1;return b},g.recycleState=function(a){var b=g.getHashByState(a);return g.discardedState(a)&&delete g.discardedStates[b],!0},g.emulated.hashChange&&(g.hashChangeInit=function(){g.checkerFunction=null;var b="",d,e,h,i;return g.isInternetExplorer()?(d="historyjs-iframe",e=c.createElement("iframe"),e.setAttribute("id",d),e.style.display="none",c.body.appendChild(e),e.contentWindow.document.open(),e.contentWindow.document.close(),h="",i=!1,g.checkerFunction=function(){if(i)return!1;i=!0;var c=g.getHash()||"",d=g.unescapeHash(e.contentWindow.document.location.hash)||"";return c!==b?(b=c,d!==c&&(h=d=c,e.contentWindow.document.open(),e.contentWindow.document.close(),e.contentWindow.document.location.hash=g.escapeHash(c)),g.Adapter.trigger(a,"hashchange")):d!==h&&(h=d,g.setHash(d,!1)),i=!1,!0}):g.checkerFunction=function(){var c=g.getHash();return c!==b&&(b=c,g.Adapter.trigger(a,"hashchange")),!0},g.intervalList.push(f(g.checkerFunction,g.options.hashChangeInterval)),!0},g.Adapter.onDomLoad(g.hashChangeInit)),g.emulated.pushState&&(g.onHashChange=function(b){var d=b&&b.newURL||c.location.href,e=g.getHashByUrl(d),f=null,h=null,i=null,j;return g.isLastHash(e)?(g.busy(!1),!1):(g.doubleCheckComplete(),g.saveHash(e),e&&g.isTraditionalAnchor(e)?(g.Adapter.trigger(a,"anchorchange"),g.busy(!1),!1):(f=g.extractState(g.getFullUrl(e||c.location.href,!1),!0),g.isLastSavedState(f)?(g.busy(!1),!1):(h=g.getHashByState(f),j=g.discardedState(f),j?(g.getHashByIndex(-2)===g.getHashByState(j.forwardState)?g.back(!1):g.forward(!1),!1):(g.pushState(f.data,f.title,f.url,!1),!0))))},g.Adapter.bind(a,"hashchange",g.onHashChange),g.pushState=function(b,d,e,f){if(g.getHashByUrl(e))throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(f!==!1&&g.busy())return g.pushQueue({scope:g,callback:g.pushState,args:arguments,queue:f}),!1;g.busy(!0);var h=g.createStateObject(b,d,e),i=g.getHashByState(h),j=g.getState(!1),k=g.getHashByState(j),l=g.getHash();return g.storeState(h),g.expectedStateId=h.id,g.recycleState(h),g.setTitle(h),i===k?(g.busy(!1),!1):i!==l&&i!==g.getShortUrl(c.location.href)?(g.setHash(i,!1),!1):(g.saveState(h),g.Adapter.trigger(a,"statechange"),g.busy(!1),!0)},g.replaceState=function(a,b,c,d){if(g.getHashByUrl(c))throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(d!==!1&&g.busy())return g.pushQueue({scope:g,callback:g.replaceState,args:arguments,queue:d}),!1;g.busy(!0);var e=g.createStateObject(a,b,c),f=g.getState(!1),h=g.getStateByIndex(-2);return g.discardState(f,e,h),g.pushState(e.data,e.title,e.url,!1),!0}),g.emulated.pushState&&g.getHash()&&!g.emulated.hashChange&&g.Adapter.onDomLoad(function(){g.Adapter.trigger(a,"hashchange")})},typeof g.init!="undefined"&&g.init()}(window),function(a,b){"use strict";var c=a.console||b,d=a.document,e=a.navigator,f=a.sessionStorage||!1,g=a.setTimeout,h=a.clearTimeout,i=a.setInterval,j=a.clearInterval,k=a.JSON,l=a.alert,m=a.History=a.History||{},n=a.history;k.stringify=k.stringify||k.encode,k.parse=k.parse||k.decode;if(typeof m.init!="undefined")throw new Error("History.js Core has already been loaded...");m.init=function(){return typeof m.Adapter=="undefined"?!1:(typeof m.initCore!="undefined"&&m.initCore(),typeof m.initHtml4!="undefined"&&m.initHtml4(),!0)},m.initCore=function(){if(typeof m.initCore.initialized!="undefined")return!1;m.initCore.initialized=!0,m.options=m.options||{},m.options.hashChangeInterval=m.options.hashChangeInterval||100,m.options.safariPollInterval=m.options.safariPollInterval||500,m.options.doubleCheckInterval=m.options.doubleCheckInterval||500,m.options.storeInterval=m.options.storeInterval||1e3,m.options.busyDelay=m.options.busyDelay||250,m.options.debug=m.options.debug||!1,m.options.initialTitle=m.options.initialTitle||d.title,m.intervalList=[],m.clearAllIntervals=function(){var a,b=m.intervalList;if(typeof b!="undefined"&&b!==null){for(a=0;a<b.length;a++)j(b[a]);m.intervalList=null}},m.debug=function(){(m.options.debug||!1)&&m.log.apply(m,arguments)},m.log=function(){var a=typeof c!="undefined"&&typeof c.log!="undefined"&&typeof c.log.apply!="undefined",b=d.getElementById("log"),e,f,g,h,i;a?(h=Array.prototype.slice.call(arguments),e=h.shift(),typeof c.debug!="undefined"?c.debug.apply(c,[e,h]):c.log.apply(c,[e,h])):e="\n"+arguments[0]+"\n";for(f=1,g=arguments.length;f<g;++f){i=arguments[f];if(typeof i=="object"&&typeof k!="undefined")try{i=k.stringify(i)}catch(j){}e+="\n"+i+"\n"}return b?(b.value+=e+"\n-----\n",b.scrollTop=b.scrollHeight-b.clientHeight):a||l(e),!0},m.getInternetExplorerMajorVersion=function(){var a=m.getInternetExplorerMajorVersion.cached=typeof m.getInternetExplorerMajorVersion.cached!="undefined"?m.getInternetExplorerMajorVersion.cached:function(){var a=3,b=d.createElement("div"),c=b.getElementsByTagName("i");while((b.innerHTML="<!--[if gt IE "+ ++a+"]><i></i><![endif]-->")&&c[0]);return a>4?a:!1}();return a},m.isInternetExplorer=function(){var a=m.isInternetExplorer.cached=typeof m.isInternetExplorer.cached!="undefined"?m.isInternetExplorer.cached:Boolean(m.getInternetExplorerMajorVersion());return a},m.emulated={pushState:!Boolean(a.history&&a.history.pushState&&a.history.replaceState&&!/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i.test(e.userAgent)&&!/AppleWebKit\/5([0-2]|3[0-2])/i.test(e.userAgent)),hashChange:Boolean(!("onhashchange"in a||"onhashchange"in d)||m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<8)},m.enabled=!m.emulated.pushState,m.bugs={setHash:Boolean(!m.emulated.pushState&&e.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(e.userAgent)),safariPoll:Boolean(!m.emulated.pushState&&e.vendor==="Apple Computer, Inc."&&/AppleWebKit\/5([0-2]|3[0-3])/.test(e.userAgent)),ieDoubleCheck:Boolean(m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<8),hashEscape:Boolean(m.isInternetExplorer()&&m.getInternetExplorerMajorVersion()<7)},m.isEmptyObject=function(a){for(var b in a)return!1;return!0},m.cloneObject=function(a){var b,c;return a?(b=k.stringify(a),c=k.parse(b)):c={},c},m.getRootUrl=function(){var a=d.location.protocol+"//"+(d.location.hostname||d.location.host);if(d.location.port||!1)a+=":"+d.location.port;return a+="/",a},m.getBaseHref=function(){var a=d.getElementsByTagName("base"),b=null,c="";return a.length===1&&(b=a[0],c=b.href.replace(/[^\/]+$/,"")),c=c.replace(/\/+$/,""),c&&(c+="/"),c},m.getBaseUrl=function(){var a=m.getBaseHref()||m.getBasePageUrl()||m.getRootUrl();return a},m.getPageUrl=function(){var a=m.getState(!1,!1),b=(a||{}).url||d.location.href,c;return c=b.replace(/\/+$/,"").replace(/[^\/]+$/,function(a,b,c){return/\./.test(a)?a:a+"/"}),c},m.getBasePageUrl=function(){var a=d.location.href.replace(/[#\?].*/,"").replace(/[^\/]+$/,function(a,b,c){return/[^\/]$/.test(a)?"":a}).replace(/\/+$/,"")+"/";return a},m.getFullUrl=function(a,b){var c=a,d=a.substring(0,1);return b=typeof b=="undefined"?!0:b,/[a-z]+\:\/\//.test(a)||(d==="/"?c=m.getRootUrl()+a.replace(/^\/+/,""):d==="#"?c=m.getPageUrl().replace(/#.*/,"")+a:d==="?"?c=m.getPageUrl().replace(/[\?#].*/,"")+a:b?c=m.getBaseUrl()+a.replace(/^(\.\/)+/,""):c=m.getBasePageUrl()+a.replace(/^(\.\/)+/,"")),c.replace(/\#$/,"")},m.getShortUrl=function(a){var b=a,c=m.getBaseUrl(),d=m.getRootUrl();return m.emulated.pushState&&(b=b.replace(c,"")),b=b.replace(d,"/"),m.isTraditionalAnchor(b)&&(b="./"+b),b=b.replace(/^(\.\/)+/g,"./").replace(/\#$/,""),b},m.store={},m.idToState=m.idToState||{},m.stateToId=m.stateToId||{},m.urlToId=m.urlToId||{},m.storedStates=m.storedStates||[],m.savedStates=m.savedStates||[],m.normalizeStore=function(){m.store.idToState=m.store.idToState||{},m.store.urlToId=m.store.urlToId||{},m.store.stateToId=m.store.stateToId||{}},m.getState=function(a,b){typeof a=="undefined"&&(a=!0),typeof b=="undefined"&&(b=!0);var c=m.getLastSavedState();return!c&&b&&(c=m.createStateObject()),a&&(c=m.cloneObject(c),c.url=c.cleanUrl||c.url),c},m.getIdByState=function(a){var b=m.extractId(a.url),c;if(!b){c=m.getStateString(a);if(typeof m.stateToId[c]!="undefined")b=m.stateToId[c];else if(typeof m.store.stateToId[c]!="undefined")b=m.store.stateToId[c];else{for(;;){b=(new Date).getTime()+String(Math.random()).replace(/\D/g,"");if(typeof m.idToState[b]=="undefined"&&typeof m.store.idToState[b]=="undefined")break}m.stateToId[c]=b,m.idToState[b]=a}}return b},m.normalizeState=function(a){var b,c;if(!a||typeof a!="object")a={};if(typeof a.normalized!="undefined")return a;if(!a.data||typeof a.data!="object")a.data={};b={},b.normalized=!0,b.title=a.title||"",b.url=m.getFullUrl(m.unescapeString(a.url||d.location.href)),b.hash=m.getShortUrl(b.url),b.data=m.cloneObject(a.data),b.id=m.getIdByState(b),b.cleanUrl=b.url.replace(/\??\&_suid.*/,""),b.url=b.cleanUrl,c=!m.isEmptyObject(b.data);if(b.title||c)b.hash=m.getShortUrl(b.url).replace(/\??\&_suid.*/,""),/\?/.test(b.hash)||(b.hash+="?"),b.hash+="&_suid="+b.id;return b.hashedUrl=m.getFullUrl(b.hash),(m.emulated.pushState||m.bugs.safariPoll)&&m.hasUrlDuplicate(b)&&(b.url=b.hashedUrl),b},m.createStateObject=function(a,b,c){var d={data:a,title:b,url:c};return d=m.normalizeState(d),d},m.getStateById=function(a){a=String(a);var c=m.idToState[a]||m.store.idToState[a]||b;return c},m.getStateString=function(a){var b,c,d;return b=m.normalizeState(a),c={data:b.data,title:a.title,url:a.url},d=k.stringify(c),d},m.getStateId=function(a){var b,c;return b=m.normalizeState(a),c=b.id,c},m.getHashByState=function(a){var b,c;return b=m.normalizeState(a),c=b.hash,c},m.extractId=function(a){var b,c,d;return c=/(.*)\&_suid=([0-9]+)$/.exec(a),d=c?c[1]||a:a,b=c?String(c[2]||""):"",b||!1},m.isTraditionalAnchor=function(a){var b=!/[\/\?\.]/.test(a);return b},m.extractState=function(a,b){var c=null,d,e;return b=b||!1,d=m.extractId(a),d&&(c=m.getStateById(d)),c||(e=m.getFullUrl(a),d=m.getIdByUrl(e)||!1,d&&(c=m.getStateById(d)),!c&&b&&!m.isTraditionalAnchor(a)&&(c=m.createStateObject(null,null,e))),c},m.getIdByUrl=function(a){var c=m.urlToId[a]||m.store.urlToId[a]||b;return c},m.getLastSavedState=function(){return m.savedStates[m.savedStates.length-1]||b},m.getLastStoredState=function(){return m.storedStates[m.storedStates.length-1]||b},m.hasUrlDuplicate=function(a){var b=!1,c;return c=m.extractState(a.url),b=c&&c.id!==a.id,b},m.storeState=function(a){return m.urlToId[a.url]=a.id,m.storedStates.push(m.cloneObject(a)),a},m.isLastSavedState=function(a){var b=!1,c,d,e;return m.savedStates.length&&(c=a.id,d=m.getLastSavedState(),e=d.id,b=c===e),b},m.saveState=function(a){return m.isLastSavedState(a)?!1:(m.savedStates.push(m.cloneObject(a)),!0)},m.getStateByIndex=function(a){var b=null;return typeof a=="undefined"?b=m.savedStates[m.savedStates.length-1]:a<0?b=m.savedStates[m.savedStates.length+a]:b=m.savedStates[a],b},m.getHash=function(){var a=m.unescapeHash(d.location.hash);return a},m.unescapeString=function(b){var c=b,d;for(;;){d=a.unescape(c);if(d===c)break;c=d}return c},m.unescapeHash=function(a){var b=m.normalizeHash(a);return b=m.unescapeString(b),b},m.normalizeHash=function(a){var b=a.replace(/[^#]*#/,"").replace(/#.*/,"");return b},m.setHash=function(a,b){var c,e,f;return b!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.setHash,args:arguments,queue:b}),!1):(c=m.escapeHash(a),m.busy(!0),e=m.extractState(a,!0),e&&!m.emulated.pushState?m.pushState(e.data,e.title,e.url,!1):d.location.hash!==c&&(m.bugs.setHash?(f=m.getPageUrl(),m.pushState(null,null,f+"#"+c,!1)):d.location.hash=c),m)},m.escapeHash=function(b){var c=m.normalizeHash(b);return c=a.escape(c),m.bugs.hashEscape||(c=c.replace(/\%21/g,"!").replace(/\%26/g,"&").replace(/\%3D/g,"=").replace(/\%3F/g,"?")),c},m.getHashByUrl=function(a){var b=String(a).replace(/([^#]*)#?([^#]*)#?(.*)/,"$2");return b=m.unescapeHash(b),b},m.setTitle=function(a){var b=a.title,c;b||(c=m.getStateByIndex(0),c&&c.url===a.url&&(b=c.title||m.options.initialTitle));try{d.getElementsByTagName("title")[0].innerHTML=b.replace("<","&lt;").replace(">","&gt;").replace(" & "," &amp; ")}catch(e){}return d.title=b,m},m.queues=[],m.busy=function(a){typeof a!="undefined"?m.busy.flag=a:typeof m.busy.flag=="undefined"&&(m.busy.flag=!1);if(!m.busy.flag){h(m.busy.timeout);var b=function(){var a,c,d;if(m.busy.flag)return;for(a=m.queues.length-1;a>=0;--a){c=m.queues[a];if(c.length===0)continue;d=c.shift(),m.fireQueueItem(d),m.busy.timeout=g(b,m.options.busyDelay)}};m.busy.timeout=g(b,m.options.busyDelay)}return m.busy.flag},m.busy.flag=!1,m.fireQueueItem=function(a){return a.callback.apply(a.scope||m,a.args||[])},m.pushQueue=function(a){return m.queues[a.queue||0]=m.queues[a.queue||0]||[],m.queues[a.queue||0].push(a),m},m.queue=function(a,b){return typeof a=="function"&&(a={callback:a}),typeof b!="undefined"&&(a.queue=b),m.busy()?m.pushQueue(a):m.fireQueueItem(a),m},m.clearQueue=function(){return m.busy.flag=!1,m.queues=[],m},m.stateChanged=!1,m.doubleChecker=!1,m.doubleCheckComplete=function(){return m.stateChanged=!0,m.doubleCheckClear(),m},m.doubleCheckClear=function(){return m.doubleChecker&&(h(m.doubleChecker),m.doubleChecker=!1),m},m.doubleCheck=function(a){return m.stateChanged=!1,m.doubleCheckClear(),m.bugs.ieDoubleCheck&&(m.doubleChecker=g(function(){return m.doubleCheckClear(),m.stateChanged||a(),!0},m.options.doubleCheckInterval)),m},m.safariStatePoll=function(){var b=m.extractState(d.location.href),c;if(!m.isLastSavedState(b))c=b;else return;return c||(c=m.createStateObject()),m.Adapter.trigger(a,"popstate"),m},m.back=function(a){return a!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.back,args:arguments,queue:a}),!1):(m.busy(!0),m.doubleCheck(function(){m.back(!1)}),n.go(-1),!0)},m.forward=function(a){return a!==!1&&m.busy()?(m.pushQueue({scope:m,callback:m.forward,args:arguments,queue:a}),!1):(m.busy(!0),m.doubleCheck(function(){m.forward(!1)}),n.go(1),!0)},m.go=function(a,b){var c;if(a>0)for(c=1;c<=a;++c)m.forward(b);else{if(!(a<0))throw new Error("History.go: History.go requires a positive or negative integer passed.");for(c=-1;c>=a;--c)m.back(b)}return m};if(m.emulated.pushState){var o=function(){};m.pushState=m.pushState||o,m.replaceState=m.replaceState||o}else m.onPopState=function(b,c){var e=!1,f=!1,g,h;return m.doubleCheckComplete(),g=m.getHash(),g?(h=m.extractState(g||d.location.href,!0),h?m.replaceState(h.data,h.title,h.url,!1):(m.Adapter.trigger(a,"anchorchange"),m.busy(!1)),m.expectedStateId=!1,!1):(e=m.Adapter.extractEventData("state",b,c)||!1,e?f=m.getStateById(e):m.expectedStateId?f=m.getStateById(m.expectedStateId):f=m.extractState(d.location.href),f||(f=m.createStateObject(null,null,d.location.href)),m.expectedStateId=!1,m.isLastSavedState(f)?(m.busy(!1),!1):(m.storeState(f),m.saveState(f),m.setTitle(f),m.Adapter.trigger(a,"statechange"),m.busy(!1),!0))},m.Adapter.bind(a,"popstate",m.onPopState),m.pushState=function(b,c,d,e){if(m.getHashByUrl(d)&&m.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(e!==!1&&m.busy())return m.pushQueue({scope:m,callback:m.pushState,args:arguments,queue:e}),!1;m.busy(!0);var f=m.createStateObject(b,c,d);return m.isLastSavedState(f)?m.busy(!1):(m.storeState(f),m.expectedStateId=f.id,n.pushState(f.id,f.title,f.url),m.Adapter.trigger(a,"popstate")),!0},m.replaceState=function(b,c,d,e){if(m.getHashByUrl(d)&&m.emulated.pushState)throw new Error("History.js does not support states with fragement-identifiers (hashes/anchors).");if(e!==!1&&m.busy())return m.pushQueue({scope:m,callback:m.replaceState,args:arguments,queue:e}),!1;m.busy(!0);var f=m.createStateObject(b,c,d);return m.isLastSavedState(f)?m.busy(!1):(m.storeState(f),m.expectedStateId=f.id,n.replaceState(f.id,f.title,f.url),m.Adapter.trigger(a,"popstate")),!0};if(f){try{m.store=k.parse(f.getItem("History.store"))||{}}catch(p){m.store={}}m.normalizeStore()}else m.store={},m.normalizeStore();m.Adapter.bind(a,"beforeunload",m.clearAllIntervals),m.Adapter.bind(a,"unload",m.clearAllIntervals),m.saveState(m.storeState(m.extractState(d.location.href,!0))),f&&(m.onUnload=function(){var a,b;try{a=k.parse(f.getItem("History.store"))||{}}catch(c){a={}}a.idToState=a.idToState||{},a.urlToId=a.urlToId||{},a.stateToId=a.stateToId||{};for(b in m.idToState){if(!m.idToState.hasOwnProperty(b))continue;a.idToState[b]=m.idToState[b]}for(b in m.urlToId){if(!m.urlToId.hasOwnProperty(b))continue;a.urlToId[b]=m.urlToId[b]}for(b in m.stateToId){if(!m.stateToId.hasOwnProperty(b))continue;a.stateToId[b]=m.stateToId[b]}m.store=a,m.normalizeStore(),f.setItem("History.store",k.stringify(a))},m.intervalList.push(i(m.onUnload,m.options.storeInterval)),m.Adapter.bind(a,"beforeunload",m.onUnload),m.Adapter.bind(a,"unload",m.onUnload));if(!m.emulated.pushState){m.bugs.safariPoll&&m.intervalList.push(i(m.safariStatePoll,m.options.safariPollInterval));if(e.vendor==="Apple Computer, Inc."||(e.appCodeName||"")==="Mozilla")m.Adapter.bind(a,"hashchange",function(){m.Adapter.trigger(a,"popstate")}),m.getHash()&&m.Adapter.onDomLoad(function(){m.Adapter.trigger(a,"hashchange")})}},m.init()}(window) \ No newline at end of file