summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVinnie Okada <vokada@mrvinn.com>2014-12-07 20:25:58 -0700
committerVinnie Okada <vokada@mrvinn.com>2014-12-07 20:25:58 -0700
commit742e6eeed221489d5f35bdfde2e6ce55db75d25f (patch)
tree710c01fbd18e81a7590819161434e19855e35a97
parent7a5072c5a8f03cd7342a5f8e74e1fde0250ce360 (diff)
parentbbf9953b99d59801c72dd7b9550ee149ca77bfcf (diff)
downloadgitlab-ce-742e6eeed221489d5f35bdfde2e6ce55db75d25f.tar.gz
Merge branch 'upstream-master' into markdown-preview
Conflicts: spec/routing/project_routing_spec.rb
-rw-r--r--.gitignore1
-rw-r--r--CHANGELOG78
-rw-r--r--CONTRIBUTING.md32
-rw-r--r--GITLAB_SHELL_VERSION2
-rw-r--r--Gemfile11
-rw-r--r--Gemfile.lock52
-rw-r--r--README.md12
-rw-r--r--VERSION2
-rw-r--r--app/assets/javascripts/activities.js.coffee4
-rw-r--r--app/assets/javascripts/admin.js.coffee4
-rw-r--r--app/assets/javascripts/application.js.coffee3
-rw-r--r--app/assets/javascripts/blob.js.coffee5
-rw-r--r--app/assets/javascripts/commit.js.coffee4
-rw-r--r--app/assets/javascripts/commit/file.js.coffee4
-rw-r--r--app/assets/javascripts/commit/image-file.js.coffee4
-rw-r--r--app/assets/javascripts/commits.js.coffee4
-rw-r--r--app/assets/javascripts/confirm_danger_modal.js.coffee4
-rw-r--r--app/assets/javascripts/dashboard.js.coffee5
-rw-r--r--app/assets/javascripts/diff.js.coffee5
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee35
-rw-r--r--app/assets/javascripts/flash.js.coffee4
-rw-r--r--app/assets/javascripts/group_avatar.js.coffee9
-rw-r--r--app/assets/javascripts/groups.js.coffee15
-rw-r--r--app/assets/javascripts/issue.js.coffee4
-rw-r--r--app/assets/javascripts/labels.js.coffee4
-rw-r--r--app/assets/javascripts/merge_request.js.coffee4
-rw-r--r--app/assets/javascripts/milestone.js.coffee4
-rw-r--r--app/assets/javascripts/namespace_select.js.coffee43
-rw-r--r--app/assets/javascripts/notes.js.coffee6
-rw-r--r--app/assets/javascripts/notes_votes.js.coffee4
-rw-r--r--app/assets/javascripts/password_strength.js.coffee31
-rw-r--r--app/assets/javascripts/profile.js.coffee45
-rw-r--r--app/assets/javascripts/project.js.coffee80
-rw-r--r--app/assets/javascripts/project_fork.js.coffee5
-rw-r--r--app/assets/javascripts/project_import.js.coffee4
-rw-r--r--app/assets/javascripts/project_new.js.coffee25
-rw-r--r--app/assets/javascripts/project_show.js.coffee15
-rw-r--r--app/assets/javascripts/project_users_select.js.coffee19
-rw-r--r--app/assets/javascripts/search_autocomplete.js.coffee4
-rw-r--r--app/assets/javascripts/stat_graph.js.coffee2
-rw-r--r--app/assets/javascripts/stat_graph_contributors.js.coffee2
-rw-r--r--app/assets/javascripts/stat_graph_contributors_graph.js.coffee6
-rw-r--r--app/assets/javascripts/team_members.js.coffee6
-rw-r--r--app/assets/javascripts/tree.js.coffee4
-rw-r--r--app/assets/javascripts/user.js.coffee3
-rw-r--r--app/assets/javascripts/users_select.js.coffee53
-rw-r--r--app/assets/javascripts/wikis.js.coffee5
-rw-r--r--app/assets/stylesheets/behaviors.scss26
-rw-r--r--app/assets/stylesheets/generic/common.scss4
-rw-r--r--app/assets/stylesheets/generic/files.scss3
-rw-r--r--app/assets/stylesheets/generic/highlight.scss4
-rw-r--r--app/assets/stylesheets/generic/issue_box.scss5
-rw-r--r--app/assets/stylesheets/generic/mobile.scss17
-rw-r--r--app/assets/stylesheets/generic/timeline.scss17
-rw-r--r--app/assets/stylesheets/highlight/monokai.scss31
-rw-r--r--app/assets/stylesheets/highlight/white.scss8
-rw-r--r--app/assets/stylesheets/main/fonts.scss2
-rw-r--r--app/assets/stylesheets/main/mixins.scss6
-rw-r--r--app/assets/stylesheets/sections/events.scss31
-rw-r--r--app/assets/stylesheets/sections/header.scss1
-rw-r--r--app/assets/stylesheets/sections/issues.scss12
-rw-r--r--app/assets/stylesheets/sections/merge_requests.scss6
-rw-r--r--app/assets/stylesheets/sections/nav.scss2
-rw-r--r--app/assets/stylesheets/sections/notes.scss7
-rw-r--r--app/assets/stylesheets/sections/profile.scss17
-rw-r--r--app/assets/stylesheets/sections/projects.scss38
-rw-r--r--app/controllers/admin/background_jobs_controller.rb2
-rw-r--r--app/controllers/admin/projects_controller.rb10
-rw-r--r--app/controllers/application_controller.rb60
-rw-r--r--app/controllers/explore/groups_controller.rb3
-rw-r--r--app/controllers/explore/projects_controller.rb3
-rw-r--r--app/controllers/groups/group_members_controller.rb1
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb27
-rw-r--r--app/controllers/projects/application_controller.rb27
-rw-r--r--app/controllers/projects/base_tree_controller.rb3
-rw-r--r--app/controllers/projects/blame_controller.rb3
-rw-r--r--app/controllers/projects/blob_controller.rb7
-rw-r--r--app/controllers/projects/branches_controller.rb11
-rw-r--r--app/controllers/projects/commit_controller.rb15
-rw-r--r--app/controllers/projects/commits_controller.rb3
-rw-r--r--app/controllers/projects/compare_controller.rb3
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb2
-rw-r--r--app/controllers/projects/edit_tree_controller.rb4
-rw-r--r--app/controllers/projects/forks_controller.rb22
-rw-r--r--app/controllers/projects/graphs_controller.rb3
-rw-r--r--app/controllers/projects/hooks_controller.rb1
-rw-r--r--app/controllers/projects/imports_controller.rb49
-rw-r--r--app/controllers/projects/issues_controller.rb16
-rw-r--r--app/controllers/projects/merge_requests_controller.rb15
-rw-r--r--app/controllers/projects/milestones_controller.rb4
-rw-r--r--app/controllers/projects/network_controller.rb3
-rw-r--r--app/controllers/projects/new_tree_controller.rb2
-rw-r--r--app/controllers/projects/raw_controller.rb3
-rw-r--r--app/controllers/projects/refs_controller.rb3
-rw-r--r--app/controllers/projects/repositories_controller.rb12
-rw-r--r--app/controllers/projects/services_controller.rb3
-rw-r--r--app/controllers/projects/tags_controller.rb6
-rw-r--r--app/controllers/projects/team_members_controller.rb8
-rw-r--r--app/controllers/projects_controller.rb76
-rw-r--r--app/controllers/snippets_controller.rb2
-rw-r--r--app/controllers/users_controller.rb7
-rw-r--r--app/finders/issuable_finder.rb15
-rw-r--r--app/finders/snippets_finder.rb2
-rw-r--r--app/helpers/blob_helper.rb11
-rw-r--r--app/helpers/commits_helper.rb8
-rw-r--r--app/helpers/emails_helper.rb32
-rw-r--r--app/helpers/events_helper.rb22
-rw-r--r--app/helpers/git_helper.rb5
-rw-r--r--app/helpers/gitlab_markdown_helper.rb12
-rw-r--r--app/helpers/issues_helper.rb28
-rw-r--r--app/helpers/namespaces_helper.rb8
-rw-r--r--app/helpers/oauth_helper.rb4
-rw-r--r--app/helpers/profile_helper.rb6
-rw-r--r--app/helpers/projects_helper.rb4
-rw-r--r--app/helpers/sorting_helper.rb17
-rw-r--r--app/helpers/tree_helper.rb2
-rw-r--r--app/mailers/emails/profile.rb3
-rw-r--r--app/mailers/notify.rb9
-rw-r--r--app/models/ability.rb8
-rw-r--r--app/models/concerns/issuable.rb3
-rw-r--r--app/models/concerns/mentionable.rb6
-rw-r--r--app/models/event.rb4
-rw-r--r--app/models/hooks/web_hook.rb8
-rw-r--r--app/models/identity.rb5
-rw-r--r--app/models/merge_request.rb14
-rw-r--r--app/models/namespace.rb4
-rw-r--r--app/models/note.rb51
-rw-r--r--app/models/project.rb77
-rw-r--r--app/models/project_services/bamboo_service.rb105
-rw-r--r--app/models/project_services/buildbox_service.rb2
-rw-r--r--app/models/project_services/flowdock_service.rb3
-rw-r--r--app/models/project_services/gemnasium_service.rb3
-rw-r--r--app/models/project_services/gitlab_ci_service.rb4
-rw-r--r--app/models/project_services/hipchat_service.rb12
-rw-r--r--app/models/project_services/slack_service.rb13
-rw-r--r--app/models/service.rb4
-rw-r--r--app/models/user.rb40
-rw-r--r--app/services/base_service.rb6
-rw-r--r--app/services/files/base_service.rb6
-rw-r--r--app/services/git_push_service.rb24
-rw-r--r--app/services/issues/base_service.rb2
-rw-r--r--app/services/merge_requests/base_merge_service.rb3
-rw-r--r--app/services/merge_requests/base_service.rb3
-rw-r--r--app/services/merge_requests/refresh_service.rb86
-rw-r--r--app/services/notification_service.rb5
-rw-r--r--app/services/projects/create_service.rb50
-rw-r--r--app/services/projects/fork_service.rb24
-rw-r--r--app/services/system_hooks_service.rb2
-rw-r--r--app/services/test_hook_service.rb3
-rw-r--r--app/views/admin/background_jobs/show.html.haml4
-rw-r--r--app/views/admin/groups/_form.html.haml25
-rw-r--r--app/views/admin/groups/index.html.haml2
-rw-r--r--app/views/admin/groups/show.html.haml6
-rw-r--r--app/views/admin/logs/show.html.haml87
-rw-r--r--app/views/admin/projects/index.html.haml10
-rw-r--r--app/views/admin/users/index.html.haml5
-rw-r--r--app/views/admin/users/show.html.haml2
-rw-r--r--app/views/dashboard/_zero_authorized_projects.html.haml2
-rw-r--r--app/views/dashboard/issues.atom.builder21
-rw-r--r--app/views/dashboard/projects.html.haml9
-rw-r--r--app/views/dashboard/show.atom.builder25
-rw-r--r--app/views/devise/passwords/edit.html.haml4
-rw-r--r--app/views/devise/registrations/new.html.haml4
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml2
-rw-r--r--app/views/devise/sessions/_oauth_providers.html.haml2
-rw-r--r--app/views/devise/sessions/new.html.haml20
-rw-r--r--app/views/explore/groups/index.html.haml10
-rw-r--r--app/views/explore/projects/index.html.haml10
-rw-r--r--app/views/groups/edit.html.haml19
-rw-r--r--app/views/groups/issues.atom.builder13
-rw-r--r--app/views/groups/members.html.haml2
-rw-r--r--app/views/groups/new.html.haml25
-rw-r--r--app/views/groups/show.atom.builder24
-rw-r--r--app/views/layouts/_search.html.haml13
-rw-r--r--app/views/layouts/notify.html.haml1
-rw-r--r--app/views/notify/new_ssh_key_email.html.haml2
-rw-r--r--app/views/notify/new_ssh_key_email.text.erb4
-rw-r--r--app/views/profiles/passwords/edit.html.haml2
-rw-r--r--app/views/profiles/passwords/new.html.haml2
-rw-r--r--app/views/profiles/show.html.haml2
-rw-r--r--app/views/projects/_home_panel.html.haml6
-rw-r--r--app/views/projects/_issuable_filter.html.haml72
-rw-r--r--app/views/projects/_issuable_form.html.haml4
-rw-r--r--app/views/projects/_issues_nav.html.haml55
-rw-r--r--app/views/projects/blob/_actions.html.haml3
-rw-r--r--app/views/projects/blob/_remove.html.haml2
-rw-r--r--app/views/projects/branches/index.html.haml4
-rw-r--r--app/views/projects/branches/new.html.haml2
-rw-r--r--app/views/projects/compare/_form.html.haml2
-rw-r--r--app/views/projects/create.js.haml13
-rw-r--r--app/views/projects/diffs/_file.html.haml5
-rw-r--r--app/views/projects/edit.html.haml8
-rw-r--r--app/views/projects/fork.html.haml19
-rw-r--r--app/views/projects/forks/error.html.haml20
-rw-r--r--app/views/projects/forks/new.html.haml38
-rw-r--r--app/views/projects/import.html.haml31
-rw-r--r--app/views/projects/imports/new.html.haml21
-rw-r--r--app/views/projects/imports/show.html.haml9
-rw-r--r--app/views/projects/issues/_head.html.haml36
-rw-r--r--app/views/projects/issues/_issue.html.haml6
-rw-r--r--app/views/projects/issues/_issue_context.html.haml3
-rw-r--r--app/views/projects/issues/_issues.html.haml50
-rw-r--r--app/views/projects/issues/index.atom.builder13
-rw-r--r--app/views/projects/issues/index.html.haml2
-rw-r--r--app/views/projects/issues/show.html.haml6
-rw-r--r--app/views/projects/labels/index.html.haml2
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml6
-rw-r--r--app/views/projects/merge_requests/index.html.haml63
-rw-r--r--app/views/projects/merge_requests/show/_how_to_merge.html.haml6
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml4
-rw-r--r--app/views/projects/merge_requests/show/_state_widget.html.haml13
-rw-r--r--app/views/projects/milestones/index.html.haml2
-rw-r--r--app/views/projects/milestones/show.html.haml2
-rw-r--r--app/views/projects/new.html.haml2
-rw-r--r--app/views/projects/new_tree/show.html.haml4
-rw-r--r--app/views/projects/no_repo.html.haml22
-rw-r--r--app/views/projects/notes/_form.html.haml2
-rw-r--r--app/views/projects/notes/_note.html.haml4
-rw-r--r--app/views/projects/services/_form.html.haml4
-rw-r--r--app/views/projects/tags/_tag.html.haml3
-rw-r--r--app/views/projects/tags/new.html.haml2
-rw-r--r--app/views/projects/team_members/_team_member.html.haml2
-rw-r--r--app/views/projects/team_members/import.html.haml2
-rw-r--r--app/views/search/_project_filter.html.haml1
-rw-r--r--app/views/search/show.html.haml2
-rw-r--r--app/views/shared/_choose_group_avatar_button.html.haml7
-rw-r--r--app/views/shared/_group_form.html.haml12
-rw-r--r--app/views/shared/_group_tips.html.haml6
-rw-r--r--app/views/shared/_promo.html.haml2
-rw-r--r--app/views/shared/_sort_dropdown.html.haml8
-rw-r--r--app/views/users/_groups.html.haml2
-rw-r--r--app/views/users/show.atom.builder12
-rw-r--r--app/views/users/show.html.haml10
-rw-r--r--app/workers/emails_on_push_worker.rb3
-rw-r--r--app/workers/project_service_worker.rb9
-rw-r--r--config/application.rb8
-rw-r--r--config/gitlab.yml.example7
-rw-r--r--config/initializers/1_settings.rb4
-rw-r--r--config/initializers/4_sidekiq.rb3
-rw-r--r--config/initializers/7_omniauth.rb3
-rw-r--r--config/initializers/disable_email_interceptor.rb2
-rw-r--r--config/initializers/gitlab_shell_secret_token.rb19
-rw-r--r--config/initializers/time_zone.rb1
-rw-r--r--config/routes.rb19
-rw-r--r--config/unicorn.rb.example1
-rw-r--r--db/fixtures/development/12_snippets.rb34
-rw-r--r--db/fixtures/production/001_admin.rb14
-rw-r--r--db/migrate/20140907220153_serialize_service_properties.rb2
-rw-r--r--db/migrate/20141006143943_move_slack_service_to_webhook.rb2
-rw-r--r--db/migrate/20141121133009_add_timestamps_to_members.rb15
-rw-r--r--db/migrate/20141121161704_add_identity_table.rb32
-rw-r--r--db/migrate/20141205134006_add_locked_at_to_merge_request.rb5
-rw-r--r--db/schema.rb16
-rw-r--r--doc/README.md14
-rw-r--r--doc/api/README.md12
-rw-r--r--doc/api/branches.md8
-rw-r--r--doc/api/commits.md63
-rw-r--r--doc/api/projects.md3
-rw-r--r--doc/api/repositories.md4
-rw-r--r--doc/api/services.md46
-rw-r--r--doc/api/users.md18
-rw-r--r--doc/development/README.md1
-rw-r--r--doc/development/architecture.md32
-rw-r--r--doc/development/ci_setup.md8
-rw-r--r--doc/development/rake_tasks.md6
-rw-r--r--doc/hooks/custom_hooks.md41
-rw-r--r--doc/install/installation.md42
-rw-r--r--doc/install/requirements.md17
-rw-r--r--doc/integration/gitlab_actions.pngbin0 -> 17321 bytes
-rw-r--r--doc/integration/gitlab_buttons_in_gmail.md28
-rw-r--r--doc/integration/ldap.md104
-rw-r--r--doc/integration/slack.md28
-rw-r--r--doc/markdown/markdown.md4
-rw-r--r--doc/permissions/permissions.md3
-rw-r--r--doc/project_services/bamboo.md60
-rw-r--r--doc/project_services/project_services.md18
-rw-r--r--doc/raketasks/backup_restore.md30
-rw-r--r--doc/raketasks/import.md47
-rw-r--r--doc/raketasks/maintenance.md24
-rw-r--r--doc/release/monthly.md243
-rw-r--r--doc/release/patch.md37
-rw-r--r--doc/release/security.md4
-rw-r--r--doc/sidekiq_debugging.md14
-rw-r--r--doc/update/6.x-or-7.x-to-7.5.md (renamed from doc/update/6.x-or-7.x-to-7.3.md)111
-rw-r--r--doc/update/7.2-to-7.3.md2
-rw-r--r--doc/update/7.3-to-7.4.md124
-rw-r--r--doc/update/7.4-to-7.5.md195
-rw-r--r--doc/update/7.5-to-7.6.md114
-rw-r--r--doc/update/patch_versions.md6
-rw-r--r--doc/update/upgrader.md19
-rw-r--r--doc/web_hooks/web_hooks.md10
-rw-r--r--docker/Dockerfile34
-rw-r--r--docker/README.md68
-rw-r--r--docker/gitlab.rb37
-rw-r--r--docker/troubleshooting.md63
-rw-r--r--features/admin/groups.feature7
-rw-r--r--features/profile/profile.feature19
-rw-r--r--features/project/active_tab.feature2
-rw-r--r--features/project/fork.feature2
-rw-r--r--features/project/service.feature12
-rw-r--r--features/project/source/browse_files.feature20
-rw-r--r--features/snippets/public_snippets.feature10
-rw-r--r--features/snippets/snippets.feature2
-rw-r--r--features/steps/admin/groups.rb23
-rw-r--r--features/steps/dashboard/event_filters.rb2
-rw-r--r--features/steps/dashboard/issues.rb10
-rw-r--r--features/steps/dashboard/merge_requests.rb46
-rw-r--r--features/steps/profile/profile.rb44
-rw-r--r--features/steps/project/active_tab.rb4
-rw-r--r--features/steps/project/fork.rb6
-rw-r--r--features/steps/project/issues/milestones.rb4
-rw-r--r--features/steps/project/project.rb2
-rw-r--r--features/steps/project/services.rb39
-rw-r--r--features/steps/project/source/browse_files.rb10
-rw-r--r--features/steps/shared/paths.rb9
-rw-r--r--features/steps/shared/project.rb4
-rw-r--r--features/steps/shared/snippet.rb9
-rw-r--r--features/steps/snippets/public_snippets.rb25
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/branches.rb5
-rw-r--r--lib/api/commits.rb61
-rw-r--r--lib/api/entities.rb40
-rw-r--r--lib/api/files.rb6
-rw-r--r--lib/api/group_members.rb80
-rw-r--r--lib/api/groups.rb51
-rw-r--r--lib/api/helpers.rb8
-rw-r--r--lib/api/internal.rb19
-rw-r--r--lib/api/projects.rb2
-rw-r--r--lib/api/repositories.rb5
-rw-r--r--lib/api/services.rb38
-rw-r--r--lib/api/users.rb12
-rw-r--r--lib/backup/database.rb13
-rw-r--r--lib/backup/manager.rb32
-rw-r--r--lib/backup/repository.rb59
-rw-r--r--lib/disable_email_interceptor.rb8
-rw-r--r--lib/gitlab/app_logger.rb4
-rw-r--r--lib/gitlab/auth.rb2
-rw-r--r--lib/gitlab/backend/grack_auth.rb4
-rw-r--r--lib/gitlab/backend/shell.rb52
-rw-r--r--lib/gitlab/force_push_check.rb15
-rw-r--r--lib/gitlab/git.rb5
-rw-r--r--lib/gitlab/git_access.rb93
-rw-r--r--lib/gitlab/git_access_status.rb15
-rw-r--r--lib/gitlab/git_access_wiki.rb8
-rw-r--r--lib/gitlab/git_logger.rb4
-rw-r--r--lib/gitlab/git_ref_validator.rb3
-rw-r--r--lib/gitlab/issues_labels.rb1
-rw-r--r--lib/gitlab/ldap/access.rb8
-rw-r--r--lib/gitlab/ldap/adapter.rb6
-rw-r--r--lib/gitlab/ldap/authentication.rb4
-rw-r--r--lib/gitlab/ldap/config.rb27
-rw-r--r--lib/gitlab/ldap/user.rb13
-rw-r--r--lib/gitlab/logger.rb4
-rw-r--r--lib/gitlab/markdown.rb2
-rw-r--r--lib/gitlab/oauth/user.rb63
-rw-r--r--lib/gitlab/production_logger.rb7
-rw-r--r--lib/gitlab/regex.rb3
-rw-r--r--lib/gitlab/sidekiq_logger.rb7
-rw-r--r--lib/gitlab/sidekiq_middleware/memory_killer.rb48
-rw-r--r--lib/gitlab/url_builder.rb2
-rw-r--r--lib/gitlab/utils.rb13
-rw-r--r--lib/support/nginx/gitlab10
-rw-r--r--lib/support/nginx/gitlab-ssl47
-rw-r--r--lib/tasks/gitlab/backup.rake37
-rw-r--r--lib/tasks/gitlab/check.rake50
-rw-r--r--lib/tasks/gitlab/cleanup.rake4
-rw-r--r--lib/tasks/gitlab/db/drop_all_postgres_sequences.rake10
-rw-r--r--lib/tasks/gitlab/import.rake17
-rw-r--r--lib/tasks/gitlab/mail_google_schema_whitelisting.rake73
-rw-r--r--lib/tasks/gitlab/shell.rake14
-rw-r--r--spec/controllers/branches_controller_spec.rb51
-rw-r--r--spec/factories.rb22
-rw-r--r--spec/factories/projects.rb18
-rw-r--r--spec/features/atom/users_spec.rb43
-rw-r--r--spec/features/projects_spec.rb25
-rw-r--r--spec/features/users_spec.rb2
-rw-r--r--spec/finders/issues_finder_spec.rb81
-rw-r--r--spec/finders/snippets_finder_spec.rb7
-rw-r--r--spec/helpers/application_helper_spec.rb10
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb4
-rw-r--r--spec/helpers/oauth_helper_spec.rb20
-rw-r--r--spec/lib/disable_email_interceptor_spec.rb26
-rw-r--r--spec/lib/gitlab/auth_spec.rb10
-rw-r--r--spec/lib/gitlab/git_access_spec.rb50
-rw-r--r--spec/lib/gitlab/git_access_wiki_spec.rb4
-rw-r--r--spec/lib/gitlab/ldap/access_spec.rb2
-rw-r--r--spec/lib/gitlab/ldap/authentication_spec.rb2
-rw-r--r--spec/lib/gitlab/ldap/config_spec.rb16
-rw-r--r--spec/lib/gitlab/ldap/user_spec.rb8
-rw-r--r--spec/lib/gitlab/oauth/user_spec.rb84
-rw-r--r--spec/mailers/notify_spec.rb4
-rw-r--r--spec/models/concerns/mentionable_spec.rb14
-rw-r--r--spec/models/event_spec.rb3
-rw-r--r--spec/models/gitlab_ci_service_spec.rb4
-rw-r--r--spec/models/note_spec.rb6
-rw-r--r--spec/models/project_spec.rb57
-rw-r--r--spec/models/slack_message_spec.rb8
-rw-r--r--spec/models/slack_service_spec.rb36
-rw-r--r--spec/models/user_spec.rb30
-rw-r--r--spec/requests/api/branches_spec.rb1
-rw-r--r--spec/requests/api/commits_spec.rb65
-rw-r--r--spec/requests/api/group_members_spec.rb136
-rw-r--r--spec/requests/api/groups_spec.rb110
-rw-r--r--spec/requests/api/internal_spec.rb54
-rw-r--r--spec/requests/api/projects_spec.rb13
-rw-r--r--spec/requests/api/repositories_spec.rb32
-rw-r--r--spec/requests/api/services_spec.rb26
-rw-r--r--spec/requests/api/session_spec.rb26
-rw-r--r--spec/requests/api/users_spec.rb11
-rw-r--r--spec/routing/project_routing_spec.rb15
-rw-r--r--spec/services/git_push_service_spec.rb2
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb98
-rw-r--r--spec/services/projects/fork_service_spec.rb52
-rw-r--r--spec/services/projects/transfer_service_spec.rb20
-rw-r--r--spec/support/test_env.rb45
-rw-r--r--spec/tasks/gitlab/mail_google_schema_whitelisting.rb27
-rw-r--r--vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js659
417 files changed, 6299 insertions, 2204 deletions
diff --git a/.gitignore b/.gitignore
index 4f778371512..2c6b65b7b7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,4 @@ public/assets/
.envrc
dump.rdb
tags
+.gitlab_shell_secret
diff --git a/CHANGELOG b/CHANGELOG
index 6ddd59df1c4..ab84b36d749 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,73 @@
+v 7.6.0
+ - Fork repository to groups
+ - New rugged version
+ - Add CRON=1 backup setting for quiet backups
+ - Fix failing wiki restore
+ -
+ - Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable)
+ -
+ -
+ - Monokai highlighting style now more faithful to original design (Mark Riedesel)
+ - Create project with repository in synchrony
+ - Added ability to create empty repo or import existing one if project does not have repository
+ -
+ -
+ - Reactivate highlight.js language autodetection
+ - Mobile UI improvements
+ -
+ - Change maximum avatar file size from 100KB to 200KB
+ -
+ -
+ - In the docker directory is a container template based on the Omnibus packages.
+ - Update Sidekiq to version 2.17.8
+ - Add author filter to project issues and merge requests pages
+ - Atom feed for user activity
+
+v 7.5.2
+ - Don't log Sidekiq arguments by default
+
+v 7.5.0
+ - API: Add support for Hipchat (Kevin Houdebert)
+ - Add time zone configuration in gitlab.yml (Sullivan Senechal)
+ - Fix LDAP authentication for Git HTTP access
+ - Run 'GC.start' after every EmailsOnPushWorker job
+ - Fix LDAP config lookup for provider 'ldap'
+ - Drop all sequences during Postgres database restore
+ - Project title links to project homepage (Ben Bodenmiller)
+ - Add Atlassian Bamboo CI service (Drew Blessing)
+ - Mentioned @user will receive email even if he is not participating in issue or commit
+ - Session API: Use case-insensitive authentication like in UI (Andrey Krivko)
+ - Tie up loose ends with annotated tags: API & UI (Sean Edge)
+ - Return valid json for deleting branch via API (sponsored by O'Reilly Media)
+ - Expose username in project events API (sponsored by O'Reilly Media)
+ - Adds comments to commits in the API
+ - Performance improvements
+ - Fix post-receive issue for projects with deleted forks
+ - New gitlab-shell version with custom hooks support
+ - Improve code
+ - GitLab CI 5.2+ support (does not support older versions)
+ - Fixed bug when you can not push commits starting with 000000 to protected branches
+ - Added a password strength indicator
+ - Change project name and path in one form
+ - Display renamed files in diff views (Vinnie Okada)
+ - Fix raw view for public snippets
+ - Use secret token with GitLab internal API.
+ - Add missing timestamps to 'members' table
+
+v 7.4.3
+ - Fix raw snippets view
+ - Fix security issue for member api
+ - Fix buildbox integration
+
+v 7.4.2
+ - Fix internal snippet exposing for unauthenticated users
+
+v 7.4.1
+ - Fix LDAP authentication for Git HTTP access
+ - Fix LDAP config lookup for provider 'ldap'
+ - Fix public snippets
+ - Fix 500 error on projects with nested submodules
+
v 7.4.0
- Refactored membership logic
- Improve error reporting on users API (Julien Bianchi)
@@ -9,6 +79,7 @@ v 7.4.0
- Do not delete tmp/repositories itself during clean-up, only its contents
- Support for backup uploads to remote storage
- Prevent notes polling when there are not notes
+ - Internal ForkService: Prepare support for fork to a given namespace
- API: Add support for forking a project via the API (Bernhard Kaindl)
- API: filter project issues by milestone (Julien Bianchi)
- Fail harder in the backup script
@@ -28,6 +99,13 @@ v 7.4.0
- Improved repository graphs
- Improve event note display in dashboard and project activity views (Vinnie Okada)
- Add users sorting to admin area
+ - UI improvements
+ - Fix ambiguous sha problem with mentioned commit
+ - Fixed bug with apostrophe when at mentioning users
+ - Add active directory ldap option
+ - Developers can push to wiki repo. Protected branches does not affect wiki repo any more
+ - Faster rev list
+ - Fix branch removal
v 7.3.2
- Fix creating new file via web editor
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ce454a11a08..9da89cc2107 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -54,6 +54,8 @@ We welcome merge requests with fixes and improvements to GitLab code, tests, and
Merge requests can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests) or [github.com](https://github.com/gitlabhq/gitlabhq/pulls).
+If you are new to GitLab development (or web development in general), search for the label `easyfix` ([gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=easyfix), [github](https://github.com/gitlabhq/gitlabhq/labels/easyfix)). Those are issues easy to fix, marked by the GitLab core-team. If you are unsure how to proceed but want to help, mention one of the core-team members to give you a hint.
+
### Merge request guidelines
If you can, please submit a merge request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a merge request is as follows:
@@ -78,15 +80,33 @@ The **official merge window** is in the beginning of the month from the 1st to t
Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a minimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it.
-For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the following contribution acceptance criteria.
+For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the contribution acceptance criteria.
+
+## Definition of done
+
+If you contribute to GitLab please know that changes involve more than just code.
+We have the following [definition of done](http://guide.agilealliance.org/guide/definition-of-done.html).
+Please ensure you support the feature you contribute through all of these steps.
+
+1. Description explaning the relevancy (see following item)
+1. Working and clean code that is commented where needed
+1. Unit and integration tests that pass on the CI server
+1. Documented in the /doc directory
+1. Changelog entry added
+1. Reviewed and any concerns are addressed
+1. Merged by the project lead
+1. Added to the release blog article
+1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/) if relevant
+1. Community questions answered
+1. Answers to questions radiated (in docs/wiki/etc.)
-**Please format your merge request description as follows:**
+## Merge request description format
1. What does this MR do?
1. Are there points in the code the reviewer needs to double check?
1. Why was this MR needed?
1. What are the relevant issue numbers / [Feature requests](http://feedback.gitlab.com/)?
-1. Screenshots (If appropriate)
+1. Screenshots (if relevant)
## Contribution acceptance criteria
@@ -101,7 +121,11 @@ For examples of feedback on merge requests please look at already [closed merge
1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options since they complicate future changes
1. Changes after submitting the merge request should be in separate commits (no squashing). You will be asked to squash when the review is over, before merging.
-1. It conforms to the following style guides
+1. It conforms to the following style guides.
+ If your change touches a line that does not follow the style,
+ modify the entire line to follow it. This prevents linting tools from generating warnings.
+ Don't touch neighbouring lines. As an exception, automatic mass refactoring modifications
+ may leave style non-compliant.
## Style guides
diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION
index 38f77a65b30..197c4d5c2d7 100644
--- a/GITLAB_SHELL_VERSION
+++ b/GITLAB_SHELL_VERSION
@@ -1 +1 @@
-2.0.1
+2.4.0
diff --git a/Gemfile b/Gemfile
index c6be76f4ecc..b4ca5969277 100644
--- a/Gemfile
+++ b/Gemfile
@@ -31,13 +31,13 @@ gem 'omniauth-shibboleth'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '7.0.0.rc9'
+gem "gitlab_git", '7.0.0.rc12'
# Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
# LDAP Auth
-gem 'gitlab_omniauth-ldap', '1.1.0', require: "omniauth-ldap"
+gem 'gitlab_omniauth-ldap', '1.2.0', require: "omniauth-ldap"
# Git Wiki
gem 'gollum-lib', '~> 3.0.0'
@@ -112,7 +112,7 @@ gem "acts-as-taggable-on"
# Background jobs
gem 'slim'
gem 'sinatra', require: nil
-gem 'sidekiq', '2.17.0'
+gem 'sidekiq', '2.17.8'
# HTTP requests
gem "httparty"
@@ -134,7 +134,7 @@ gem "redis-rails"
gem 'tinder', '~> 1.9.2'
# HipChat integration
-gem "hipchat", "~> 0.14.0"
+gem "hipchat", "~> 1.4.0"
# Flowdock integration
gem "gitlab-flowdock-git-hook", "~> 0.4.2"
@@ -143,7 +143,7 @@ gem "gitlab-flowdock-git-hook", "~> 0.4.2"
gem "gemnasium-gitlab-service", "~> 0.2"
# Slack integration
-gem "slack-notifier", "~> 0.3.2"
+gem "slack-notifier", "~> 1.0.0"
# d3
gem "d3_rails", "~> 3.1.4"
@@ -186,6 +186,7 @@ gem "gon", '~> 5.0.0'
gem 'nprogress-rails'
gem 'request_store'
gem "virtus"
+gem 'addressable'
group :development do
gem "annotate", "~> 2.6.0.beta2"
diff --git a/Gemfile.lock b/Gemfile.lock
index 0e82f14ca9d..4bcb1eb0de5 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -78,7 +78,7 @@ GEM
coffee-script-source (1.6.3)
colored (1.2)
colorize (0.5.8)
- connection_pool (1.2.0)
+ connection_pool (2.1.0)
coveralls (0.7.0)
multi_json (~> 1.3)
rest-client
@@ -179,17 +179,17 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.0.1.1)
emoji (~> 1.0.1)
- gitlab_git (7.0.0.rc9)
+ gitlab_git (7.0.0.rc12)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
- rugged (~> 0.21.0)
+ rugged (~> 0.21.2)
gitlab_meta (7.0)
- gitlab_omniauth-ldap (1.1.0)
- net-ldap (~> 0.7.0)
+ gitlab_omniauth-ldap (1.2.0)
+ net-ldap (~> 0.9)
omniauth (~> 1.0)
pyu-ruby-sasl (~> 0.0.3.1)
- rubyntlm (~> 0.1.1)
+ rubyntlm (~> 0.3)
gollum-lib (3.0.0)
github-markup (~> 1.1.0)
gitlab-grit (~> 2.6.5)
@@ -235,8 +235,7 @@ GEM
railties (>= 4.0.1)
hashie (2.1.2)
hike (1.2.3)
- hipchat (0.14.0)
- httparty
+ hipchat (1.4.0)
httparty
html-pipeline (1.11.0)
activesupport (>= 2)
@@ -281,7 +280,7 @@ GEM
addressable (~> 2.3)
letter_opener (1.1.2)
launchy (~> 2.2)
- libv8 (3.16.14.3)
+ libv8 (3.16.14.7)
listen (2.3.1)
celluloid (>= 0.15.2)
rb-fsevent (>= 0.9.3)
@@ -299,7 +298,7 @@ GEM
multi_xml (0.5.5)
multipart-post (1.2.0)
mysql2 (0.3.16)
- net-ldap (0.7.0)
+ net-ldap (0.9.0)
net-scp (1.1.2)
net-ssh (>= 2.6.5)
net-ssh (2.8.0)
@@ -403,7 +402,7 @@ GEM
rdoc (3.12.2)
json (~> 1.4)
redcarpet (3.1.2)
- redis (3.0.6)
+ redis (3.1.0)
redis-actionpack (4.0.0)
actionpack (~> 4)
redis-rack (~> 1.5.0)
@@ -411,8 +410,8 @@ GEM
redis-activesupport (4.0.0)
activesupport (~> 4)
redis-store (~> 1.1.0)
- redis-namespace (1.4.1)
- redis (~> 3.0.4)
+ redis-namespace (1.5.1)
+ redis (~> 3.0, >= 3.0.4)
redis-rack (1.5.0)
rack (~> 1.5)
redis-store (~> 1.1.0)
@@ -445,9 +444,9 @@ GEM
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
ruby-progressbar (1.2.0)
- rubyntlm (0.1.1)
+ rubyntlm (0.4.0)
rubypants (0.2.0)
- rugged (0.21.0)
+ rugged (0.21.2)
safe_yaml (0.9.7)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
@@ -471,12 +470,12 @@ GEM
sexp_processor (4.4.0)
shoulda-matchers (2.1.0)
activesupport (>= 3.0.0)
- sidekiq (2.17.0)
- celluloid (>= 0.15.2)
- connection_pool (>= 1.0.0)
+ sidekiq (2.17.8)
+ celluloid (= 0.15.2)
+ connection_pool (~> 2.0)
json
- redis (>= 3.0.4)
- redis-namespace (>= 1.3.1)
+ redis (~> 3.1)
+ redis-namespace (~> 1.3)
simple_oauth (0.1.9)
simplecov (0.9.0)
docile (~> 1.1.0)
@@ -488,7 +487,7 @@ GEM
rack-protection (~> 1.4)
tilt (~> 1.3, >= 1.3.4)
six (0.2.0)
- slack-notifier (0.3.2)
+ slack-notifier (1.0.0)
slim (2.0.2)
temple (~> 0.6.6)
tilt (>= 1.3.3, < 2.1)
@@ -592,6 +591,7 @@ DEPENDENCIES
RedCloth
ace-rails-ap
acts-as-taggable-on
+ addressable
annotate (~> 2.6.0.beta2)
asciidoctor (= 0.1.4)
awesome_print
@@ -624,9 +624,9 @@ DEPENDENCIES
gitlab-grack (~> 2.0.0.pre)
gitlab-linguist (~> 3.0.0)
gitlab_emoji (~> 0.0.1.1)
- gitlab_git (= 7.0.0.rc9)
+ gitlab_git (= 7.0.0.rc12)
gitlab_meta (= 7.0)
- gitlab_omniauth-ldap (= 1.1.0)
+ gitlab_omniauth-ldap (= 1.2.0)
gollum-lib (~> 3.0.0)
gon (~> 5.0.0)
grape (~> 0.6.1)
@@ -635,7 +635,7 @@ DEPENDENCIES
guard-rspec
guard-spinach
haml-rails
- hipchat (~> 0.14.0)
+ hipchat (~> 1.4.0)
html-pipeline-gitlab (~> 0.1.0)
httparty
jasmine (= 2.0.2)
@@ -684,11 +684,11 @@ DEPENDENCIES
semantic-ui-sass (~> 0.16.1.0)
settingslogic
shoulda-matchers (~> 2.1.0)
- sidekiq (= 2.17.0)
+ sidekiq (= 2.17.8)
simplecov
sinatra
six
- slack-notifier (~> 0.3.2)
+ slack-notifier (~> 1.0.0)
slim
spinach-rails
spring (= 1.1.3)
diff --git a/README.md b/README.md
index 2c0643cf598..f303e8e7383 100644
--- a/README.md
+++ b/README.md
@@ -55,14 +55,8 @@ Since a manual installation is a lot of work and error prone we strongly recomme
## Third-party applications
-Access GitLab from multiple platforms with applications below.
-These applications are maintained by contributors, GitLab B.V. does not offer support for them.
-
-- [iPhone app](http://gitlabcontrol.com/)
-- [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en)
-- [Chrome app](https://chrome.google.com/webstore/detail/chrome-gitlab-notifier/eageapgbnjicdjjihgclpclilenjbobi)
-- [Command line client](https://github.com/drewblessing/gitlab-cli)
-- [Ruby API wrapper](https://github.com/NARKOZ/gitlab)
+There are a lot of applications and API wrappers for GitLab.
+Find them [on our website](https://about.gitlab.com/applications/).
### New versions
@@ -137,4 +131,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on
## Is it awesome?
Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua.
-[These people](https://twitter.com/gitlabhq/favorites) seem to like it.
+[These people](https://twitter.com/gitlab/favorites) seem to like it.
diff --git a/VERSION b/VERSION
index 8b258729874..a28398aef42 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-7.4.0-pre
+7.6.0.pre
diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee
index fdefbfb92bd..4f76d8ce486 100644
--- a/app/assets/javascripts/activities.js.coffee
+++ b/app/assets/javascripts/activities.js.coffee
@@ -1,4 +1,4 @@
-class Activities
+class @Activities
constructor: ->
Pager.init 20, true
$(".event_filter_link").bind "click", (event) =>
@@ -27,5 +27,3 @@ class Activities
event_filters.splice index, 1
$.cookie "event_filter", event_filters.join(","), { path: '/' }
-
-@Activities = Activities
diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee
index a333eed87f2..bcb2e6df7c0 100644
--- a/app/assets/javascripts/admin.js.coffee
+++ b/app/assets/javascripts/admin.js.coffee
@@ -1,4 +1,4 @@
-class Admin
+class @Admin
constructor: ->
$('input#user_force_random_password').on 'change', (elem) ->
elems = $('#user_password, #user_password_confirmation')
@@ -51,5 +51,3 @@ class Admin
$('li.group_member').bind 'ajax:success', ->
Turbolinks.visit(location.href)
-
-@Admin = Admin
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index ff0d0bb32b9..e9a28c12159 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -18,6 +18,7 @@
#= require jquery.turbolinks
#= require turbolinks
#= require bootstrap
+#= require password_strength
#= require select2
#= require raphael
#= require g.raphael-min
@@ -63,7 +64,7 @@ window.extractLast = (term) ->
return split( term ).pop()
window.rstrip = (val) ->
- return val.replace(/\s+$/, '')
+ return if val then val.replace(/\s+$/, '') else val
# Disable button if text field is empty
window.disableButtonIfEmptyField = (field_selector, button_selector) ->
diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob.js.coffee
index 9db919e5a62..a5f15f80c5c 100644
--- a/app/assets/javascripts/blob.js.coffee
+++ b/app/assets/javascripts/blob.js.coffee
@@ -1,4 +1,4 @@
-class BlobView
+class @BlobView
constructor: ->
# handle multi-line select
handleMultiSelect = (e) ->
@@ -71,6 +71,3 @@ class BlobView
# Highlight the correct lines when the hash part of the URL changes
$(window).on("hashchange", highlightBlobLines)
-
-
-@BlobView = BlobView
diff --git a/app/assets/javascripts/commit.js.coffee b/app/assets/javascripts/commit.js.coffee
index 5f53439ca4b..0566e239191 100644
--- a/app/assets/javascripts/commit.js.coffee
+++ b/app/assets/javascripts/commit.js.coffee
@@ -1,6 +1,4 @@
-class Commit
+class @Commit
constructor: ->
$('.files .diff-file').each ->
new CommitFile(this)
-
-@Commit = Commit
diff --git a/app/assets/javascripts/commit/file.js.coffee b/app/assets/javascripts/commit/file.js.coffee
index 4db9116a9de..83e793863b6 100644
--- a/app/assets/javascripts/commit/file.js.coffee
+++ b/app/assets/javascripts/commit/file.js.coffee
@@ -1,7 +1,5 @@
-class CommitFile
+class @CommitFile
constructor: (file) ->
if $('.image', file).length
new ImageFile(file)
-
-@CommitFile = CommitFile
diff --git a/app/assets/javascripts/commit/image-file.js.coffee b/app/assets/javascripts/commit/image-file.js.coffee
index 607b85eb45c..9e5f49b1f69 100644
--- a/app/assets/javascripts/commit/image-file.js.coffee
+++ b/app/assets/javascripts/commit/image-file.js.coffee
@@ -1,4 +1,4 @@
-class ImageFile
+class @ImageFile
# Width where images must fits in, for 2-up this gets divided by 2
@availWidth = 900
@@ -124,5 +124,3 @@ class ImageFile
else
img.on 'load', =>
callback.call(this, domImg.naturalWidth, domImg.naturalHeight)
-
-@ImageFile = ImageFile
diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee
index 784d7d20bb1..c183e78e513 100644
--- a/app/assets/javascripts/commits.js.coffee
+++ b/app/assets/javascripts/commits.js.coffee
@@ -1,4 +1,4 @@
-class CommitsList
+class @CommitsList
@data =
ref: null
limit: 0
@@ -53,5 +53,3 @@ class CommitsList
@disable
callback: =>
this.getOld()
-
-this.CommitsList = CommitsList
diff --git a/app/assets/javascripts/confirm_danger_modal.js.coffee b/app/assets/javascripts/confirm_danger_modal.js.coffee
index 1687b7d961c..bb99edbd09e 100644
--- a/app/assets/javascripts/confirm_danger_modal.js.coffee
+++ b/app/assets/javascripts/confirm_danger_modal.js.coffee
@@ -1,4 +1,4 @@
-class ConfirmDangerModal
+class @ConfirmDangerModal
constructor: (form, text) ->
@form = form
$('.js-confirm-text').text(text || '')
@@ -16,5 +16,3 @@ class ConfirmDangerModal
$('.js-confirm-danger-submit').on 'click', =>
@form.submit()
-
-@ConfirmDangerModal = ConfirmDangerModal
diff --git a/app/assets/javascripts/dashboard.js.coffee b/app/assets/javascripts/dashboard.js.coffee
index c4a0ccd9c2a..6ef5a539b8f 100644
--- a/app/assets/javascripts/dashboard.js.coffee
+++ b/app/assets/javascripts/dashboard.js.coffee
@@ -1,4 +1,4 @@
-class Dashboard
+class @Dashboard
constructor: ->
@initSidebarTab()
@@ -28,6 +28,3 @@ class Dashboard
# show tab from cookie
sidebar_filter = $.cookie(key)
$("#" + sidebar_filter).tab('show') if sidebar_filter
-
-
-@Dashboard = Dashboard
diff --git a/app/assets/javascripts/diff.js.coffee b/app/assets/javascripts/diff.js.coffee
index dbe00c487dc..52b4208524f 100644
--- a/app/assets/javascripts/diff.js.coffee
+++ b/app/assets/javascripts/diff.js.coffee
@@ -1,4 +1,4 @@
-class Diff
+class @Diff
UNFOLD_COUNT = 20
constructor: ->
$(document).on('click', '.js-unfold', (event) =>
@@ -41,6 +41,3 @@ class Diff
lines = line.children().slice(0, 2)
line_numbers = ($(l).attr('data-linenumber') for l in lines)
(parseInt(line_number) for line_number in line_numbers)
-
-
-@Diff = Diff
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index 00b52758fa8..e8b71a71945 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -58,15 +58,11 @@ class Dispatcher
when 'groups:show', 'projects:show'
new Activities()
shortcut_handler = new ShortcutsNavigation()
- when 'projects:new'
- new Project()
- when 'projects:edit'
- new Project()
- shortcut_handler = new ShortcutsNavigation()
- when 'projects:teams:members:index'
- new TeamMembers()
when 'groups:members'
new GroupMembers()
+ new UsersSelect()
+ when 'groups:new', 'groups:edit', 'admin:groups:edit'
+ new GroupAvatar()
when 'projects:tree:show'
new TreeView()
shortcut_handler = new ShortcutsNavigation()
@@ -79,13 +75,35 @@ class Dispatcher
# Ensure we don't create a particular shortcut handler here. This is
# already created, where the network graph is created.
shortcut_handler = true
+ when 'projects:forks:new'
+ new ProjectFork()
+ when 'users:show'
+ new User()
switch path.first()
- when 'admin' then new Admin()
+ when 'admin'
+ new Admin()
+ switch path[1]
+ when 'groups'
+ new UsersSelect()
+ when 'projects'
+ new NamespaceSelect()
when 'dashboard'
shortcut_handler = new ShortcutsDashboardNavigation()
+ when 'profiles'
+ new Profile()
when 'projects'
+ new Project()
switch path[1]
+ when 'edit'
+ shortcut_handler = new ShortcutsNavigation()
+ new ProjectNew()
+ when 'new'
+ new ProjectNew()
+ when 'show'
+ new ProjectShow()
+ when 'issues', 'merge_requests'
+ new ProjectUsersSelect()
when 'wikis'
new Wikis()
shortcut_handler = new ShortcutsNavigation()
@@ -94,6 +112,7 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
when 'team_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation()
+ new UsersSelect()
# If we haven't installed a custom shortcut handler, install the default one
diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee
index cf1a37eae3e..b39ab0c4475 100644
--- a/app/assets/javascripts/flash.js.coffee
+++ b/app/assets/javascripts/flash.js.coffee
@@ -1,4 +1,4 @@
-class Flash
+class @Flash
constructor: (message, type)->
flash = $(".flash-container")
flash.html("")
@@ -10,5 +10,3 @@ class Flash
flash.click -> $(@).fadeOut()
flash.show()
-
-@Flash = Flash
diff --git a/app/assets/javascripts/group_avatar.js.coffee b/app/assets/javascripts/group_avatar.js.coffee
new file mode 100644
index 00000000000..0825fd3ce52
--- /dev/null
+++ b/app/assets/javascripts/group_avatar.js.coffee
@@ -0,0 +1,9 @@
+class @GroupAvatar
+ constructor: ->
+ $('.js-choose-group-avatar-button').bind "click", ->
+ form = $(this).closest("form")
+ form.find(".js-group-avatar-input").click()
+ $('.js-group-avatar-input').bind "change", ->
+ form = $(this).closest("form")
+ filename = $(this).val().replace(/^.*[\\\/]/, '')
+ form.find(".js-avatar-filename").text(filename)
diff --git a/app/assets/javascripts/groups.js.coffee b/app/assets/javascripts/groups.js.coffee
index 4b1000f9a6a..cc905e91ea2 100644
--- a/app/assets/javascripts/groups.js.coffee
+++ b/app/assets/javascripts/groups.js.coffee
@@ -1,17 +1,4 @@
-class GroupMembers
+class @GroupMembers
constructor: ->
$('li.group_member').bind 'ajax:success', ->
$(this).fadeOut()
-
-@GroupMembers = GroupMembers
-
-$ ->
- # avatar
- $('.js-choose-group-avatar-button').bind "click", ->
- form = $(this).closest("form")
- form.find(".js-group-avatar-input").click()
-
- $('.js-group-avatar-input').bind "change", ->
- form = $(this).closest("form")
- filename = $(this).val().replace(/^.*[\\\/]/, '')
- form.find(".js-avatar-filename").text(filename)
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index 0e2a2fa792a..597b4695a6d 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -1,4 +1,4 @@
-class Issue
+class @Issue
constructor: ->
$('.edit-issue.inline-update input[type="submit"]').hide()
$(".issue-box .inline-update").on "change", "select", ->
@@ -15,5 +15,3 @@ class Issue
"issue"
updateTaskState
)
-
-@Issue = Issue
diff --git a/app/assets/javascripts/labels.js.coffee b/app/assets/javascripts/labels.js.coffee
index d306ad64f5b..1bc8840f9ac 100644
--- a/app/assets/javascripts/labels.js.coffee
+++ b/app/assets/javascripts/labels.js.coffee
@@ -1,4 +1,4 @@
-class Labels
+class @Labels
constructor: ->
form = $('.label-form')
@setupLabelForm(form)
@@ -31,5 +31,3 @@ class Labels
# Notify the form, that color has changed
$('.label-form').trigger('keyup')
e.preventDefault()
-
-@Labels = Labels
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
index 9f99ff403f8..46e06424e5a 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -1,4 +1,4 @@
-class MergeRequest
+class @MergeRequest
constructor: (@opts) ->
@initContextWidget()
this.$el = $('.merge-request')
@@ -132,5 +132,3 @@ class MergeRequest
this.$('.automerge_widget').hide()
this.$('.merge-in-progress').hide()
this.$('.automerge_widget.already_cannot_be_merged').show()
-
-this.MergeRequest = MergeRequest
diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee
index ea01c318d4f..c42f31933d3 100644
--- a/app/assets/javascripts/milestone.js.coffee
+++ b/app/assets/javascripts/milestone.js.coffee
@@ -1,4 +1,4 @@
-class Milestone
+class @Milestone
@updateIssue: (li, issue_url, data) ->
$.ajax
type: "PUT"
@@ -115,5 +115,3 @@ class Milestone
Milestone.updateMergeRequest(ui.item, merge_request_url, data)
).disableSelection()
-
-@Milestone = Milestone
diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee
index 00d135d1449..a02c4515ccc 100644
--- a/app/assets/javascripts/namespace_select.js.coffee
+++ b/app/assets/javascripts/namespace_select.js.coffee
@@ -1,24 +1,25 @@
-$ ->
- namespaceFormatResult = (namespace) ->
- markup = "<div class='namespace-result'>"
- markup += "<span class='namespace-kind'>" + namespace.kind + "</span>"
- markup += "<span class='namespace-path'>" + namespace.path + "</span>"
- markup += "</div>"
- markup
+class @NamespaceSelect
+ constructor: ->
+ namespaceFormatResult = (namespace) ->
+ markup = "<div class='namespace-result'>"
+ markup += "<span class='namespace-kind'>" + namespace.kind + "</span>"
+ markup += "<span class='namespace-path'>" + namespace.path + "</span>"
+ markup += "</div>"
+ markup
- formatSelection = (namespace) ->
- namespace.kind + ": " + namespace.path
+ formatSelection = (namespace) ->
+ namespace.kind + ": " + namespace.path
- $('.ajax-namespace-select').each (i, select) ->
- $(select).select2
- placeholder: "Search for namespace"
- multiple: $(select).hasClass('multiselect')
- minimumInputLength: 0
- query: (query) ->
- Api.namespaces query.term, (namespaces) ->
- data = { results: namespaces }
- query.callback(data)
+ $('.ajax-namespace-select').each (i, select) ->
+ $(select).select2
+ placeholder: "Search for namespace"
+ multiple: $(select).hasClass('multiselect')
+ minimumInputLength: 0
+ query: (query) ->
+ Api.namespaces query.term, (namespaces) ->
+ data = { results: namespaces }
+ query.callback(data)
- dropdownCssClass: "ajax-namespace-dropdown"
- formatResult: namespaceFormatResult
- formatSelection: formatSelection
+ dropdownCssClass: "ajax-namespace-dropdown"
+ formatResult: namespaceFormatResult
+ formatSelection: formatSelection
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index b6bb0c42ad4..30f8530dfda 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -1,4 +1,4 @@
-class Notes
+class @Notes
@interval: null
constructor: (notes_url, note_ids, last_fetched_at) ->
@@ -465,7 +465,3 @@ class Notes
else
form.find('.js-note-target-reopen').text('Reopen')
form.find('.js-note-target-close').text('Close')
-
-
-
-@Notes = Notes
diff --git a/app/assets/javascripts/notes_votes.js.coffee b/app/assets/javascripts/notes_votes.js.coffee
index b31eb9ac9de..65c149b7886 100644
--- a/app/assets/javascripts/notes_votes.js.coffee
+++ b/app/assets/javascripts/notes_votes.js.coffee
@@ -1,4 +1,4 @@
-class NotesVotes
+class @NotesVotes
updateVotes: ->
votes = $("#votes .votes")
notes = $("#notes-list .note .vote")
@@ -18,5 +18,3 @@ class NotesVotes
# replace vote numbers
votes.find(".upvotes").text votes.find(".upvotes").text().replace(/\d+/, upvotes)
votes.find(".downvotes").text votes.find(".downvotes").text().replace(/\d+/, downvotes)
-
-@NotesVotes = NotesVotes
diff --git a/app/assets/javascripts/password_strength.js.coffee b/app/assets/javascripts/password_strength.js.coffee
new file mode 100644
index 00000000000..825f5630266
--- /dev/null
+++ b/app/assets/javascripts/password_strength.js.coffee
@@ -0,0 +1,31 @@
+#= require pwstrength-bootstrap-1.2.2
+overwritten_messages =
+ wordSimilarToUsername: "Your password should not contain your username"
+
+overwritten_rules =
+ wordSequences: false
+
+options =
+ showProgressBar: false
+ showVerdicts: false
+ showPopover: true
+ showErrors: true
+ showStatus: true
+ errorMessages: overwritten_messages
+
+$(document).ready ->
+ profileOptions = {}
+ profileOptions.ui = options
+ profileOptions.rules =
+ activated: overwritten_rules
+
+ deviseOptions = {}
+ deviseOptions.common =
+ usernameField: "#user_username"
+ deviseOptions.ui = options
+ deviseOptions.rules =
+ activated: overwritten_rules
+
+ $("#user_password_profile").pwstrength profileOptions
+ $("#user_password_sign_up").pwstrength deviseOptions
+ $("#user_password_recover").pwstrength deviseOptions
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
index 0e99921f899..de356fbec77 100644
--- a/app/assets/javascripts/profile.js.coffee
+++ b/app/assets/javascripts/profile.js.coffee
@@ -1,30 +1,29 @@
-$ ->
- $('.edit_user .application-theme input, .edit_user .code-preview-theme input').click ->
- # Submit the form
- $('.edit_user').submit()
+class @Profile
+ constructor: ->
+ $('.edit_user .application-theme input, .edit_user .code-preview-theme input').click ->
+ # Submit the form
+ $('.edit_user').submit()
- new Flash("Appearance settings saved", "notice")
+ new Flash("Appearance settings saved", "notice")
- $('.update-username form').on 'ajax:before', ->
- $('.loading-gif').show()
- $(this).find('.update-success').hide()
- $(this).find('.update-failed').hide()
+ $('.update-username form').on 'ajax:before', ->
+ $('.loading-gif').show()
+ $(this).find('.update-success').hide()
+ $(this).find('.update-failed').hide()
- $('.update-username form').on 'ajax:complete', ->
- $(this).find('.btn-save').enableButton()
- $(this).find('.loading-gif').hide()
+ $('.update-username form').on 'ajax:complete', ->
+ $(this).find('.btn-save').enableButton()
+ $(this).find('.loading-gif').hide()
- $('.update-notifications').on 'ajax:complete', ->
- $(this).find('.btn-save').enableButton()
+ $('.update-notifications').on 'ajax:complete', ->
+ $(this).find('.btn-save').enableButton()
- $('.js-choose-user-avatar-button').bind "click", ->
- form = $(this).closest("form")
- form.find(".js-user-avatar-input").click()
+ $('.js-choose-user-avatar-button').bind "click", ->
+ form = $(this).closest("form")
+ form.find(".js-user-avatar-input").click()
- $('.js-user-avatar-input').bind "change", ->
- form = $(this).closest("form")
- filename = $(this).val().replace(/^.*[\\\/]/, '')
- form.find(".js-avatar-filename").text(filename)
-
- $('.profile-groups-avatars').tooltip("placement": "top")
+ $('.js-user-avatar-input').bind "change", ->
+ form = $(this).closest("form")
+ filename = $(this).val().replace(/^.*[\\\/]/, '')
+ form.find(".js-avatar-filename").text(filename)
diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee
index f4a8a178e76..5a9cc66c8f0 100644
--- a/app/assets/javascripts/project.js.coffee
+++ b/app/assets/javascripts/project.js.coffee
@@ -1,62 +1,20 @@
-class Project
+class @Project
constructor: ->
- $('.project-edit-container').on 'ajax:before', =>
- $('.project-edit-container').hide()
- $('.save-project-loader').show()
-
- @initEvents()
-
-
- initEvents: ->
- disableButtonIfEmptyField '#project_name', '.project-submit'
-
- $('#project_issues_enabled').change ->
- if ($(this).is(':checked') == true)
- $('#project_issues_tracker').removeAttr('disabled')
- else
- $('#project_issues_tracker').attr('disabled', 'disabled')
-
- $('#project_issues_tracker').change()
-
- $('#project_issues_tracker').change ->
- if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled'))
- $('#project_issues_tracker_id').attr('disabled', 'disabled')
- else
- $('#project_issues_tracker_id').removeAttr('disabled')
-
-
-@Project = Project
-
-$ ->
- # Git clone panel switcher
- scope = $ '.git-clone-holder'
- if scope.length > 0
- $('a, button', scope).click ->
- $('a, button', scope).removeClass 'active'
- $(@).addClass 'active'
- $('#project_clone', scope).val $(@).data 'clone'
- $(".clone").text("").append $(@).data 'clone'
-
- # Ref switcher
- $('.project-refs-select').on 'change', ->
- $(@).parents('form').submit()
-
- $('.hide-no-ssh-message').on 'click', (e) ->
- path = '/'
- $.cookie('hide_no_ssh_message', 'false', { path: path })
- $(@).parents('.no-ssh-key-message').hide()
- e.preventDefault()
-
- $('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) ->
- $(@).toggleClass('on').find('.count').html(data.star_count)
- .on 'ajax:error', (e, xhr, status, error) ->
- new Flash('Star toggle failed. Try again later.', 'alert')
-
- $("a[data-toggle='tab']").on "shown.bs.tab", (e) ->
- $.cookie "default_view", $(e.target).attr("href")
-
- defaultView = $.cookie("default_view")
- if defaultView
- $("a[href=" + defaultView + "]").tab "show"
- else
- $("a[data-toggle='tab']:first").tab "show"
+ # Git clone panel switcher
+ scope = $ '.git-clone-holder'
+ if scope.length > 0
+ $('a, button', scope).click ->
+ $('a, button', scope).removeClass 'active'
+ $(@).addClass 'active'
+ $('#project_clone', scope).val $(@).data 'clone'
+ $(".clone").text("").append $(@).data 'clone'
+
+ # Ref switcher
+ $('.project-refs-select').on 'change', ->
+ $(@).parents('form').submit()
+
+ $('.hide-no-ssh-message').on 'click', (e) ->
+ path = '/'
+ $.cookie('hide_no_ssh_message', 'false', { path: path })
+ $(@).parents('.no-ssh-key-message').hide()
+ e.preventDefault()
diff --git a/app/assets/javascripts/project_fork.js.coffee b/app/assets/javascripts/project_fork.js.coffee
new file mode 100644
index 00000000000..e15a1c4ef76
--- /dev/null
+++ b/app/assets/javascripts/project_fork.js.coffee
@@ -0,0 +1,5 @@
+class @ProjectFork
+ constructor: ->
+ $('.fork-thumbnail a').on 'click', ->
+ $('.fork-namespaces').hide()
+ $('.save-project-loader').show()
diff --git a/app/assets/javascripts/project_import.js.coffee b/app/assets/javascripts/project_import.js.coffee
index 7cf44da99fe..6633564a079 100644
--- a/app/assets/javascripts/project_import.js.coffee
+++ b/app/assets/javascripts/project_import.js.coffee
@@ -1,7 +1,5 @@
-class ProjectImport
+class @ProjectImport
constructor: ->
setTimeout ->
Turbolinks.visit(location.href)
, 5000
-
-@ProjectImport = ProjectImport
diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee
new file mode 100644
index 00000000000..f4a2ca813d2
--- /dev/null
+++ b/app/assets/javascripts/project_new.js.coffee
@@ -0,0 +1,25 @@
+class @ProjectNew
+ constructor: ->
+ $('.project-edit-container').on 'ajax:before', =>
+ $('.project-edit-container').hide()
+ $('.save-project-loader').show()
+
+ @initEvents()
+
+
+ initEvents: ->
+ disableButtonIfEmptyField '#project_name', '.project-submit'
+
+ $('#project_issues_enabled').change ->
+ if ($(this).is(':checked') == true)
+ $('#project_issues_tracker').removeAttr('disabled')
+ else
+ $('#project_issues_tracker').attr('disabled', 'disabled')
+
+ $('#project_issues_tracker').change()
+
+ $('#project_issues_tracker').change ->
+ if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled'))
+ $('#project_issues_tracker_id').attr('disabled', 'disabled')
+ else
+ $('#project_issues_tracker_id').removeAttr('disabled')
diff --git a/app/assets/javascripts/project_show.js.coffee b/app/assets/javascripts/project_show.js.coffee
new file mode 100644
index 00000000000..02a7d7b731d
--- /dev/null
+++ b/app/assets/javascripts/project_show.js.coffee
@@ -0,0 +1,15 @@
+class @ProjectShow
+ constructor: ->
+ $('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) ->
+ $(@).toggleClass('on').find('.count').html(data.star_count)
+ .on 'ajax:error', (e, xhr, status, error) ->
+ new Flash('Star toggle failed. Try again later.', 'alert')
+
+ $("a[data-toggle='tab']").on "shown.bs.tab", (e) ->
+ $.cookie "default_view", $(e.target).attr("href")
+
+ defaultView = $.cookie("default_view")
+ if defaultView
+ $("a[href=" + defaultView + "]").tab "show"
+ else
+ $("a[data-toggle='tab']:first").tab "show"
diff --git a/app/assets/javascripts/project_users_select.js.coffee b/app/assets/javascripts/project_users_select.js.coffee
index cfbcd5108c8..7fb33926096 100644
--- a/app/assets/javascripts/project_users_select.js.coffee
+++ b/app/assets/javascripts/project_users_select.js.coffee
@@ -1,6 +1,6 @@
-@projectUsersSelect =
- init: ->
- $('.ajax-project-users-select').each (i, select) ->
+class @ProjectUsersSelect
+ constructor: ->
+ $('.ajax-project-users-select').each (i, select) =>
project_id = $(select).data('project-id') || $('body').data('project-id')
$(select).select2
@@ -28,14 +28,16 @@
Api.user(id, callback)
- formatResult: projectUsersSelect.projectUserFormatResult
- formatSelection: projectUsersSelect.projectUserFormatSelection
+ formatResult: (args...) =>
+ @formatResult(args...)
+ formatSelection: (args...) =>
+ @formatSelection(args...)
dropdownCssClass: "ajax-project-users-dropdown"
dropdownAutoWidth: true
escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
m
- projectUserFormatResult: (user) ->
+ formatResult: (user) ->
if user.avatar_url
avatar = user.avatar_url
else
@@ -52,8 +54,5 @@
<div class='user-username'>#{user.username}</div>
</div>"
- projectUserFormatSelection: (user) ->
+ formatSelection: (user) ->
user.name
-
-$ ->
- projectUsersSelect.init()
diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee
index e144dfa1d68..c1801365266 100644
--- a/app/assets/javascripts/search_autocomplete.js.coffee
+++ b/app/assets/javascripts/search_autocomplete.js.coffee
@@ -1,4 +1,4 @@
-class SearchAutocomplete
+class @SearchAutocomplete
constructor: (search_autocomplete_path, project_id, project_ref) ->
project_id = '' unless project_id
project_ref = '' unless project_ref
@@ -9,5 +9,3 @@ class SearchAutocomplete
minLength: 1
select: (event, ui) ->
location.href = ui.item.url
-
-@SearchAutocomplete = SearchAutocomplete
diff --git a/app/assets/javascripts/stat_graph.js.coffee b/app/assets/javascripts/stat_graph.js.coffee
index b129619696f..f36c71fd25e 100644
--- a/app/assets/javascripts/stat_graph.js.coffee
+++ b/app/assets/javascripts/stat_graph.js.coffee
@@ -1,4 +1,4 @@
-class window.StatGraph
+class @StatGraph
@log: {}
@get_log: ->
@log
diff --git a/app/assets/javascripts/stat_graph_contributors.js.coffee b/app/assets/javascripts/stat_graph_contributors.js.coffee
index ab785a54543..27f0fd31d50 100644
--- a/app/assets/javascripts/stat_graph_contributors.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors.js.coffee
@@ -1,4 +1,4 @@
-class window.ContributorsStatGraph
+class @ContributorsStatGraph
init: (log) ->
@parsed_log = ContributorsStatGraphUtil.parse_log(log)
@set_current_field("commits")
diff --git a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
index 834c7e5dab0..9952fa0b00a 100644
--- a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee
@@ -1,4 +1,4 @@
-class window.ContributorsGraph
+class @ContributorsGraph
MARGIN:
top: 20
right: 20
@@ -44,7 +44,7 @@ class window.ContributorsGraph
set_data: (data) ->
@data = data
-class window.ContributorsMasterGraph extends ContributorsGraph
+class @ContributorsMasterGraph extends ContributorsGraph
constructor: (@data) ->
@width = $('.container').width() - 70
@height = 200
@@ -117,7 +117,7 @@ class window.ContributorsMasterGraph extends ContributorsGraph
@svg.select("path").attr("d", @area)
@svg.select(".y.axis").call(@y_axis)
-class window.ContributorsAuthorGraph extends ContributorsGraph
+class @ContributorsAuthorGraph extends ContributorsGraph
constructor: (@data) ->
@width = $('.container').width()/2 - 100
@height = 200
diff --git a/app/assets/javascripts/team_members.js.coffee b/app/assets/javascripts/team_members.js.coffee
deleted file mode 100644
index 5eaa8ad4ff9..00000000000
--- a/app/assets/javascripts/team_members.js.coffee
+++ /dev/null
@@ -1,6 +0,0 @@
-class TeamMembers
- constructor: ->
- $('.team-members .project-access-select').on "change", ->
- $(this.form).submit()
-
-@TeamMembers = TeamMembers
diff --git a/app/assets/javascripts/tree.js.coffee b/app/assets/javascripts/tree.js.coffee
index 4852e879b68..d428db5b422 100644
--- a/app/assets/javascripts/tree.js.coffee
+++ b/app/assets/javascripts/tree.js.coffee
@@ -1,4 +1,4 @@
-class TreeView
+class @TreeView
constructor: ->
@initKeyNav()
@@ -39,5 +39,3 @@ class TreeView
else if e.which is 13
path = $('.tree-item.selected .tree-item-file-name a').attr('href')
Turbolinks.visit(path)
-
-@TreeView = TreeView
diff --git a/app/assets/javascripts/user.js.coffee b/app/assets/javascripts/user.js.coffee
new file mode 100644
index 00000000000..8a2e2421c2e
--- /dev/null
+++ b/app/assets/javascripts/user.js.coffee
@@ -0,0 +1,3 @@
+class @User
+ constructor: ->
+ $('.profile-groups-avatars').tooltip("placement": "top")
diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee
index 86318bd7d94..9eee7406511 100644
--- a/app/assets/javascripts/users_select.js.coffee
+++ b/app/assets/javascripts/users_select.js.coffee
@@ -1,5 +1,30 @@
-$ ->
- userFormatResult = (user) ->
+class @UsersSelect
+ constructor: ->
+ $('.ajax-users-select').each (i, select) =>
+ $(select).select2
+ placeholder: "Search for a user"
+ multiple: $(select).hasClass('multiselect')
+ minimumInputLength: 0
+ query: (query) ->
+ Api.users query.term, (users) ->
+ data = { results: users }
+ query.callback(data)
+
+ initSelection: (element, callback) ->
+ id = $(element).val()
+ if id isnt ""
+ Api.user(id, callback)
+
+
+ formatResult: (args...) =>
+ @formatResult(args...)
+ formatSelection: (args...) =>
+ @formatSelection(args...)
+ dropdownCssClass: "ajax-users-dropdown"
+ escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
+ m
+
+ formatResult: (user) ->
if user.avatar_url
avatar = user.avatar_url
else
@@ -11,27 +36,5 @@ $ ->
<div class='user-username'>#{user.username}</div>
</div>"
- userFormatSelection = (user) ->
+ formatSelection: (user) ->
user.name
-
- $('.ajax-users-select').each (i, select) ->
- $(select).select2
- placeholder: "Search for a user"
- multiple: $(select).hasClass('multiselect')
- minimumInputLength: 0
- query: (query) ->
- Api.users query.term, (users) ->
- data = { results: users }
- query.callback(data)
-
- initSelection: (element, callback) ->
- id = $(element).val()
- if id isnt ""
- Api.user(id, callback)
-
-
- formatResult: userFormatResult
- formatSelection: userFormatSelection
- dropdownCssClass: "ajax-users-dropdown"
- escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results
- m
diff --git a/app/assets/javascripts/wikis.js.coffee b/app/assets/javascripts/wikis.js.coffee
index 17e790e5b7c..66757565d3a 100644
--- a/app/assets/javascripts/wikis.js.coffee
+++ b/app/assets/javascripts/wikis.js.coffee
@@ -1,4 +1,4 @@
-class Wikis
+class @Wikis
constructor: ->
$('.build-new-wiki').bind "click", ->
field = $('#new_wiki_path')
@@ -7,6 +7,3 @@ class Wikis
if(slug.length > 0)
location.href = path + "/" + slug
-
-
-@Wikis = Wikis
diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss
index be4c4d07f1c..469f4f296ae 100644
--- a/app/assets/stylesheets/behaviors.scss
+++ b/app/assets/stylesheets/behaviors.scss
@@ -1,12 +1,22 @@
// Details
//--------
-.js-details-container .content { display: none; }
-.js-details-container .content.hide { display: block; }
-.js-details-container.open .content { display: block; }
-.js-details-container.open .content.hide { display: none; }
+.js-details-container {
+ .content {
+ display: none;
+ &.hide { display: block; }
+ }
+ &.open .content {
+ display: block;
+ &.hide { display: none; }
+ }
+}
// Toggle between two states.
-.js-toggler-container .turn-on { display: block; }
-.js-toggler-container .turn-off { display: none; }
-.js-toggler-container.on .turn-on { display: none; }
-.js-toggler-container.on .turn-off { display: block; }
+.js-toggler-container {
+ .turn-on { display: block; }
+ .turn-off { display: none; }
+ &.on {
+ .turn-on { display: none; }
+ .turn-off { display: block; }
+ }
+}
diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index cd2f4e45e3c..2fc738c18d8 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/generic/common.scss
@@ -330,10 +330,6 @@ table {
}
}
-@media (max-width: $screen-xs-max) {
- .container .content { margin-top: 20px; }
-}
-
.wiki .highlight, .note-body .highlight {
margin-bottom: 9px;
}
diff --git a/app/assets/stylesheets/generic/files.scss b/app/assets/stylesheets/generic/files.scss
index e2b0ef0c5ea..1ed41272ac5 100644
--- a/app/assets/stylesheets/generic/files.scss
+++ b/app/assets/stylesheets/generic/files.scss
@@ -42,7 +42,6 @@
}
.file-content {
background: #fff;
- font-size: 11px;
&.image_file {
background: #eee;
@@ -54,8 +53,6 @@
}
&.wiki {
- font-size: 14px;
- line-height: 1.6;
padding: 25px;
.highlight {
diff --git a/app/assets/stylesheets/generic/highlight.scss b/app/assets/stylesheets/generic/highlight.scss
index 4110bddf4f3..ae08539d454 100644
--- a/app/assets/stylesheets/generic/highlight.scss
+++ b/app/assets/stylesheets/generic/highlight.scss
@@ -59,6 +59,10 @@
pre {
white-space: pre;
word-wrap: normal;
+
+ code {
+ font-family: $monospace_font;
+ }
}
}
}
diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss
index 94149594e24..79fbad4b946 100644
--- a/app/assets/stylesheets/generic/issue_box.scss
+++ b/app/assets/stylesheets/generic/issue_box.scss
@@ -113,6 +113,11 @@
padding: 10px 15px;
}
+ .cross-project-ref {
+ float: left;
+ padding: 10px 15px;
+ }
+
.creator {
float: right;
padding: 10px 15px;
diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss
new file mode 100644
index 00000000000..c164b07b104
--- /dev/null
+++ b/app/assets/stylesheets/generic/mobile.scss
@@ -0,0 +1,17 @@
+/** Common mobile (screen XS) styles **/
+@media (max-width: $screen-xs-max) {
+ .container .content {
+ margin-top: 20px;
+ }
+
+ .nav.nav-tabs > li > a {
+ padding: 10px;
+ font-size: 12px;
+ margin-right: 3px;
+
+ .badge {
+ display: none;
+ }
+ }
+}
+
diff --git a/app/assets/stylesheets/generic/timeline.scss b/app/assets/stylesheets/generic/timeline.scss
index f29cf25fa4c..57e9e8ae5c5 100644
--- a/app/assets/stylesheets/generic/timeline.scss
+++ b/app/assets/stylesheets/generic/timeline.scss
@@ -75,3 +75,20 @@
}
}
}
+
+@media (max-width: $screen-xs-max) {
+ .timeline {
+ &:before {
+ background: none;
+ }
+ .timeline-entry .timeline-entry-inner {
+ .timeline-icon {
+ display: none;
+ }
+
+ .timeline-content {
+ margin-left: 0;
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss
index 36bc5df2f44..dffa2dc9ed2 100644
--- a/app/assets/stylesheets/highlight/monokai.scss
+++ b/app/assets/stylesheets/highlight/monokai.scss
@@ -29,28 +29,30 @@
.hljs-tag,
.hljs-tag .hljs-title,
- .hljs-keyword,
- .hljs-literal,
.hljs-strong,
.hljs-change,
.hljs-winutils,
.hljs-flow,
.lisp .hljs-title,
.clojure .hljs-built_in,
+ .hljs-keyword,
.nginx .hljs-title,
.tex .hljs-special {
color: #F92672;
}
.hljs {
- color: #DDD;
+ color: #F8F8F2;
}
- .hljs .hljs-constant,
- .asciidoc .hljs-code {
+ .asciidoc .hljs-code,
+ .markdown .hljs-code,
+ .hljs-literal,
+ .hljs-function .hljs-keyword {
color: #66D9EF;
}
+
.hljs-code,
.hljs-class .hljs-title,
.hljs-header {
@@ -62,18 +64,27 @@
.hljs-symbol,
.hljs-symbol .hljs-string,
.hljs-value,
+ .hljs-constant,
+ .hljs-number,
.hljs-regexp {
- color: #BF79DB;
+ color: #AE81FF;
+ }
+
+ .hljs-string {
+ color: #E6DB74;
+ }
+
+ .hljs-params {
+ color: #fd971f;
}
.hljs-link_url,
.hljs-tag .hljs-value,
- .hljs-string,
.hljs-bullet,
.hljs-subst,
.hljs-title,
.hljs-emphasis,
- .haskell .hljs-type,
+ .hljs-type,
.hljs-preprocessor,
.hljs-pragma,
.ruby .hljs-class .hljs-parent,
@@ -99,12 +110,12 @@
}
.hljs-comment,
- .java .hljs-annotation,
+ .hljs-annotation,
.smartquote,
.hljs-blockquote,
.hljs-horizontal_rule,
- .python .hljs-decorator,
.hljs-template_comment,
+ .hljs-decorator,
.hljs-pi,
.hljs-doctype,
.hljs-deletion,
diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss
index 815cf367ae8..8d5822937a0 100644
--- a/app/assets/stylesheets/highlight/white.scss
+++ b/app/assets/stylesheets/highlight/white.scss
@@ -186,3 +186,11 @@
}
}
}
+
+.readme-holder .wiki, .note-body, .wiki-holder {
+ .white {
+ .highlight, pre, .hljs {
+ background: #F9F9F9;
+ }
+ }
+}
diff --git a/app/assets/stylesheets/main/fonts.scss b/app/assets/stylesheets/main/fonts.scss
index d90274a0db9..f945aaca848 100644
--- a/app/assets/stylesheets/main/fonts.scss
+++ b/app/assets/stylesheets/main/fonts.scss
@@ -1,3 +1,3 @@
/** Typo **/
-$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace;
+$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace;
$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif;
diff --git a/app/assets/stylesheets/main/mixins.scss b/app/assets/stylesheets/main/mixins.scss
index 7f607fc4e8b..5f83913b73b 100644
--- a/app/assets/stylesheets/main/mixins.scss
+++ b/app/assets/stylesheets/main/mixins.scss
@@ -58,8 +58,8 @@
}
@mixin md-typography {
- font-size: 14px;
- line-height: 1.6;
+ font-size: 15px;
+ line-height: 1.5;
img {
max-width: 100%;
@@ -93,7 +93,7 @@
blockquote p {
color: #888;
- font-size: 14px;
+ font-size: 15px;
line-height: 1.5;
}
diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/sections/events.scss
index 656aa5b18a6..a766d6e77ab 100644
--- a/app/assets/stylesheets/sections/events.scss
+++ b/app/assets/stylesheets/sections/events.scss
@@ -47,7 +47,7 @@
.event-title {
@include str-truncated(72%);
color: #333;
- font-weight: normal;
+ font-weight: 500;
font-size: 14px;
.author_name {
color: #333;
@@ -56,12 +56,9 @@
.event-body {
margin-left: 35px;
margin-right: 100px;
+ color: #777;
- .event-info {
- color: #666;
- }
.event-note {
- color: #666;
margin-top: 5px;
.md {
@@ -72,7 +69,7 @@
border: none;
background: #f9f9f9;
border-radius: 0;
- color: #666;
+ color: #777;
margin: 0 20px;
}
@@ -120,7 +117,6 @@
padding: 3px;
padding-left: 0;
border: none;
- color: #666;
.commit-row-title {
font-size: 12px;
}
@@ -186,7 +182,24 @@
}
@media (max-width: $screen-xs-max) {
- .event-item .event-title {
- @include str-truncated(65%);
+ .event-item {
+ .event-title {
+ white-space: normal;
+ overflow: visible;
+ max-width: 100%;
+ }
+ .avatar {
+ display: none;
+ }
+
+ .event-body {
+ margin: 0;
+ border-left: 2px solid #DDD;
+ padding-left: 10px;
+ }
+
+ .event-item-timestamp {
+ display: none;
+ }
}
}
diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/sections/header.scss
index e0e0d60c387..9ad1a1db2cd 100644
--- a/app/assets/stylesheets/sections/header.scss
+++ b/app/assets/stylesheets/sections/header.scss
@@ -59,6 +59,7 @@ header {
}
.navbar-collapse {
+ margin-top: 47px;
padding-right: 0;
padding-left: 0;
}
diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/sections/issues.scss
index a7fa715d2e0..9a5400fffbc 100644
--- a/app/assets/stylesheets/sections/issues.scss
+++ b/app/assets/stylesheets/sections/issues.scss
@@ -75,7 +75,7 @@
}
.participants {
- margin-bottom: 10px;
+ margin-bottom: 20px;
}
.issues_bulk_update {
@@ -151,4 +151,14 @@ form.edit-issue {
}
}
}
+
+ .issue {
+ &:hover .issue-actions {
+ display: none !important;
+ }
+
+ .issue-updated-at {
+ display: none;
+ }
+ }
}
diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss
index 22f20a7df4d..ec844cc00b0 100644
--- a/app/assets/stylesheets/sections/merge_requests.scss
+++ b/app/assets/stylesheets/sections/merge_requests.scss
@@ -113,30 +113,36 @@
font-size: 15px;
border-bottom: 1px solid #BBB;
color: #777;
+ background-color: #F5F5F5;
&.ci-success {
color: $bg_success;
border-color: $border_success;
+ background-color: #F1FAF1;
}
&.ci-pending {
color: #548;
border-color: #548;
+ background-color: #F4F1FA;
}
&.ci-running {
color: $bg_warning;
border-color: $border_warning;
+ background-color: #FAF5F1;
}
&.ci-failed {
color: $bg_danger;
border-color: $border_danger;
+ background-color: #FAF1F1;
}
&.ci-error {
color: $bg_danger;
border-color: $border_danger;
+ background-color: #FAF1F1;
}
}
diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss
index 31c0a0835db..ccd672c5f67 100644
--- a/app/assets/stylesheets/sections/nav.scss
+++ b/app/assets/stylesheets/sections/nav.scss
@@ -63,7 +63,6 @@
@media (max-width: $screen-xs-max) {
font-size: 18px;
margin: 0;
-
max-height: none;
&, .container {
@@ -86,6 +85,7 @@
color: #fff;
font-weight: normal;
text-shadow: none;
+ border: none;
&:after { display: none; }
}
diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss
index 65ad46a4579..e1f9c0cb258 100644
--- a/app/assets/stylesheets/sections/notes.scss
+++ b/app/assets/stylesheets/sections/notes.scss
@@ -36,13 +36,16 @@ ul.notes {
font-size: 13px;
}
.author {
- color: #555;
+ color: #333;
font-weight: bold;
font-size: 14px;
&:hover {
- color: $link_hover_color;
+ color: $link_color;
}
}
+ .author-username {
+ font-size: 14px;
+ }
}
.discussion {
diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/sections/profile.scss
index 086875582f3..b9f4e317e9c 100644
--- a/app/assets/stylesheets/sections/profile.scss
+++ b/app/assets/stylesheets/sections/profile.scss
@@ -111,3 +111,20 @@
height: 50px;
}
}
+
+//CSS for password-strength indicator
+#password-strength {
+ margin-bottom: 0;
+}
+
+.has-success input {
+ background-color: #D6F1D7 !important;
+}
+
+.has-error input {
+ background-color: #F3CECE !important;
+}
+
+.has-warning input {
+ background-color: #FFE9A4 !important;
+}
diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss
index b4ee5ccc8d7..7b894cf00bb 100644
--- a/app/assets/stylesheets/sections/projects.scss
+++ b/app/assets/stylesheets/sections/projects.scss
@@ -270,3 +270,41 @@ ul.nav.nav-projects-tabs {
color: #999;
}
}
+
+.fork-namespaces {
+ .thumbnail {
+
+ &.fork-exists-thumbnail {
+ border-color: #EEE;
+
+ .caption {
+ color: #999;
+ }
+ }
+
+ &.fork-thumbnail {
+ border-color: #AAA;
+
+ &:hover {
+ background-color: $hover;
+ }
+ }
+
+ a {
+ text-decoration: none;
+ }
+ }
+}
+
+@media (max-width: $screen-xs-max) {
+ .project-home-panel {
+ .star-fork-buttons {
+ padding-top: 10px;
+ padding-right: 15px;
+ }
+ }
+
+ .project-home-links {
+ display: none;
+ }
+}
diff --git a/app/controllers/admin/background_jobs_controller.rb b/app/controllers/admin/background_jobs_controller.rb
index 4c1d0df4110..338496013a0 100644
--- a/app/controllers/admin/background_jobs_controller.rb
+++ b/app/controllers/admin/background_jobs_controller.rb
@@ -1,6 +1,6 @@
class Admin::BackgroundJobsController < Admin::ApplicationController
def show
- ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Settings.gitlab.user} -o pid,pcpu,pmem,stat,start,command))
+ ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Gitlab.config.gitlab.user} -o pid,pcpu,pmem,stat,start,command))
@sidekiq_processes = ps_output.split("\n").grep(/sidekiq/)
end
end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index 2f0d344802f..7c2388e81be 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -31,17 +31,11 @@ class Admin::ProjectsController < Admin::ApplicationController
protected
def project
- id = params[:project_id] || params[:id]
-
- @project = Project.find_with_namespace(id)
+ @project = Project.find_with_namespace(params[:id])
@project || render_404
end
def group
- @group ||= project.group
- end
-
- def repository
- @repository ||= project.repository
+ @group ||= @project.group
end
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 13d8d2a3e0a..f1e1bebe5ce 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -5,9 +5,7 @@ class ApplicationController < ActionController::Base
before_filter :authenticate_user!
before_filter :reject_blocked!
before_filter :check_password_expiration
- before_filter :add_abilities
before_filter :ldap_security_check
- before_filter :dev_tools if Rails.env == 'development'
before_filter :default_headers
before_filter :add_gon_variables
before_filter :configure_permitted_parameters, if: :devise_controller?
@@ -73,7 +71,7 @@ class ApplicationController < ActionController::Base
end
def abilities
- @abilities ||= Six.new
+ Ability.abilities
end
def can?(object, action, subject)
@@ -81,28 +79,31 @@ class ApplicationController < ActionController::Base
end
def project
- id = params[:project_id] || params[:id]
-
- # Redirect from
- # localhost/group/project.git
- # to
- # localhost/group/project
- #
- if id =~ /\.git\Z/
- redirect_to request.original_url.gsub(/\.git\Z/, '') and return
- end
+ unless @project
+ id = params[:project_id] || params[:id]
+
+ # Redirect from
+ # localhost/group/project.git
+ # to
+ # localhost/group/project
+ #
+ if id =~ /\.git\Z/
+ redirect_to request.original_url.gsub(/\.git\Z/, '') and return
+ end
- @project = Project.find_with_namespace(id)
+ @project = Project.find_with_namespace(id)
- if @project and can?(current_user, :read_project, @project)
- @project
- elsif current_user.nil?
- @project = nil
- authenticate_user!
- else
- @project = nil
- render_404 and return
+ if @project and can?(current_user, :read_project, @project)
+ @project
+ elsif current_user.nil?
+ @project = nil
+ authenticate_user!
+ else
+ @project = nil
+ render_404 and return
+ end
end
+ @project
end
def repository
@@ -111,22 +112,10 @@ class ApplicationController < ActionController::Base
nil
end
- def add_abilities
- abilities << Ability
- end
-
def authorize_project!(action)
return access_denied! unless can?(current_user, action, project)
end
- def authorize_code_access!
- return access_denied! unless can?(current_user, :download_code, project)
- end
-
- def authorize_push!
- return access_denied! unless can?(current_user, :push_code, project)
- end
-
def authorize_labels!
# Labels should be accessible for issues and/or merge requests
authorize_read_issue! || authorize_read_merge_request!
@@ -170,9 +159,6 @@ class ApplicationController < ActionController::Base
response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT"
end
- def dev_tools
- end
-
def default_headers
headers['X-Frame-Options'] = 'DENY'
headers['X-XSS-Protection'] = '1; mode=block'
diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb
index f8e1a31e0b3..ada7031fea4 100644
--- a/app/controllers/explore/groups_controller.rb
+++ b/app/controllers/explore/groups_controller.rb
@@ -1,7 +1,6 @@
class Explore::GroupsController < ApplicationController
skip_before_filter :authenticate_user!,
- :reject_blocked, :set_current_user_for_observers,
- :add_abilities
+ :reject_blocked, :set_current_user_for_observers
layout "explore"
diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb
index b6fa8b7e387..d75fd8e72fa 100644
--- a/app/controllers/explore/projects_controller.rb
+++ b/app/controllers/explore/projects_controller.rb
@@ -1,7 +1,6 @@
class Explore::ProjectsController < ApplicationController
skip_before_filter :authenticate_user!,
- :reject_blocked,
- :add_abilities
+ :reject_blocked
layout 'explore'
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 63c05d4f33b..ca88d033878 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -19,6 +19,7 @@ class Groups::GroupMembersController < ApplicationController
def destroy
@users_group = @group.group_members.find(params[:id])
+
if can?(current_user, :destroy, @users_group) # May fail if last owner.
@users_group.destroy
respond_to do |format|
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index f46b36568f3..3e984e5007a 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -42,25 +42,32 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def handle_omniauth
if current_user
- # Change a logged-in user's authentication method:
- current_user.extern_uid = oauth['uid']
- current_user.provider = oauth['provider']
- current_user.save
+ # Add new authentication method
+ current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider'])
redirect_to profile_path
else
@user = Gitlab::OAuth::User.new(oauth)
+ @user.save
- if Gitlab.config.omniauth['allow_single_sign_on'] && @user.new?
- @user.save
- end
-
- if @user.valid?
+ # Only allow properly saved users to login.
+ if @user.persisted? && @user.valid?
sign_in_and_redirect(@user.gl_user)
else
- error_message = @user.gl_user.errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ")
+ error_message =
+ if @user.gl_user.errors.any?
+ @user.gl_user.errors.map do |attribute, message|
+ "#{attribute} #{message}"
+ end.join(", ")
+ else
+ ''
+ end
+
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
end
end
+ rescue ForbiddenAction => e
+ flash[:notice] = e.message
+ redirect_to new_user_session_path
end
def oauth
diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb
index 7e4580017dd..6b7fe06d59f 100644
--- a/app/controllers/projects/application_controller.rb
+++ b/app/controllers/projects/application_controller.rb
@@ -29,4 +29,31 @@ class Projects::ApplicationController < ApplicationController
redirect_to project_tree_path(@project, @ref), notice: "This action is not allowed unless you are on top of a branch"
end
end
+
+ def set_filter_variables(collection)
+ params[:sort] ||= 'newest'
+ params[:scope] = 'all' if params[:scope].blank?
+ params[:state] = 'opened' if params[:state].blank?
+
+ @sort = params[:sort].humanize
+
+ assignee_id = params[:assignee_id]
+ author_id = params[:author_id]
+ milestone_id = params[:milestone_id]
+
+ if assignee_id.present? && !assignee_id.to_i.zero?
+ @assignee = @project.team.find(assignee_id)
+ end
+
+ if author_id.present? && !author_id.to_i.zero?
+ @author = @project.team.find(assignee_id)
+ end
+
+ if milestone_id.present? && !milestone_id.to_i.zero?
+ @milestone = @project.milestones.find(milestone_id)
+ end
+
+ @assignees = User.where(id: collection.pluck(:assignee_id))
+ @authors = User.where(id: collection.pluck(:author_id))
+ end
end
diff --git a/app/controllers/projects/base_tree_controller.rb b/app/controllers/projects/base_tree_controller.rb
index 5e305934433..a7b1b7b40e8 100644
--- a/app/controllers/projects/base_tree_controller.rb
+++ b/app/controllers/projects/base_tree_controller.rb
@@ -1,8 +1,7 @@
class Projects::BaseTreeController < Projects::ApplicationController
include ExtractsPath
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
end
diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb
index a3c41301676..367d1295f34 100644
--- a/app/controllers/projects/blame_controller.rb
+++ b/app/controllers/projects/blame_controller.rb
@@ -3,8 +3,7 @@ class Projects::BlameController < Projects::ApplicationController
include ExtractsPath
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index 7009e3b1bc8..2412800c493 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -3,10 +3,9 @@ class Projects::BlobController < Projects::ApplicationController
include ExtractsPath
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
- before_filter :authorize_push!, only: [:destroy]
+ before_filter :authorize_push_code!, only: [:destroy]
before_filter :blob
@@ -20,7 +19,7 @@ class Projects::BlobController < Projects::ApplicationController
flash[:notice] = "Your changes have been successfully committed"
redirect_to project_tree_path(@project, @ref)
else
- flash[:alert] = result[:error]
+ flash[:alert] = result[:message]
render :show
end
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index faa0ce67ca8..cff1a907dc2 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -1,10 +1,10 @@
class Projects::BranchesController < Projects::ApplicationController
+ include ActionView::Helpers::SanitizeHelper
# Authorize
- before_filter :authorize_read_project!
before_filter :require_non_empty_project
- before_filter :authorize_code_access!
- before_filter :authorize_push!, only: [:create, :destroy]
+ before_filter :authorize_download_code!
+ before_filter :authorize_push_code!, only: [:create, :destroy]
def index
@sort = params[:sort] || 'name'
@@ -17,8 +17,11 @@ class Projects::BranchesController < Projects::ApplicationController
end
def create
+ branch_name = sanitize(strip_tags(params[:branch_name]))
+ ref = sanitize(strip_tags(params[:ref]))
result = CreateBranchService.new(project, current_user).
- execute(params[:branch_name], params[:ref])
+ execute(branch_name, ref)
+
if result[:status] == :success
@branch = result[:branch]
redirect_to project_tree_path(@project, @branch.name)
diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb
index 66c67b661db..dac858d8e16 100644
--- a/app/controllers/projects/commit_controller.rb
+++ b/app/controllers/projects/commit_controller.rb
@@ -3,20 +3,19 @@
# Not to be confused with CommitsController, plural.
class Projects::CommitController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
before_filter :commit
def show
return git_not_found! unless @commit
- @line_notes = project.notes.for_commit_id(commit.id).inline
- @branches = project.repository.branch_names_contains(commit.id)
+ @line_notes = @project.notes.for_commit_id(commit.id).inline
+ @branches = @project.repository.branch_names_contains(commit.id)
@diffs = @commit.diffs
- @note = project.build_commit_note(commit)
- @notes_count = project.notes.for_commit_id(commit.id).count
- @notes = project.notes.for_commit_id(@commit.id).not_inline.fresh
+ @note = @project.build_commit_note(commit)
+ @notes_count = @project.notes.for_commit_id(commit.id).count
+ @notes = @project.notes.for_commit_id(@commit.id).not_inline.fresh
@noteable = @commit
@comments_allowed = @reply_allowed = true
@comments_target = {
@@ -32,6 +31,6 @@ class Projects::CommitController < Projects::ApplicationController
end
def commit
- @commit ||= project.repository.commit(params[:id])
+ @commit ||= @project.repository.commit(params[:id])
end
end
diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb
index b7f09eb271d..9476b6c0284 100644
--- a/app/controllers/projects/commits_controller.rb
+++ b/app/controllers/projects/commits_controller.rb
@@ -4,8 +4,7 @@ class Projects::CommitsController < Projects::ApplicationController
include ExtractsPath
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb
index 7a671e8455d..ffb8c2e4af1 100644
--- a/app/controllers/projects/compare_controller.rb
+++ b/app/controllers/projects/compare_controller.rb
@@ -1,7 +1,6 @@
class Projects::CompareController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
def index
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index d20937ea8ea..024b9520d30 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -42,7 +42,7 @@ class Projects::DeployKeysController < Projects::ApplicationController
end
def enable
- project.deploy_keys << available_keys.find(params[:id])
+ @project.deploy_keys << available_keys.find(params[:id])
redirect_to project_deploy_keys_path(@project)
end
diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb
index 8976d7c7be8..65661c80410 100644
--- a/app/controllers/projects/edit_tree_controller.rb
+++ b/app/controllers/projects/edit_tree_controller.rb
@@ -1,7 +1,7 @@
class Projects::EditTreeController < Projects::BaseTreeController
before_filter :require_branch_head
before_filter :blob
- before_filter :authorize_push!
+ before_filter :authorize_push_code!
before_filter :from_merge_request
before_filter :after_edit_path
@@ -22,7 +22,7 @@ class Projects::EditTreeController < Projects::BaseTreeController
redirect_to after_edit_path
else
- flash[:alert] = result[:error]
+ flash[:alert] = result[:message]
render :show
end
end
diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb
new file mode 100644
index 00000000000..a0481d11582
--- /dev/null
+++ b/app/controllers/projects/forks_controller.rb
@@ -0,0 +1,22 @@
+class Projects::ForksController < Projects::ApplicationController
+ # Authorize
+ before_filter :authorize_download_code!
+ before_filter :require_non_empty_project
+
+ def new
+ @namespaces = current_user.manageable_namespaces
+ @namespaces.delete(@project.namespace)
+ end
+
+ def create
+ namespace = Namespace.find(params[:namespace_id])
+ @forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute
+
+ if @forked_project.saved? && @forked_project.forked?
+ redirect_to(@forked_project, notice: 'Project was successfully forked.')
+ else
+ @title = 'Fork project'
+ render :error
+ end
+ end
+end
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index 610b4967fea..4a318cb7d56 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -1,7 +1,6 @@
class Projects::GraphsController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb
index cab8fd76e6c..2d6c3111192 100644
--- a/app/controllers/projects/hooks_controller.rb
+++ b/app/controllers/projects/hooks_controller.rb
@@ -26,6 +26,7 @@ class Projects::HooksController < Projects::ApplicationController
def test
if !@project.empty_repo?
status = TestHookService.new.execute(hook, current_user)
+
if status
flash[:notice] = 'Hook successfully executed.'
else
diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb
new file mode 100644
index 00000000000..b8350642804
--- /dev/null
+++ b/app/controllers/projects/imports_controller.rb
@@ -0,0 +1,49 @@
+class Projects::ImportsController < Projects::ApplicationController
+ # Authorize
+ before_filter :authorize_admin_project!
+ before_filter :require_no_repo
+ before_filter :redirect_if_progress, except: :show
+
+ def new
+ end
+
+ def create
+ @project.import_url = params[:project][:import_url]
+
+ if @project.save
+ @project.reload
+
+ if @project.import_failed?
+ @project.import_retry
+ else
+ @project.import_start
+ end
+ end
+
+ redirect_to project_import_path(@project)
+ end
+
+ def show
+ unless @project.import_in_progress?
+ if @project.import_finished?
+ redirect_to(@project) and return
+ else
+ redirect_to new_project_import_path(@project) and return
+ end
+ end
+ end
+
+ private
+
+ def require_no_repo
+ if @project.repository_exists?
+ redirect_to(@project) and return
+ end
+ end
+
+ def redirect_if_progress
+ if @project.import_in_progress?
+ redirect_to project_import_path(@project) and return
+ end
+ end
+end
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index c6d526f05c5..22235123826 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -18,18 +18,12 @@ class Projects::IssuesController < Projects::ApplicationController
def index
terms = params['issue_search']
+ set_filter_variables(@project.issues)
- @issues = issues_filtered
+ @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id))
@issues = @issues.full_search(terms) if terms.present?
@issues = @issues.page(params[:page]).per(20)
- assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
- @assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero?
- @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
- sort_param = params[:sort] || 'newest'
- @sort = sort_param.humanize unless sort_param.empty?
- @assignees = User.where(id: @project.issues.pluck(:assignee_id)).active
-
respond_to do |format|
format.html
format.atom { render layout: false }
@@ -127,12 +121,6 @@ class Projects::IssuesController < Projects::ApplicationController
return render_404 unless @project.issues_enabled
end
- def issues_filtered
- params[:scope] = 'all' if params[:scope].blank?
- params[:state] = 'opened' if params[:state].blank?
- @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id))
- end
-
# Since iids are implemented only in 6.1
# user may navigate to issue page using old global ids.
#
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 20a733b10e1..4d6f41e9de5 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -17,18 +17,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
def index
- params[:sort] ||= 'newest'
- params[:scope] = 'all' if params[:scope].blank?
- params[:state] = 'opened' if params[:state].blank?
+ set_filter_variables(@project.merge_requests)
@merge_requests = MergeRequestsFinder.new.execute(current_user, params.merge(project_id: @project.id))
@merge_requests = @merge_requests.page(params[:page]).per(20)
-
- @sort = params[:sort].humanize
- assignee_id, milestone_id = params[:assignee_id], params[:milestone_id]
- @assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero?
- @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
- @assignees = User.where(id: @project.merge_requests.pluck(:assignee_id))
end
def show
@@ -225,6 +217,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@allowed_to_merge = allowed_to_merge?
@show_merge_controls = @merge_request.open? && @commits.any? && @allowed_to_merge
@source_branch = @merge_request.source_project.repository.find_branch(@merge_request.source_branch).try(:name)
+
+ if @merge_request.locked_long_ago?
+ @merge_request.unlock_mr
+ @merge_request.close
+ end
end
def allowed_to_merge?
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index d338cdedfaf..f362f449e70 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -103,7 +103,9 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def module_enabled
- return render_404 unless @project.issues_enabled
+ unless @project.issues_enabled || @project.merge_requests_enabled
+ return render_404
+ end
end
def milestone_params
diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb
index 9832495c64f..ada1aed0df7 100644
--- a/app/controllers/projects/network_controller.rb
+++ b/app/controllers/projects/network_controller.rb
@@ -3,8 +3,7 @@ class Projects::NetworkController < Projects::ApplicationController
include ApplicationHelper
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
diff --git a/app/controllers/projects/new_tree_controller.rb b/app/controllers/projects/new_tree_controller.rb
index 71a5c6499ec..ffba706b2f6 100644
--- a/app/controllers/projects/new_tree_controller.rb
+++ b/app/controllers/projects/new_tree_controller.rb
@@ -1,6 +1,6 @@
class Projects::NewTreeController < Projects::BaseTreeController
before_filter :require_branch_head
- before_filter :authorize_push!
+ before_filter :authorize_push_code!
def show
end
diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb
index 5ec9c576a66..fdbc4c5a098 100644
--- a/app/controllers/projects/raw_controller.rb
+++ b/app/controllers/projects/raw_controller.rb
@@ -3,8 +3,7 @@ class Projects::RawController < Projects::ApplicationController
include ExtractsPath
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
def show
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 7997c726fbb..5d9336bdc49 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -2,8 +2,7 @@ class Projects::RefsController < Projects::ApplicationController
include ExtractsPath
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
+ before_filter :authorize_download_code!
before_filter :require_non_empty_project
def switch
diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb
index 4e0f190ed1c..3a90c1c806d 100644
--- a/app/controllers/projects/repositories_controller.rb
+++ b/app/controllers/projects/repositories_controller.rb
@@ -1,8 +1,14 @@
class Projects::RepositoriesController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
- before_filter :authorize_code_access!
- before_filter :require_non_empty_project
+ before_filter :authorize_download_code!
+ before_filter :require_non_empty_project, except: :create
+ before_filter :authorize_admin_project!, only: :create
+
+ def create
+ @project.create_repository
+
+ redirect_to @project
+ end
def archive
unless can?(current_user, :download_code, @project)
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index b50f6286459..c50a1f1e75b 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -41,7 +41,8 @@ class Projects::ServicesController < Projects::ApplicationController
params.require(:service).permit(
:title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url, :webhook,
- :user_key, :device, :priority, :sound
+ :user_key, :device, :priority, :sound, :bamboo_url, :username, :password,
+ :build_key, :server
)
end
end
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 537c94bda20..162ddef0fec 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -1,10 +1,8 @@
class Projects::TagsController < Projects::ApplicationController
# Authorize
- before_filter :authorize_read_project!
before_filter :require_non_empty_project
-
- before_filter :authorize_code_access!
- before_filter :authorize_push!, only: [:create]
+ before_filter :authorize_download_code!
+ before_filter :authorize_push_code!, only: [:create]
before_filter :authorize_admin_project!, only: [:destroy]
def index
diff --git a/app/controllers/projects/team_members_controller.rb b/app/controllers/projects/team_members_controller.rb
index 7bb799eba64..0791e6080fb 100644
--- a/app/controllers/projects/team_members_controller.rb
+++ b/app/controllers/projects/team_members_controller.rb
@@ -10,7 +10,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def new
- @user_project_relation = project.project_members.new
+ @user_project_relation = @project.project_members.new
end
def create
@@ -26,7 +26,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def update
- @user_project_relation = project.project_members.find_by(user_id: member)
+ @user_project_relation = @project.project_members.find_by(user_id: member)
@user_project_relation.update_attributes(member_params)
unless @user_project_relation.valid?
@@ -36,7 +36,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def destroy
- @user_project_relation = project.project_members.find_by(user_id: member)
+ @user_project_relation = @project.project_members.find_by(user_id: member)
@user_project_relation.destroy
respond_to do |format|
@@ -46,7 +46,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end
def leave
- project.project_members.find_by(user_id: current_user).destroy
+ @project.project_members.find_by(user_id: current_user).destroy
respond_to do |format|
format.html { redirect_to :back }
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index aca091e7d2c..fcff6952d38 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -4,9 +4,7 @@ class ProjectsController < ApplicationController
before_filter :repository, except: [:new, :create]
# Authorize
- before_filter :authorize_read_project!, except: [:index, :new, :create]
- before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import]
- before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
+ before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
layout 'navless', only: [:new, :create, :fork]
before_filter :set_title, only: [:new, :create]
@@ -21,10 +19,11 @@ class ProjectsController < ApplicationController
def create
@project = ::Projects::CreateService.new(current_user, project_params).execute
- flash[:notice] = 'Project was successfully created.' if @project.saved?
- respond_to do |format|
- format.js
+ if @project.saved?
+ redirect_to project_path(@project), notice: 'Project was successfully created.'
+ else
+ render 'new'
end
end
@@ -49,12 +48,10 @@ class ProjectsController < ApplicationController
def show
if @project.import_in_progress?
- redirect_to import_project_path(@project)
+ redirect_to project_import_path(@project)
return
end
- return authenticate_user! unless @project.public? || current_user
-
limit = (params[:limit] || 20).to_i
@events = @project.events.recent
@events = event_filter.apply_filter(@events)
@@ -64,41 +61,24 @@ class ProjectsController < ApplicationController
respond_to do |format|
format.html do
- if @project.empty_repo?
- render "projects/empty", layout: user_layout
+ if @project.repository_exists?
+ if @project.empty_repo?
+ render "projects/empty", layout: user_layout
+ else
+ @last_push = current_user.recent_push(@project.id) if current_user
+ render :show, layout: user_layout
+ end
else
- @last_push = current_user.recent_push(@project.id) if current_user
- render :show, layout: user_layout
+ render "projects/no_repo", layout: user_layout
end
end
- format.json { pager_json("events/_events", @events.count) }
- end
- end
-
- def import
- if project.import_finished?
- redirect_to @project
- return
- end
- end
-
- def retry_import
- unless @project.import_failed?
- redirect_to import_project_path(@project)
- end
-
- @project.import_url = project_params[:import_url]
- if @project.save
- @project.reload
- @project.import_retry
+ format.json { pager_json("events/_events", @events.count) }
end
-
- redirect_to import_project_path(@project)
end
def destroy
- return access_denied! unless can?(current_user, :remove_project, project)
+ return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).execute
@@ -115,22 +95,6 @@ class ProjectsController < ApplicationController
end
end
- def fork
- @forked_project = ::Projects::ForkService.new(project, current_user).execute
-
- respond_to do |format|
- format.html do
- if @forked_project.saved? && @forked_project.forked?
- redirect_to(@forked_project, notice: 'Project was successfully forked.')
- else
- @title = 'Fork project'
- render "fork"
- end
- end
- format.js
- end
- end
-
def autocomplete_sources
note_type = params['type']
note_id = params['type_id']
@@ -148,8 +112,8 @@ class ProjectsController < ApplicationController
end
def archive
- return access_denied! unless can?(current_user, :archive_project, project)
- project.archive!
+ return access_denied! unless can?(current_user, :archive_project, @project)
+ @project.archive!
respond_to do |format|
format.html { redirect_to @project }
@@ -157,8 +121,8 @@ class ProjectsController < ApplicationController
end
def unarchive
- return access_denied! unless can?(current_user, :archive_project, project)
- project.unarchive!
+ return access_denied! unless can?(current_user, :archive_project, @project)
+ @project.unarchive!
respond_to do |format|
format.html { redirect_to @project }
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 30fb4c5552d..bf3312fedc8 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -9,7 +9,7 @@ class SnippetsController < ApplicationController
before_filter :set_title
- skip_before_filter :authenticate_user!, only: [:index, :user_index]
+ skip_before_filter :authenticate_user!, only: [:index, :user_index, :show, :raw]
respond_to :html
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb
index 0b442f5383a..67af1801bda 100644
--- a/app/controllers/users_controller.rb
+++ b/app/controllers/users_controller.rb
@@ -20,9 +20,14 @@ class UsersController < ApplicationController
# Get user activity feed for projects common for both users
@events = @user.recent_events.
- where(project_id: authorized_projects_ids).limit(20)
+ where(project_id: authorized_projects_ids).limit(30)
@title = @user.name
+
+ respond_to do |format|
+ format.html
+ format.atom { render layout: false }
+ end
end
def determine_layout
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 56c4f22120d..e1477510065 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -33,6 +33,7 @@ class IssuableFinder
items = by_search(items)
items = by_milestone(items)
items = by_assignee(items)
+ items = by_author(items)
items = by_label(items)
items = sort(items)
end
@@ -48,7 +49,7 @@ class IssuableFinder
else
[]
end
- elsif current_user && params[:authorized_only].presence
+ elsif current_user && params[:authorized_only].presence && !current_user_related?
klass.of_projects(current_user.authorized_projects).references(:project)
else
klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project)
@@ -125,6 +126,14 @@ class IssuableFinder
items
end
+ def by_author(items)
+ if params[:author_id].present?
+ items = items.where(author_id: (params[:author_id] == '0' ? nil : params[:author_id]))
+ end
+
+ items
+ end
+
def by_label(items)
if params[:label_name].present?
label_names = params[:label_name].split(",")
@@ -142,4 +151,8 @@ class IssuableFinder
def project
Project.where(id: params[:project_id]).first if params[:project_id].present?
end
+
+ def current_user_related?
+ params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me'
+ end
end
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
index b29ab6cf40b..4b0c69f2d2f 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -29,6 +29,8 @@ class SnippetsFinder
def by_user(current_user, user, scope)
snippets = user.snippets.fresh.non_expired
+ return snippets.are_public unless current_user
+
if user == current_user
case scope
when 'are_internal' then
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 11fbf1baae7..420ac3f77c7 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -1,14 +1,9 @@
module BlobHelper
def highlightjs_class(blob_name)
- if blob_name.include?('.')
- ext = blob_name.split('.').last
- return 'language-' + ext
+ if no_highlight_files.include?(blob_name.downcase)
+ 'no-highlight'
else
- if no_highlight_files.include?(blob_name.downcase)
- 'no-highlight'
- else
- blob_name.downcase
- end
+ blob_name.downcase
end
end
diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb
index 0e0532b65b2..36adeadd8a5 100644
--- a/app/helpers/commits_helper.rb
+++ b/app/helpers/commits_helper.rb
@@ -87,8 +87,8 @@ module CommitsHelper
# avatar: true will prepend the avatar image
# size: size of the avatar image in px
def commit_person_link(commit, options = {})
- source_name = commit.send "#{options[:source]}_name".to_sym
- source_email = commit.send "#{options[:source]}_email".to_sym
+ source_name = clean(commit.send "#{options[:source]}_name".to_sym)
+ source_email = clean(commit.send "#{options[:source]}_email".to_sym)
user = User.find_for_commit(source_email, source_name)
person_name = user.nil? ? source_name : user.name
@@ -124,4 +124,8 @@ module CommitsHelper
def truncate_sha(sha)
Commit.truncate_sha(sha)
end
+
+ def clean(string)
+ Sanitize.clean(string, remove_contents: true)
+ end
end
diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb
new file mode 100644
index 00000000000..24d67c21d6b
--- /dev/null
+++ b/app/helpers/emails_helper.rb
@@ -0,0 +1,32 @@
+module EmailsHelper
+
+ # Google Actions
+ # https://developers.google.com/gmail/markup/reference/go-to-action
+ def email_action(url)
+ name = action_title(url)
+ if name
+ data = {
+ "@context" => "http://schema.org",
+ "@type" => "EmailMessage",
+ "action" => {
+ "@type" => "ViewAction",
+ "name" => name,
+ "url" => url,
+ }
+ }
+
+ content_tag :script, type: 'application/ld+json' do
+ data.to_json.html_safe
+ end
+ end
+ end
+
+ def action_title(url)
+ return unless url
+ ["merge_requests", "issues", "commit"].each do |action|
+ if url.split("/").include?(action)
+ return "View #{action.humanize.singularize}"
+ end
+ end
+ end
+end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index 71f97fbb8c8..a3136926b38 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -145,4 +145,26 @@ module EventsHelper
rescue
"--broken encoding"
end
+
+ def event_to_atom(xml, event)
+ if event.proper?
+ xml.entry do
+ event_link = event_feed_url(event)
+ event_title = event_feed_title(event)
+ event_summary = event_feed_summary(event)
+
+ xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
+ xml.link href: event_link
+ xml.title truncate(event_title, length: 80)
+ xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
+ xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(event.author_email)
+ xml.author do |author|
+ xml.name event.author_name
+ xml.email event.author_email
+ end
+
+ xml.summary(type: "xhtml") { |x| x << event_summary unless event_summary.nil? }
+ end
+ end
+ end
end
diff --git a/app/helpers/git_helper.rb b/app/helpers/git_helper.rb
new file mode 100644
index 00000000000..09684955233
--- /dev/null
+++ b/app/helpers/git_helper.rb
@@ -0,0 +1,5 @@
+module GitHelper
+ def strip_gpg_signature(text)
+ text.gsub(/-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----/m, "")
+ end
+end
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 7d3cb749829..800cacdc2c2 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -254,4 +254,16 @@ module GitlabMarkdownHelper
truncated
end
end
+
+ def cross_project_reference(project, entity)
+ path = project.path_with_namespace
+
+ if entity.kind_of?(Issue)
+ [path, entity.iid].join('#')
+ elsif entity.kind_of?(MergeRequest)
+ [path, entity.iid].join('!')
+ else
+ raise 'Not supported type'
+ end
+ end
end
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 7671033b539..a5b393c1e3c 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -62,6 +62,19 @@ module IssuesHelper
''
end
+ def issue_timestamp(issue)
+ # Shows the created at time and the updated at time if different
+ ts = "#{time_ago_with_tooltip(issue.created_at, 'bottom', 'note_created_ago')}"
+ if issue.updated_at != issue.created_at
+ ts << capture_haml do
+ haml_tag :small do
+ haml_concat " (Edited #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_edited_ago')})"
+ end
+ end
+ end
+ ts.html_safe
+ end
+
# Checks if issues_tracker setting exists in gitlab.yml
def external_issues_tracker_enabled?
Gitlab.config.issues_tracker && Gitlab.config.issues_tracker.values.any?
@@ -100,4 +113,19 @@ module IssuesHelper
'issue-box-open'
end
end
+
+ def issue_to_atom(xml, issue)
+ xml.entry do
+ xml.id project_issue_url(issue.project, issue)
+ xml.link href: project_issue_url(issue.project, issue)
+ xml.title truncate(issue.title, length: 80)
+ xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
+ xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(issue.author_email)
+ xml.author do |author|
+ xml.name issue.author_name
+ xml.email issue.author_email
+ end
+ xml.summary issue.title
+ end
+ end
end
diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb
index bf25dce2301..2bcfde62830 100644
--- a/app/helpers/namespaces_helper.rb
+++ b/app/helpers/namespaces_helper.rb
@@ -25,4 +25,12 @@ module NamespacesHelper
hidden_field_tag(id, value, class: css_class)
end
+
+ def namespace_icon(namespace, size = 40)
+ if namespace.kind_of?(Group)
+ group_icon(namespace.path)
+ else
+ avatar_icon(namespace.owner.email, size)
+ end
+ end
end
diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb
index 7024483b8b3..df18db71c84 100644
--- a/app/helpers/oauth_helper.rb
+++ b/app/helpers/oauth_helper.rb
@@ -16,4 +16,8 @@ module OauthHelper
[:twitter, :github, :google_oauth2].include?(name.to_sym)
end
end
+
+ def additional_providers
+ enabled_oauth_providers.reject{|provider| provider.to_s.starts_with?('ldap')}
+ end
end
diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb
index 0b375558305..6480fd3886f 100644
--- a/app/helpers/profile_helper.rb
+++ b/app/helpers/profile_helper.rb
@@ -1,6 +1,6 @@
module ProfileHelper
def oauth_active_class(provider)
- if current_user.provider == provider.to_s
+ if current_user.identities.exists?(provider: provider.to_s)
'active'
end
end
@@ -10,10 +10,10 @@ module ProfileHelper
end
def show_profile_social_tab?
- enabled_social_providers.any? && !current_user.ldap_user?
+ enabled_social_providers.any?
end
def show_profile_remove_tab?
- gitlab_config.signup_enabled && !current_user.ldap_user?
+ gitlab_config.signup_enabled
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 883c1f63af6..fb5470d98e5 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -42,12 +42,12 @@ module ProjectsHelper
def project_title(project)
if project.group
content_tag :span do
- link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name
+ link_to(simple_sanitize(project.group.name), group_path(project.group)) + ' / ' + link_to(simple_sanitize(project.name), project_path(project))
end
else
owner = project.namespace.owner
content_tag :span do
- link_to(simple_sanitize(owner.name), user_path(owner)) + " / " + project.name
+ link_to(simple_sanitize(owner.name), user_path(owner)) + ' / ' + link_to(simple_sanitize(project.name), project_path(project))
end
end
end
diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb
new file mode 100644
index 00000000000..492e065b713
--- /dev/null
+++ b/app/helpers/sorting_helper.rb
@@ -0,0 +1,17 @@
+module SortingHelper
+ def sort_title_oldest_updated
+ 'Oldest updated'
+ end
+
+ def sort_title_recently_updated
+ 'Recently updated'
+ end
+
+ def sort_title_oldest_created
+ 'Oldest created'
+ end
+
+ def sort_title_recently_created
+ 'Recently created'
+ end
+end
diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb
index 9c611a1c147..8e209498323 100644
--- a/app/helpers/tree_helper.rb
+++ b/app/helpers/tree_helper.rb
@@ -66,7 +66,7 @@ module TreeHelper
def tree_breadcrumbs(tree, max_links = 2)
if @path.present?
part_path = ""
- parts = @path.split("\/")
+ parts = @path.split('/')
yield('..', nil) if parts.count > max_links
diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb
index f8a7d133d1d..6d7f8eb4b02 100644
--- a/app/mailers/emails/profile.rb
+++ b/app/mailers/emails/profile.rb
@@ -1,8 +1,7 @@
module Emails
module Profile
- def new_user_email(user_id, password, token = nil)
+ def new_user_email(user_id, token = nil)
@user = User.find(user_id)
- @password = password
@target_url = user_url(@user)
@token = token
mail(to: @user.email, subject: subject("Account was created for you"))
diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb
index bd438bab89a..6d671e6e0bd 100644
--- a/app/mailers/notify.rb
+++ b/app/mailers/notify.rb
@@ -11,6 +11,7 @@ class Notify < ActionMailer::Base
add_template_helper ApplicationHelper
add_template_helper GitlabMarkdownHelper
add_template_helper MergeRequestsHelper
+ add_template_helper EmailsHelper
default_url_options[:host] = Gitlab.config.gitlab.host
default_url_options[:protocol] = Gitlab.config.gitlab.protocol
@@ -25,6 +26,14 @@ class Notify < ActionMailer::Base
delay_for(2.seconds)
end
+ def test_email(recepient_email, subject, body)
+ mail(to: recepient_email,
+ subject: subject,
+ body: body.html_safe,
+ content_type: 'text/html'
+ )
+ end
+
private
# The default email address to send emails from
diff --git a/app/models/ability.rb b/app/models/ability.rb
index e155abc1449..97a72bf3635 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -262,5 +262,13 @@ class Ability
end
rules
end
+
+ def abilities
+ @abilities ||= begin
+ abilities = Six.new
+ abilities << self
+ abilities
+ end
+ end
end
end
diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb
index 553087946d6..f49708fd6eb 100644
--- a/app/models/concerns/issuable.rb
+++ b/app/models/concerns/issuable.rb
@@ -131,9 +131,10 @@ module Issuable
users.concat(mentions.reduce([], :|)).uniq
end
- def to_hook_data
+ def to_hook_data(user)
{
object_kind: self.class.name.underscore,
+ user: user.hook_attrs,
object_attributes: hook_attrs
}
end
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 5938d9cb28e..6c1aa99668a 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -52,11 +52,7 @@ module Mentionable
if identifier == "all"
users += project.team.members.flatten
else
- if has_project
- id = project.team.members.find_by(username: identifier).try(:id)
- else
- id = User.find_by(username: identifier).try(:id)
- end
+ id = User.find_by(username: identifier).try(:id)
users << User.find(id) unless id.blank?
end
end
diff --git a/app/models/event.rb b/app/models/event.rb
index c0b126713a6..65b4c2edfee 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -186,10 +186,6 @@ class Event < ActiveRecord::Base
data[:ref]["refs/heads"]
end
- def new_branch?
- commit_from =~ /^00000/
- end
-
def new_ref?
commit_from =~ /^00000/
end
diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb
index 23fa01e0b70..8479d4aecf6 100644
--- a/app/models/hooks/web_hook.rb
+++ b/app/models/hooks/web_hook.rb
@@ -32,7 +32,10 @@ class WebHook < ActiveRecord::Base
def execute(data)
parsed_url = URI.parse(url)
if parsed_url.userinfo.blank?
- WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false)
+ WebHook.post(url,
+ body: data.to_json,
+ headers: { "Content-Type" => "application/json" },
+ verify: false)
else
post_url = url.gsub("#{parsed_url.userinfo}@", "")
auth = {
@@ -45,6 +48,9 @@ class WebHook < ActiveRecord::Base
verify: false,
basic_auth: auth)
end
+ rescue SocketError, Errno::ECONNREFUSED => e
+ logger.error("WebHook Error => #{e}")
+ false
end
def async_execute(data)
diff --git a/app/models/identity.rb b/app/models/identity.rb
new file mode 100644
index 00000000000..5fb1850c30b
--- /dev/null
+++ b/app/models/identity.rb
@@ -0,0 +1,5 @@
+class Identity < ActiveRecord::Base
+ belongs_to :user
+
+ validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
+end \ No newline at end of file
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 7c525b02f48..2cc427d35c2 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -70,6 +70,16 @@ class MergeRequest < ActiveRecord::Base
transition locked: :reopened
end
+ after_transition any => :locked do |merge_request, transition|
+ merge_request.locked_at = Time.now
+ merge_request.save
+ end
+
+ after_transition :locked => (any - :locked) do |merge_request, transition|
+ merge_request.locked_at = nil
+ merge_request.save
+ end
+
state :opened
state :reopened
state :closed
@@ -336,4 +346,8 @@ class MergeRequest < ActiveRecord::Base
source_project.repository.branch_names
end
end
+
+ def locked_long_ago?
+ locked_at && locked_at < (Time.now - 1.day)
+ end
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index c0c6de0ee7d..ea4b48fdd7f 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -90,4 +90,8 @@ class Namespace < ActiveRecord::Base
def kind
type == 'Group' ? 'group' : 'user'
end
+
+ def find_fork_of(project)
+ projects.joins(:forked_project_link).where('forked_project_links.forked_from_project_id = ?', project.id).first
+ end
end
diff --git a/app/models/note.rb b/app/models/note.rb
index 6f1b1a4da94..5bf645bbd1d 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -80,7 +80,7 @@ class Note < ActiveRecord::Base
note_options = {
project: project,
author: author,
- note: "_mentioned in #{gfm_reference}_",
+ note: cross_reference_note_content(gfm_reference),
system: true
}
@@ -90,7 +90,7 @@ class Note < ActiveRecord::Base
note_options.merge!(noteable: noteable)
end
- create(note_options)
+ create(note_options) unless cross_reference_disallowed?(noteable, mentioner)
end
def create_milestone_change_note(noteable, project, author, milestone)
@@ -165,6 +165,15 @@ class Note < ActiveRecord::Base
[:discussion, type.try(:underscore), id, line_code].join("-").to_sym
end
+ # Determine if cross reference note should be created.
+ # eg. mentioning a commit in MR comments which exists inside a MR
+ # should not create "mentioned in" note.
+ def cross_reference_disallowed?(noteable, mentioner)
+ if mentioner.kind_of?(MergeRequest)
+ mentioner.commits.map(&:id).include? noteable.id
+ end
+ end
+
# Determine whether or not a cross-reference note already exists.
def cross_reference_exists?(noteable, mentioner)
gfm_reference = mentioner_gfm_ref(noteable, mentioner)
@@ -174,7 +183,7 @@ class Note < ActiveRecord::Base
where(noteable_id: noteable.id)
end
- notes.where('note like ?', "_mentioned in #{gfm_reference}_").
+ notes.where('note like ?', cross_reference_note_content(gfm_reference)).
system.any?
end
@@ -182,8 +191,16 @@ class Note < ActiveRecord::Base
where("note like :query", query: "%#{query}%")
end
+ def cross_reference_note_prefix
+ '_mentioned in '
+ end
+
private
+ def cross_reference_note_content(gfm_reference)
+ cross_reference_note_prefix + "#{gfm_reference}_"
+ end
+
# Prepend the mentioner's namespaced project path to the GFM reference for
# cross-project references. For same-project references, return the
# unmodified GFM reference.
@@ -243,12 +260,16 @@ class Note < ActiveRecord::Base
def commit_author
@commit_author ||=
- project.users.find_by(email: noteable.author_email) ||
- project.users.find_by(name: noteable.author_name)
+ project.team.users.find_by(email: noteable.author_email) ||
+ project.team.users.find_by(name: noteable.author_name)
rescue
nil
end
+ def cross_reference?
+ note.start_with?(self.class.cross_reference_note_prefix)
+ end
+
def find_diff
return nil unless noteable && noteable.diffs.present?
@@ -296,7 +317,7 @@ class Note < ActiveRecord::Base
end
def diff_file_index
- line_code.split('_')[0]
+ line_code.split('_')[0] if line_code
end
def diff_file_name
@@ -312,11 +333,11 @@ class Note < ActiveRecord::Base
end
def diff_old_line
- line_code.split('_')[1].to_i
+ line_code.split('_')[1].to_i if line_code
end
def diff_new_line
- line_code.split('_')[2].to_i
+ line_code.split('_')[2].to_i if line_code
end
def generate_line_code(line)
@@ -337,6 +358,20 @@ class Note < ActiveRecord::Base
@diff_line
end
+ def diff_line_type
+ return @diff_line_type if @diff_line_type
+
+ if diff
+ diff_lines.each do |line|
+ if generate_line_code(line) == self.line_code
+ @diff_line_type = line.type
+ end
+ end
+ end
+
+ @diff_line_type
+ end
+
def truncated_diff_lines
max_number_of_lines = 16
prev_match_line = nil
diff --git a/app/models/project.rb b/app/models/project.rb
index 90d2649ba23..32b0145ca24 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -65,6 +65,7 @@ class Project < ActiveRecord::Base
has_one :gemnasium_service, dependent: :destroy
has_one :slack_service, dependent: :destroy
has_one :buildbox_service, dependent: :destroy
+ has_one :bamboo_service, dependent: :destroy
has_one :pushover_service, dependent: :destroy
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
has_one :forked_from_project, through: :forked_project_link
@@ -135,7 +136,7 @@ class Project < ActiveRecord::Base
state_machine :import_status, initial: :none do
event :import_start do
- transition :none => :started
+ transition [:none, :finished] => :started
end
event :import_finish do
@@ -173,7 +174,7 @@ class Project < ActiveRecord::Base
end
def with_push
- includes(:events).where('events.action = ?', Event::PUSHED)
+ joins(:events).where('events.action = ?', Event::PUSHED)
end
def active
@@ -313,7 +314,7 @@ class Project < ActiveRecord::Base
end
def available_services_names
- %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover buildbox)
+ %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack pushover buildbox bamboo)
end
def gitlab_ci?
@@ -389,55 +390,14 @@ class Project < ActiveRecord::Base
end
def execute_services(data)
- services.each do |service|
-
- # Call service hook only if it is active
- begin
- service.execute(data) if service.active
- rescue => e
- logger.error(e)
- end
+ services.select(&:active).each do |service|
+ service.async_execute(data)
end
end
def update_merge_requests(oldrev, newrev, ref, user)
- return true unless ref =~ /heads/
- branch_name = ref.gsub("refs/heads/", "")
- commits = self.repository.commits_between(oldrev, newrev)
- c_ids = commits.map(&:id)
-
- # Close merge requests
- mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a
- mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
-
- mrs.uniq.each do |merge_request|
- MergeRequests::MergeService.new.execute(merge_request, user, nil)
- end
-
- # Update code for merge requests into project between project branches
- mrs = self.merge_requests.opened.by_branch(branch_name).to_a
- # Update code for merge requests between project and project fork
- mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a
-
- mrs.uniq.each do |merge_request|
- merge_request.reload_code
- merge_request.mark_as_unchecked
- end
-
- # Add comment about pushing new commits to merge requests
- comment_mr_with_commits(branch_name, commits, user)
-
- true
- end
-
- def comment_mr_with_commits(branch_name, commits, user)
- mrs = self.origin_merge_requests.opened.where(source_branch: branch_name).to_a
- mrs += self.fork_merge_requests.opened.where(source_branch: branch_name).to_a
-
- mrs.uniq.each do |merge_request|
- Note.create_new_commits_note(merge_request, merge_request.project,
- user, commits)
- end
+ MergeRequests::RefreshService.new(self, user).
+ execute(oldrev, newrev, ref)
end
def valid_repo?
@@ -620,4 +580,25 @@ class Project < ActiveRecord::Base
def origin_merge_requests
merge_requests.where(source_project_id: self.id)
end
+
+ def create_repository
+ if gitlab_shell.add_repository(path_with_namespace)
+ true
+ else
+ errors.add(:base, "Failed to create repository")
+ false
+ end
+ end
+
+ def repository_exists?
+ !!repository.exists?
+ end
+
+ def create_wiki
+ ProjectWiki.new(self, self.owner).wiki
+ true
+ rescue ProjectWiki::CouldNotCreateWikiError => ex
+ errors.add(:base, "Failed create wiki")
+ false
+ end
end
diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb
new file mode 100644
index 00000000000..b9eec9ab21e
--- /dev/null
+++ b/app/models/project_services/bamboo_service.rb
@@ -0,0 +1,105 @@
+class BambooService < CiService
+ include HTTParty
+
+ prop_accessor :bamboo_url, :build_key, :username, :password
+
+ validates :bamboo_url, presence: true,
+ format: { with: URI::regexp }, if: :activated?
+ validates :build_key, presence: true, if: :activated?
+ validates :username, presence: true,
+ if: ->(service) { service.password? }, if: :activated?
+ validates :password, presence: true,
+ if: ->(service) { service.username? }, if: :activated?
+
+ attr_accessor :response
+
+ after_save :compose_service_hook, if: :activated?
+
+ def compose_service_hook
+ hook = service_hook || build_service_hook
+ hook.save
+ end
+
+ def title
+ 'Atlassian Bamboo CI'
+ end
+
+ def description
+ 'A continuous integration and build server'
+ end
+
+ def help
+ 'You must set up automatic revision labeling and a repository trigger in Bamboo.'
+ end
+
+ def to_param
+ 'bamboo'
+ end
+
+ def fields
+ [
+ { type: 'text', name: 'bamboo_url',
+ placeholder: 'Bamboo root URL like https://bamboo.example.com' },
+ { type: 'text', name: 'build_key',
+ placeholder: 'Bamboo build plan key like KEY' },
+ { type: 'text', name: 'username',
+ placeholder: 'A user with API access, if applicable' },
+ { type: 'password', name: 'password' },
+ ]
+ end
+
+ def build_info(sha)
+ url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}")
+
+ if username.blank? && password.blank?
+ @response = HTTParty.get(parsed_url.to_s, verify: false)
+ else
+ get_url = "#{url}&os_authType=basic"
+ auth = {
+ username: username,
+ password: password,
+ }
+ @response = HTTParty.get(get_url, verify: false, basic_auth: auth)
+ end
+ end
+
+ def build_page(sha)
+ build_info(sha) if @response.nil? || !@response.code
+
+ if @response.code != 200 || @response['results']['results']['size'] == '0'
+ # If actual build link can't be determined, send user to build summary page.
+ "#{bamboo_url}/browse/#{build_key}"
+ else
+ # If actual build link is available, go to build result page.
+ result_key = @response['results']['results']['result']['planResultKey']['key']
+ "#{bamboo_url}/browse/#{result_key}"
+ end
+ end
+
+ def commit_status(sha)
+ build_info(sha) if @response.nil? || !@response.code
+ return :error unless @response.code == 200 || @response.code == 404
+
+ status = if @response.code == 404 || @response['results']['results']['size'] == '0'
+ 'Pending'
+ else
+ @response['results']['results']['result']['buildState']
+ end
+
+ if status.include?('Success')
+ 'success'
+ elsif status.include?('Failed')
+ 'failed'
+ elsif status.include?('Pending')
+ 'pending'
+ else
+ :error
+ end
+ end
+
+ def execute(_data)
+ # Bamboo requires a GET and does not take any data.
+ self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}",
+ verify: false)
+ end
+end
diff --git a/app/models/project_services/buildbox_service.rb b/app/models/project_services/buildbox_service.rb
index b0f8e28c97f..0ab67b79fe4 100644
--- a/app/models/project_services/buildbox_service.rb
+++ b/app/models/project_services/buildbox_service.rb
@@ -12,6 +12,8 @@
# properties :text
#
+require "addressable/uri"
+
class BuildboxService < CiService
prop_accessor :project_url, :token
diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb
index 0020b4482e5..86705f5dabd 100644
--- a/app/models/project_services/flowdock_service.rb
+++ b/app/models/project_services/flowdock_service.rb
@@ -37,13 +37,12 @@ class FlowdockService < Service
end
def execute(push_data)
- repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
Flowdock::Git.post(
push_data[:ref],
push_data[:before],
push_data[:after],
token: token,
- repo: repo_path,
+ repo: project.repository.path_to_repo,
repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}",
commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s",
diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s",
diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb
index 6d2fc06a5d0..18fdd204ecd 100644
--- a/app/models/project_services/gemnasium_service.rb
+++ b/app/models/project_services/gemnasium_service.rb
@@ -38,14 +38,13 @@ class GemnasiumService < Service
end
def execute(push_data)
- repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
Gemnasium::GitlabService.execute(
ref: push_data[:ref],
before: push_data[:before],
after: push_data[:after],
token: token,
api_key: api_key,
- repo: repo_path
+ repo: project.repository.path_to_repo
)
end
end
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index a897c4ab76b..fadebf968bc 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -28,7 +28,7 @@ class GitlabCiService < CiService
end
def commit_status_path(sha)
- project_url + "/builds/#{sha}/status.json?token=#{token}"
+ project_url + "/commits/#{sha}/status.json?token=#{token}"
end
def get_ci_build(sha)
@@ -55,7 +55,7 @@ class GitlabCiService < CiService
end
def build_page(sha)
- project_url + "/builds/#{sha}"
+ project_url + "/commits/#{sha}"
end
def builds_path
diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb
index 4078938cdbb..a848d74044c 100644
--- a/app/models/project_services/hipchat_service.rb
+++ b/app/models/project_services/hipchat_service.rb
@@ -15,11 +15,11 @@
class HipchatService < Service
MAX_COMMITS = 3
- prop_accessor :token, :room
+ prop_accessor :token, :room, :server
validates :token, presence: true, if: :activated?
def title
- 'Hipchat'
+ 'HipChat'
end
def description
@@ -33,7 +33,9 @@ class HipchatService < Service
def fields
[
{ type: 'text', name: 'token', placeholder: '' },
- { type: 'text', name: 'room', placeholder: '' }
+ { type: 'text', name: 'room', placeholder: '' },
+ { type: 'text', name: 'server',
+ placeholder: 'Leave blank for default. https://chat.hipchat.com' }
]
end
@@ -44,7 +46,9 @@ class HipchatService < Service
private
def gate
- @gate ||= HipChat::Client.new(token)
+ options = { api_version: 'v2' }
+ options[:server_url] = server unless server.nil?
+ @gate ||= HipChat::Client.new(token, options)
end
def create_message(push)
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index 95f3ddcef45..963f5440b6f 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -30,23 +30,20 @@ class SlackService < Service
def fields
[
- { type: 'text', name: 'webhook', placeholder: '' }
+ { type: 'text', name: 'webhook', placeholder: 'https://hooks.slack.com/services/...' }
]
end
def execute(push_data)
+ return unless webhook.present?
+
message = SlackMessage.new(push_data.merge(
project_url: project_url,
project_name: project_name
))
- credentials = webhook.match(/(\w*).slack.com.*services\/(.*)/)
- if credentials.present?
- subdomain = credentials[1]
- token = credentials[2].split("token=").last
- notifier = Slack::Notifier.new(subdomain, token)
- notifier.ping(message.pretext, attachments: message.attachments)
- end
+ notifier = Slack::Notifier.new(webhook)
+ notifier.ping(message.pretext, attachments: message.attachments)
end
private
diff --git a/app/models/service.rb b/app/models/service.rb
index c489c1e96e1..71c8aa39e45 100644
--- a/app/models/service.rb
+++ b/app/models/service.rb
@@ -82,4 +82,8 @@ class Service < ActiveRecord::Base
}
end
end
+
+ def async_execute(data)
+ Sidekiq::Client.enqueue(ProjectServiceWorker, id, data)
+ end
end
diff --git a/app/models/user.rb b/app/models/user.rb
index 42faea0070e..7faeef1b5b0 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -79,6 +79,7 @@ class User < ActiveRecord::Base
# Profile
has_many :keys, dependent: :destroy
has_many :emails, dependent: :destroy
+ has_many :identities, dependent: :destroy
# Groups
has_many :members, dependent: :destroy
@@ -113,7 +114,6 @@ class User < ActiveRecord::Base
validates :name, presence: true
validates :email, presence: true, email: {strict_mode: true}, uniqueness: true
validates :bio, length: { maximum: 255 }, allow_blank: true
- validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0}
validates :username, presence: true, uniqueness: { case_sensitive: false },
exclusion: { in: Gitlab::Blacklist.path },
@@ -124,7 +124,7 @@ class User < ActiveRecord::Base
validate :namespace_uniq, if: ->(user) { user.username_changed? }
validate :avatar_type, if: ->(user) { user.avatar_changed? }
validate :unique_email, if: ->(user) { user.email_changed? }
- validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
+ validates :avatar, file_size: { maximum: 200.kilobytes.to_i }
before_validation :generate_password, on: :create
before_validation :sanitize_attrs
@@ -178,7 +178,6 @@ class User < ActiveRecord::Base
scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') }
- scope :ldap, -> { where('provider LIKE ?', 'ldap%') }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
#
@@ -226,6 +225,11 @@ class User < ActiveRecord::Base
where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%")
end
+ def by_login(login)
+ where('lower(username) = :value OR lower(email) = :value',
+ value: login.to_s.downcase).first
+ end
+
def by_username_or_id(name_or_id)
where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first
end
@@ -330,11 +334,7 @@ class User < ActiveRecord::Base
end
def abilities
- @abilities ||= begin
- abilities = Six.new
- abilities << Ability
- abilities
- end
+ Ability.abilities
end
def can_select_namespace?
@@ -406,7 +406,11 @@ class User < ActiveRecord::Base
end
def ldap_user?
- extern_uid && provider.start_with?('ldap')
+ identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"])
+ end
+
+ def ldap_identity
+ @ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"])
end
def accessible_deploy_keys
@@ -497,6 +501,14 @@ class User < ActiveRecord::Base
end
end
+ def hook_attrs
+ {
+ name: name,
+ username: username,
+ avatar_url: avatar_url
+ }
+ end
+
def ensure_namespace_correct
# Ensure user has namespace
self.create_namespace!(path: self.username, name: self.username) unless self.namespace
@@ -542,4 +554,14 @@ class User < ActiveRecord::Base
UsersStarProject.create!(project: project, user: self)
end
end
+
+ def manageable_namespaces
+ @manageable_namespaces ||=
+ begin
+ namespaces = []
+ namespaces << namespace
+ namespaces += owned_groups
+ namespaces += masters_groups
+ end
+ end
end
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
index ed286c04095..0d46eeaa18f 100644
--- a/app/services/base_service.rb
+++ b/app/services/base_service.rb
@@ -6,11 +6,7 @@ class BaseService
end
def abilities
- @abilities ||= begin
- abilities = Six.new
- abilities << Ability
- abilities
- end
+ Ability.abilities
end
def can?(object, action, subject)
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index db6f0831f8b..bd245100955 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -10,12 +10,6 @@ module Files
private
- def success
- out = super()
- out[:error] = ''
- out
- end
-
def repository
project.repository
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 8f2b0e347f6..529af1970f6 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -83,9 +83,14 @@ class GitPushService
# closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to
# a different branch.
issues_to_close = commit.closes_issues(project)
- author = commit_user(commit)
- if !issues_to_close.empty? && is_default_branch
+ # Load commit author only if needed.
+ # For push with 1k commits it prevents 900+ requests in database
+ author = nil
+
+ if issues_to_close.present? && is_default_branch
+ author ||= commit_user(commit)
+
issues_to_close.each do |issue|
Issues::CloseService.new(project, author, {}).execute(issue, commit)
end
@@ -96,8 +101,13 @@ class GitPushService
# being pushed to a different branch).
refs = commit.references(project) - issues_to_close
refs.reject! { |r| commit.has_mentioned?(r) }
- refs.each do |r|
- Note.create_cross_reference_note(r, commit, author, project)
+
+ if refs.present?
+ author ||= commit_user(commit)
+
+ refs.each do |r|
+ Note.create_cross_reference_note(r, commit, author, project)
+ end
end
end
end
@@ -160,19 +170,19 @@ class GitPushService
ref_parts = ref.split('/')
# Return if this is not a push to a branch (e.g. new commits)
- ref_parts[1] =~ /heads/ && oldrev != "0000000000000000000000000000000000000000"
+ ref_parts[1] =~ /heads/ && oldrev != Gitlab::Git::BLANK_SHA
end
def push_to_new_branch?(ref, oldrev)
ref_parts = ref.split('/')
- ref_parts[1] =~ /heads/ && oldrev == "0000000000000000000000000000000000000000"
+ ref_parts[1] =~ /heads/ && oldrev == Gitlab::Git::BLANK_SHA
end
def push_remove_branch?(ref, newrev)
ref_parts = ref.split('/')
- ref_parts[1] =~ /heads/ && newrev == "0000000000000000000000000000000000000000"
+ ref_parts[1] =~ /heads/ && newrev == Gitlab::Git::BLANK_SHA
end
def push_to_branch?(ref)
diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb
index 41948f226a6..755c0ef45a8 100644
--- a/app/services/issues/base_service.rb
+++ b/app/services/issues/base_service.rb
@@ -4,7 +4,7 @@ module Issues
private
def execute_hooks(issue, action = 'open')
- issue_data = issue.to_hook_data
+ issue_data = issue.to_hook_data(current_user)
issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id)
issue_data[:object_attributes].merge!(url: issue_url, action: action)
issue.project.execute_hooks(issue_data, :issue_hooks)
diff --git a/app/services/merge_requests/base_merge_service.rb b/app/services/merge_requests/base_merge_service.rb
index 9bc50d3d16c..700a21ca011 100644
--- a/app/services/merge_requests/base_merge_service.rb
+++ b/app/services/merge_requests/base_merge_service.rb
@@ -13,7 +13,8 @@ module MergeRequests
def execute_project_hooks(merge_request)
if merge_request.project
- merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
+ hook_data = merge_request.to_hook_data(current_user)
+ merge_request.project.execute_hooks(hook_data, :merge_request_hooks)
end
end
end
diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb
index 694994001b0..7f3421b8e4b 100644
--- a/app/services/merge_requests/base_service.rb
+++ b/app/services/merge_requests/base_service.rb
@@ -7,7 +7,8 @@ module MergeRequests
def execute_hooks(merge_request)
if merge_request.project
- merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks)
+ hook_data = merge_request.to_hook_data(current_user)
+ merge_request.project.execute_hooks(hook_data, :merge_request_hooks)
end
end
end
diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb
new file mode 100644
index 00000000000..baf0936cc3d
--- /dev/null
+++ b/app/services/merge_requests/refresh_service.rb
@@ -0,0 +1,86 @@
+module MergeRequests
+ class RefreshService < MergeRequests::BaseService
+ def execute(oldrev, newrev, ref)
+ return true unless ref =~ /heads/
+
+ @oldrev, @newrev = oldrev, newrev
+ @branch_name = ref.gsub("refs/heads/", "")
+ @fork_merge_requests = @project.fork_merge_requests.opened
+ @commits = @project.repository.commits_between(oldrev, newrev)
+
+ close_merge_requests
+ reload_merge_requests
+ comment_mr_with_commits
+
+ true
+ end
+
+ private
+
+ # Collect open merge requests that target same branch we push into
+ # and close if push to master include last commit from merge request
+ # We need this to close(as merged) merge requests that were merged into
+ # target branch manually
+ def close_merge_requests
+ commit_ids = @commits.map(&:id)
+ merge_requests = @project.merge_requests.opened.where(target_branch: @branch_name).to_a
+ merge_requests = merge_requests.select(&:last_commit)
+
+ merge_requests = merge_requests.select do |merge_request|
+ commit_ids.include?(merge_request.last_commit.id)
+ end
+
+
+ merge_requests.uniq.select(&:source_project).each do |merge_request|
+ MergeRequests::MergeService.new.execute(merge_request, @current_user, nil)
+ end
+ end
+
+ def force_push?
+ Gitlab::ForcePushCheck.force_push?(@project, @oldrev, @newrev)
+ end
+
+ # Refresh merge request diff if we push to source or target branch of merge request
+ # Note: we should update merge requests from forks too
+ def reload_merge_requests
+ merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a
+ merge_requests += @fork_merge_requests.by_branch(@branch_name).to_a
+ merge_requests = filter_merge_requests(merge_requests)
+
+ merge_requests.each do |merge_request|
+
+ if merge_request.source_branch == @branch_name || force_push?
+ merge_request.reload_code
+ merge_request.mark_as_unchecked
+ else
+ mr_commit_ids = merge_request.commits.map(&:id)
+ push_commit_ids = @commits.map(&:id)
+ matches = mr_commit_ids & push_commit_ids
+
+ if matches.any?
+ merge_request.reload_code
+ merge_request.mark_as_unchecked
+ else
+ merge_request.mark_as_unchecked
+ end
+ end
+ end
+ end
+
+ # Add comment about pushing new commits to merge requests
+ def comment_mr_with_commits
+ merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a
+ merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a
+ merge_requests = filter_merge_requests(merge_requests)
+
+ merge_requests.each do |merge_request|
+ Note.create_new_commits_note(merge_request, merge_request.project,
+ @current_user, @commits)
+ end
+ end
+
+ def filter_merge_requests(merge_requests)
+ merge_requests.uniq.select(&:source_project)
+ end
+ end
+end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index fe39f83b400..d1aadd741e1 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -107,7 +107,7 @@ class NotificationService
# Notify new user with email after creation
def new_user(user, token = nil)
# Don't email omniauth created users
- mailer.new_user_email(user.id, user.password, token) unless user.extern_uid?
+ mailer.new_user_email(user.id, token) unless user.identities.any?
end
# Notify users on new note in system
@@ -119,11 +119,12 @@ class NotificationService
# ignore gitlab service messages
return true if note.note =~ /\A_Status changed to closed_/
- return true if note.note =~ /\A_mentioned in / && note.system == true
+ return true if note.cross_reference? && note.system == true
opts = { noteable_type: note.noteable_type, project_id: note.project_id }
target = note.noteable
+
if target.respond_to?(:participants)
recipients = target.participants
else
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 12386792aab..3672b623806 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -37,35 +37,22 @@ module Projects
@project.creator = current_user
- if @project.save
- log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"")
- system_hook_service.execute_hooks_for(@project, :create)
+ Project.transaction do
+ @project.save
- unless @project.group
- @project.team << [current_user, :master]
- end
-
- @project.update_column(:last_activity_at, @project.created_at)
-
- if @project.import?
- @project.import_start
- else
- GitlabShellWorker.perform_async(
- :add_repository,
- @project.path_with_namespace
- )
+ unless @project.import?
+ unless @project.create_repository
+ raise 'Failed to create repository'
+ end
end
+ end
+ if @project.persisted?
if @project.wiki_enabled?
- begin
- # force the creation of a wiki,
- ProjectWiki.new(@project, @project.owner).wiki
- rescue ProjectWiki::CouldNotCreateWikiError => ex
- # Prevent project observer crash
- # if failed to create wiki
- nil
- end
+ @project.create_wiki
end
+
+ after_create_actions
end
@project
@@ -84,5 +71,20 @@ module Projects
namespace = Namespace.find_by(id: namespace_id)
current_user.can?(:create_projects, namespace)
end
+
+ def after_create_actions
+ log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"")
+ system_hook_service.execute_hooks_for(@project, :create)
+
+ unless @project.group
+ @project.team << [current_user, :master]
+ end
+
+ @project.update_column(:last_activity_at, @project.created_at)
+
+ if @project.import?
+ @project.import_start
+ end
+ end
end
end
diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb
index a59311bf942..4930660055a 100644
--- a/app/services/projects/fork_service.rb
+++ b/app/services/projects/fork_service.rb
@@ -2,11 +2,9 @@ module Projects
class ForkService < BaseService
include Gitlab::ShellAdapter
- def initialize(project, user)
- @from_project, @current_user = project, user
- end
-
def execute
+ @from_project = @project
+
project_params = {
visibility_level: @from_project.visibility_level,
description: @from_project.description,
@@ -15,8 +13,18 @@ module Projects
project = Project.new(project_params)
project.name = @from_project.name
project.path = @from_project.path
- project.namespace = current_user.namespace
- project.creator = current_user
+ project.creator = @current_user
+
+ if namespace = @params[:namespace]
+ project.namespace = namespace
+ else
+ project.namespace = @current_user.namespace
+ end
+
+ unless @current_user.can?(:create_projects, project.namespace)
+ project.errors.add(:namespace, 'insufficient access rights')
+ return project
+ end
# If the project cannot save, we do not want to trigger the project destroy
# as this can have the side effect of deleting a repo attached to an existing
@@ -27,7 +35,7 @@ module Projects
#First save the DB entries as they can be rolled back if the repo fork fails
project.build_forked_project_link(forked_to_project_id: project.id, forked_from_project_id: @from_project.id)
if project.save
- project.team << [current_user, :master]
+ project.team << [@current_user, :master]
end
#Now fork the repo
unless gitlab_shell.fork_repository(@from_project.path_with_namespace, project.namespace.path)
@@ -42,8 +50,8 @@ module Projects
else
project.errors.add(:base, "Invalid fork destination")
end
- project
+ project
end
end
end
diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb
index a6b68749a71..44e494525b3 100644
--- a/app/services/system_hooks_service.rb
+++ b/app/services/system_hooks_service.rb
@@ -18,7 +18,7 @@ class SystemHooksService
def build_event_data(model, event)
data = {
event_name: build_event_name(model, event),
- created_at: model.created_at
+ created_at: model.created_at.xmlschema
}
case model
diff --git a/app/services/test_hook_service.rb b/app/services/test_hook_service.rb
index b6b1ef29b51..17d86a7a274 100644
--- a/app/services/test_hook_service.rb
+++ b/app/services/test_hook_service.rb
@@ -2,8 +2,5 @@ class TestHookService
def execute(hook, current_user)
data = GitPushService.new.sample_data(hook.project, current_user)
hook.execute(data)
- true
- rescue SocketError
- false
end
end
diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml
index 9dcf7b488ee..8db2b2a709c 100644
--- a/app/views/admin/background_jobs/show.html.haml
+++ b/app/views/admin/background_jobs/show.html.haml
@@ -25,7 +25,7 @@
- next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/)
- data = process.strip.split(' ')
%tr
- %td= Settings.gitlab.user
+ %td= gitlab_config.user
- 5.times do
%td= data.shift
%td= data.join(' ')
@@ -36,7 +36,7 @@
If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'.
%p
%i.fa.fa-exclamation-circle
- If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{Settings.gitlab.user} -f sidekiq) and restart GitLab.
+ If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab.
diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml
index c56863ce274..f4d7e25fd74 100644
--- a/app/views/admin/groups/_form.html.haml
+++ b/app/views/admin/groups/_form.html.haml
@@ -2,39 +2,20 @@
- if @group.errors.any?
.alert.alert-danger
%span= @group.errors.full_messages.first
- .form-group.group_name_holder
- = f.label :name, class: 'control-label' do
- Group name
- .col-sm-10
- = f.text_field :name, placeholder: "Example Group", class: "form-control"
- .form-group.group-description-holder
- = f.label :description, "Details", class: 'control-label'
- .col-sm-10
- = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
+ = render 'shared/group_form', f: f
.form-group.group-description-holder
= f.label :avatar, "Group avatar", class: 'control-label'
.col-sm-10
- %a.choose-btn.btn.btn-small.js-choose-group-avatar-button
- %i.fa.fa-paperclip
- %span Choose File ...
- &nbsp;
- %span.file_name.js-avatar-filename File name...
- = f.file_field :avatar, class: "js-group-avatar-input hidden"
- .light The maximum file size allowed is 100KB.
+ = render 'shared/choose_group_avatar_button', f: f
- if @group.new_record?
.form-group
.col-sm-2
.col-sm-10
.bs-callout.bs-callout-info
- %ul
- %li A group is a collection of several projects
- %li Groups are private by default
- %li Members of a group may only view projects they have permission to access
- %li Group project URLs are prefixed with the group namespace
- %li Existing projects may be moved into a group
+ = render 'shared/group_tips'
.form-actions
= f.submit 'Create group', class: "btn btn-create"
= link_to 'Cancel', admin_groups_path, class: "btn btn-cancel"
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index 09105679bd2..1d7fef43184 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -10,7 +10,7 @@
= form_tag admin_groups_path, method: :get, class: 'form-inline' do
.form-group
= text_field_tag :name, params[:name], class: "form-control input-mn-300"
- = submit_tag "Search", class: "btn submit btn-primary"
+ = button_tag "Search", class: "btn submit btn-primary"
%hr
diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml
index c1a9214b77a..8057de38805 100644
--- a/app/views/admin/groups/show.html.haml
+++ b/app/views/admin/groups/show.html.haml
@@ -64,7 +64,7 @@
%div.prepend-top-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr
- = submit_tag 'Add users into group', class: "btn btn-create"
+ = button_tag 'Add users into group', class: "btn btn-create"
.panel.panel-default
.panel-heading
%h3.panel-title
@@ -74,13 +74,13 @@
%ul.well-list.group-users-list
- @members.each do |member|
- user = member.user
- %li{class: dom_class(user)}
+ %li{class: dom_class(member), id: dom_id(user)}
.list-item-name
%strong
= link_to user.name, admin_user_path(user)
%span.pull-right.light
= member.human_access
- = link_to group_group_members_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
+ = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-minus.fa-inverse
.panel-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab'
diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml
index b3f8f012f00..384c6ee9af5 100644
--- a/app/views/admin/logs/show.html.haml
+++ b/app/views/admin/logs/show.html.haml
@@ -1,68 +1,25 @@
+- loggers = [Gitlab::GitLogger, Gitlab::AppLogger,
+ Gitlab::ProductionLogger, Gitlab::SidekiqLogger]
%ul.nav.nav-tabs.log-tabs
- %li.active
- = link_to "githost.log", "#githost", 'data-toggle' => 'tab'
- %li
- = link_to "application.log", "#application", 'data-toggle' => 'tab'
- %li
- = link_to "production.log", "#production", 'data-toggle' => 'tab'
- %li
- = link_to "sidekiq.log", "#sidekiq", 'data-toggle' => 'tab'
-
+ - loggers.each do |klass|
+ %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') }
+ = link_to klass::file_name, "##{klass::file_name_noext}",
+ 'data-toggle' => 'tab'
%p.light To prevent performance issues admin logs output the last 2000 lines
.tab-content
- .tab-pane.active#githost
- .file-holder#README
- .file-title
- %i.fa.fa-file
- githost.log
- .pull-right
- = link_to '#', class: 'log-bottom' do
- %i.fa.fa-arrow-down
- Scroll down
- .file-content.logs
- %ol
- - Gitlab::GitLogger.read_latest.each do |line|
- %li
- %p= line
- .tab-pane#application
- .file-holder#README
- .file-title
- %i.fa.fa-file
- application.log
- .pull-right
- = link_to '#', class: 'log-bottom' do
- %i.fa.fa-arrow-down
- Scroll down
- .file-content.logs
- %ol
- - Gitlab::AppLogger.read_latest.each do |line|
- %li
- %p= line
- .tab-pane#production
- .file-holder#README
- .file-title
- %i.fa.fa-file
- production.log
- .pull-right
- = link_to '#', class: 'log-bottom' do
- %i.fa.fa-arrow-down
- Scroll down
- .file-content.logs
- %ol
- - Gitlab::Logger.read_latest_for('production.log').each do |line|
- %li
- %p= line
- .tab-pane#sidekiq
- .file-holder#README
- .file-title
- %i.fa.fa-file
- sidekiq.log
- .pull-right
- = link_to '#', class: 'log-bottom' do
- %i.fa.fa-arrow-down
- Scroll down
- .file-content.logs
- %ol
- - Gitlab::Logger.read_latest_for('sidekiq.log').each do |line|
- %li
- %p= line
+ - loggers.each do |klass|
+ .tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''),
+ id: klass::file_name_noext }
+ .file-holder#README
+ .file-title
+ %i.fa.fa-file
+ = klass::file_name
+ .pull-right
+ = link_to '#', class: 'log-bottom' do
+ %i.fa.fa-arrow-down
+ Scroll down
+ .file-content.logs
+ %ol
+ - klass.read_latest.each do |line|
+ %li
+ %p= line
diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml
index 5ca6090f8d3..aa59f38d213 100644
--- a/app/views/admin/projects/index.html.haml
+++ b/app/views/admin/projects/index.html.haml
@@ -35,7 +35,7 @@
= label
%hr
= hidden_field_tag :sort, params[:sort]
- = submit_tag "Search", class: "btn submit btn-primary"
+ = button_tag "Search", class: "btn submit btn-primary"
= link_to "Reset", admin_projects_path, class: "btn btn-cancel"
.col-md-9
@@ -56,13 +56,13 @@
= link_to admin_projects_path(sort: nil) do
Name
= link_to admin_projects_path(sort: 'newest') do
- Newest
+ = sort_title_recently_created
= link_to admin_projects_path(sort: 'oldest') do
- Oldest
+ = sort_title_oldest_created
= link_to admin_projects_path(sort: 'recently_updated') do
- Recently updated
+ = sort_title_recently_updated
= link_to admin_projects_path(sort: 'last_updated') do
- Last updated
+ = sort_title_oldest_updated
= link_to admin_projects_path(sort: 'largest_repository') do
Largest repository
= link_to 'New Project', new_project_path, class: "btn btn-new"
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index 92c619738a2..8e1ecb41a85 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -49,9 +49,10 @@
= link_to admin_users_path(sort: 'oldest_sign_in') do
Oldest sign in
= link_to admin_users_path(sort: 'recently_created') do
- Recently created
+ = sort_title_recently_created
= link_to admin_users_path(sort: 'late_created') do
- Late created
+ = sort_title_oldest_created
+
= link_to 'New User', new_admin_user_path, class: "btn btn-new"
%ul.well-list
- @users.each do |user|
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 211d77d5185..29717aedd80 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -95,7 +95,7 @@
%li
%span.light LDAP uid:
%strong
- = @user.extern_uid
+ = @user.ldap_identity.extern_uid
- if @user.created_by
%li
diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/_zero_authorized_projects.html.haml
index 711e607f0bc..5d133cd8285 100644
--- a/app/views/dashboard/_zero_authorized_projects.html.haml
+++ b/app/views/dashboard/_zero_authorized_projects.html.haml
@@ -46,5 +46,5 @@
%br
Public projects are an easy way to allow everyone to have read-only access.
.link_holder
- = link_to explore_projects_path, class: "btn btn-new" do
+ = link_to trending_explore_projects_path, class: "btn btn-new" do
Browse public projects »
diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder
index f5413557783..66381310221 100644
--- a/app/views/dashboard/issues.atom.builder
+++ b/app/views/dashboard/issues.atom.builder
@@ -1,24 +1,13 @@
xml.instruct!
-xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
+xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
xml.title "#{current_user.name} issues"
- xml.link :href => issues_dashboard_url(:atom, :private_token => current_user.private_token), :rel => "self", :type => "application/atom+xml"
- xml.link :href => issues_dashboard_url(:private_token => current_user.private_token), :rel => "alternate", :type => "text/html"
- xml.id issues_dashboard_url(:private_token => current_user.private_token)
+ xml.link href: issues_dashboard_url(:atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml"
+ xml.link href: issues_dashboard_url(private_token: current_user.private_token), rel: "alternate", type: "text/html"
+ xml.id issues_dashboard_url(private_token: current_user.private_token)
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue|
- xml.entry do
- xml.id project_issue_url(issue.project, issue)
- xml.link :href => project_issue_url(issue.project, issue)
- xml.title truncate(issue.title, :length => 80)
- xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
- xml.author do |author|
- xml.name issue.author_name
- xml.email issue.author_email
- end
- xml.summary issue.title
- end
+ issue_to_atom(xml, issue)
end
end
diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml
index f124c688be1..5b7835b097b 100644
--- a/app/views/dashboard/projects.html.haml
+++ b/app/views/dashboard/projects.html.haml
@@ -14,13 +14,14 @@
= link_to projects_dashboard_filter_path(sort: nil) do
Name
= link_to projects_dashboard_filter_path(sort: 'newest') do
- Newest
+ = sort_title_recently_created
= link_to projects_dashboard_filter_path(sort: 'oldest') do
- Oldest
+ = sort_title_oldest_created
= link_to projects_dashboard_filter_path(sort: 'recently_updated') do
- Recently updated
+ = sort_title_recently_updated
= link_to projects_dashboard_filter_path(sort: 'last_updated') do
- Last updated
+ = sort_title_oldest_updated
+
%p.light
All projects you have access to are listed here. Public projects are not included here unless you are a member
%hr
diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/show.atom.builder
index f4cf24ccd99..70ac66f8016 100644
--- a/app/views/dashboard/show.atom.builder
+++ b/app/views/dashboard/show.atom.builder
@@ -1,29 +1,12 @@
xml.instruct!
-xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
+xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}"
- xml.link :href => dashboard_url(:atom), :rel => "self", :type => "application/atom+xml"
- xml.link :href => dashboard_url, :rel => "alternate", :type => "text/html"
+ xml.link href: dashboard_url(:atom), rel: "self", type: "application/atom+xml"
+ xml.link href: dashboard_url, rel: "alternate", type: "text/html"
xml.id projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
- if event.proper?
- xml.entry do
- event_link = event_feed_url(event)
- event_title = event_feed_title(event)
- event_summary = event_feed_summary(event)
-
- xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
- xml.link :href => event_link
- xml.title truncate(event_title, :length => 80)
- xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email)
- xml.author do |author|
- xml.name event.author_name
- xml.email event.author_email
- end
- xml.summary(:type => "xhtml") { |x| x << event_summary unless event_summary.nil? }
- end
- end
+ event_to_atom(xml, event)
end
end
diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml
index 1326cc0aac9..f6cbf9b82ba 100644
--- a/app/views/devise/passwords/edit.html.haml
+++ b/app/views/devise/passwords/edit.html.haml
@@ -6,8 +6,8 @@
.devise-errors
= devise_error_messages!
= f.hidden_field :reset_password_token
- %div
- = f.password_field :password, class: "form-control top", placeholder: "New password", required: true
+ .form-group#password-strength
+ = f.password_field :password, class: "form-control top", id: "user_password_recover", placeholder: "New password", required: true
%div
= f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true
.clearfix.append-bottom-10
diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml
index d6a952f3dc5..123de881f59 100644
--- a/app/views/devise/registrations/new.html.haml
+++ b/app/views/devise/registrations/new.html.haml
@@ -11,8 +11,8 @@
= f.text_field :username, class: "form-control middle", placeholder: "Username", required: true
%div
= f.email_field :email, class: "form-control middle", placeholder: "Email", required: true
- %div
- = f.password_field :password, class: "form-control middle", placeholder: "Password", required: true
+ .form-group#password-strength
+ = f.password_field :password, class: "form-control middle", id: "user_password_sign_up", placeholder: "Password", required: true
%div
= f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm password", required: true
%div
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index 01584611493..bf8a593c254 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -2,4 +2,4 @@
= text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
%br/
- = submit_tag "LDAP Sign in", class: "btn-save btn"
+ = button_tag "LDAP Sign in", class: "btn-save btn"
diff --git a/app/views/devise/sessions/_oauth_providers.html.haml b/app/views/devise/sessions/_oauth_providers.html.haml
index 15048a78063..8d6aaefb9ff 100644
--- a/app/views/devise/sessions/_oauth_providers.html.haml
+++ b/app/views/devise/sessions/_oauth_providers.html.haml
@@ -1,4 +1,4 @@
-- providers = (enabled_oauth_providers - [:ldap])
+- providers = additional_providers
- if providers.present?
.bs-callout.bs-callout-info{:'data-no-turbolink' => 'data-no-turbolink'}
%span Sign in with: &nbsp;
diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml
index b9832787446..ca7e9570b43 100644
--- a/app/views/devise/sessions/new.html.haml
+++ b/app/views/devise/sessions/new.html.haml
@@ -2,22 +2,22 @@
.login-heading
%h3 Sign in
.login-body
- - if ldap_enabled? && gitlab_config.signin_enabled
+ - if ldap_enabled?
%ul.nav.nav-tabs
- @ldap_servers.each_with_index do |server, i|
- %li{class: (:active if i==0)}
+ %li{class: (:active if i.zero?)}
= link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab'
- %li
- = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
+ - if gitlab_config.signin_enabled
+ %li
+ = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab'
.tab-content
- - @ldap_servers.each_with_index do |server,i|
- %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i==0)}
+ - @ldap_servers.each_with_index do |server, i|
+ %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)}
= render 'devise/sessions/new_ldap', provider: server['provider_name']
- %div#tab-signin.tab-pane
- = render 'devise/sessions/new_base'
+ - if gitlab_config.signin_enabled
+ %div#tab-signin.tab-pane
+ = render 'devise/sessions/new_base'
- - elsif ldap_enabled?
- = render 'devise/sessions/new_ldap', ldap_servers: @ldap_servers
- elsif gitlab_config.signin_enabled
= render 'devise/sessions/new_base'
- else
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index c8243ff782c..9b1d7d0416d 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -4,7 +4,7 @@
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "groups_search"
.form-group
- = submit_tag 'Search', class: "btn btn-primary wide"
+ = button_tag 'Search', class: "btn btn-primary wide"
.pull-right
.dropdown.inline
@@ -20,13 +20,13 @@
= link_to explore_groups_path(sort: nil) do
Name
= link_to explore_groups_path(sort: 'newest') do
- Newest
+ = sort_title_recently_created
= link_to explore_groups_path(sort: 'oldest') do
- Oldest
+ = sort_title_oldest_created
= link_to explore_groups_path(sort: 'recently_updated') do
- Recently updated
+ = sort_title_recently_updated
= link_to explore_groups_path(sort: 'last_updated') do
- Last updated
+ = sort_title_oldest_updated
%hr
diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml
index c8bf78385e8..02586077d8c 100644
--- a/app/views/explore/projects/index.html.haml
+++ b/app/views/explore/projects/index.html.haml
@@ -4,7 +4,7 @@
.form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search"
.form-group
- = submit_tag 'Search', class: "btn btn-primary wide"
+ = button_tag 'Search', class: "btn btn-primary wide"
.pull-right
.dropdown.inline
@@ -20,13 +20,13 @@
= link_to explore_projects_path(sort: nil) do
Name
= link_to explore_projects_path(sort: 'newest') do
- Newest
+ = sort_title_recently_created
= link_to explore_projects_path(sort: 'oldest') do
- Oldest
+ = sort_title_oldest_created
= link_to explore_projects_path(sort: 'recently_updated') do
- Recently updated
+ = sort_title_recently_updated
= link_to explore_projects_path(sort: 'last_updated') do
- Last updated
+ = sort_title_oldest_updated
%hr
.public-projects
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index 0b15affe785..eb24fd65d9e 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -11,16 +11,7 @@
- if @group.errors.any?
.alert.alert-danger
%span= @group.errors.full_messages.first
- .form-group
- = f.label :name, class: 'control-label' do
- Group name
- .col-sm-10
- = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control left"
-
- .form-group.group-description-holder
- = f.label :description, "Details", class: 'control-label'
- .col-sm-10
- = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4
+ = render 'shared/group_form', f: f
.form-group
.col-sm-2
@@ -31,13 +22,7 @@
You can change your group avatar here
- else
You can upload a group avatar here
- %a.choose-btn.btn.btn-small.js-choose-group-avatar-button
- %i.fa.fa-paperclip
- %span Choose File ...
- &nbsp;
- %span.file_name.js-avatar-filename File name...
- = f.file_field :avatar, class: "js-group-avatar-input hidden"
- .light The maximum file size allowed is 100KB.
+ = render 'shared/choose_group_avatar_button', f: f
- if @group.avatar?
%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-small remove-avatar"
diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder
index f2005193f83..240001967f3 100644
--- a/app/views/groups/issues.atom.builder
+++ b/app/views/groups/issues.atom.builder
@@ -7,18 +7,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue|
- xml.entry do
- xml.id project_issue_url(issue.project, issue)
- xml.link :href => project_issue_url(issue.project, issue)
- xml.title truncate(issue.title, :length => 80)
- xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
- xml.author do |author|
- xml.name issue.author_name
- xml.email issue.author_email
- end
- xml.summary issue.title
- end
+ issue_to_atom(xml, issue)
end
end
diff --git a/app/views/groups/members.html.haml b/app/views/groups/members.html.haml
index ba554cd5ef1..d2ebcdab7e1 100644
--- a/app/views/groups/members.html.haml
+++ b/app/views/groups/members.html.haml
@@ -13,7 +13,7 @@
= form_tag members_group_path(@group), method: :get, class: 'form-inline member-search-form' do
.form-group
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' }
- = submit_tag 'Search', class: 'btn'
+ = button_tag 'Search', class: 'btn'
- if current_user && current_user.can?(:manage_group, @group)
.pull-right
diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml
index 235e299343a..6e17cdaef6f 100644
--- a/app/views/groups/new.html.haml
+++ b/app/views/groups/new.html.haml
@@ -2,37 +2,18 @@
- if @group.errors.any?
.alert.alert-danger
%span= @group.errors.full_messages.first
- .form-group
- = f.label :name, class: 'control-label' do
- Group name
- .col-sm-10
- = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control", tabindex: 1, autofocus: true
- .form-group.group-description-holder
- = f.label :description, "Details", class: 'control-label'
- .col-sm-10
- = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4, tabindex: 2
+ = render 'shared/group_form', f: f, autofocus: true
.form-group.group-description-holder
= f.label :avatar, "Group avatar", class: 'control-label'
.col-sm-10
- %a.choose-btn.btn.btn-small.js-choose-group-avatar-button
- %i.fa.fa-paperclip
- %span Choose File ...
- &nbsp;
- %span.file_name.js-avatar-filename File name...
- = f.file_field :avatar, class: "js-group-avatar-input hidden"
- .light The maximum file size allowed is 100KB.
+ = render 'shared/choose_group_avatar_button', f: f
.form-group
.col-sm-2
.col-sm-10
- %ul
- %li A group is a collection of several projects
- %li Groups are private by default
- %li Members of a group may only view projects they have permission to access
- %li Group project URLs are prefixed with the group namespace
- %li Existing projects may be moved into a group
+ = render 'shared/group_tips'
.form-actions
= f.submit 'Create group', class: "btn btn-create", tabindex: 3
diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder
index e07bb7d2fb7..e765ea8338d 100644
--- a/app/views/groups/show.atom.builder
+++ b/app/views/groups/show.atom.builder
@@ -1,28 +1,12 @@
xml.instruct!
-xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do
+xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
xml.title "Group feed - #{@group.name}"
- xml.link :href => group_path(@group, :atom), :rel => "self", :type => "application/atom+xml"
- xml.link :href => group_path(@group), :rel => "alternate", :type => "text/html"
+ xml.link href: group_path(@group, :atom), rel: "self", type: "application/atom+xml"
+ xml.link href: group_path(@group), rel: "alternate", type: "text/html"
xml.id projects_url
xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
@events.each do |event|
- if event.proper?
- xml.entry do
- event_link = event_feed_url(event)
- event_title = event_feed_title(event)
-
- xml.id "tag:#{request.host},#{event.created_at.strftime("%Y-%m-%d")}:#{event.id}"
- xml.link :href => event_link
- xml.title truncate(event_title, :length => 80)
- xml.updated event.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email)
- xml.author do |author|
- xml.name event.author_name
- xml.email event.author_email
- end
- xml.summary event_title
- end
- end
+ event_to_atom(xml, event)
end
end
diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml
index 5ab82122ad7..04f79846858 100644
--- a/app/views/layouts/_search.html.haml
+++ b/app/views/layouts/_search.html.haml
@@ -4,11 +4,20 @@
= hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id
- = hidden_field_tag :search_code, true
+
+ - if current_controller?(:issues)
+ = hidden_field_tag :scope, 'issues'
+ - elsif current_controller?(:merge_requests)
+ = hidden_field_tag :scope, 'merge_requests'
+ - elsif current_controller?(:wikis)
+ = hidden_field_tag :scope, 'wiki_blobs'
+ - else
+ = hidden_field_tag :search_code, true
+
- if @snippet || @snippets
= hidden_field_tag :snippets, true
= hidden_field_tag :repository_ref, @ref
- = submit_tag 'Go' if ENV['RAILS_ENV'] == 'test'
+ = button_tag 'Go' if ENV['RAILS_ENV'] == 'test'
.search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref }
:javascript
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index ab421d63f1a..da451961327 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -28,3 +28,4 @@
You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, project_url(@project)} project team.
- if @target_url
#{link_to "View it on GitLab", @target_url}
+ = email_action @target_url
diff --git a/app/views/notify/new_ssh_key_email.html.haml b/app/views/notify/new_ssh_key_email.html.haml
index deb0822d8f2..63b0cbbd205 100644
--- a/app/views/notify/new_ssh_key_email.html.haml
+++ b/app/views/notify/new_ssh_key_email.html.haml
@@ -6,5 +6,5 @@
title:
%code= @key.title
%p
- If this key was added in error, you can remove it here:
+ If this key was added in error, you can remove it under
= link_to "SSH Keys", profile_keys_url
diff --git a/app/views/notify/new_ssh_key_email.text.erb b/app/views/notify/new_ssh_key_email.text.erb
index 5f0080c2b76..05b551c89a0 100644
--- a/app/views/notify/new_ssh_key_email.text.erb
+++ b/app/views/notify/new_ssh_key_email.text.erb
@@ -2,6 +2,6 @@ Hi <%= @user.name %>!
A new public key was added to your account:
-title.................. <%= @key.title %>
+Title: <%= @key.title %>
-If this key was added in error, you can remove it here: <%= profile_keys_url %>
+If this key was added in error, you can remove it at <%= profile_keys_url %>
diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml
index 2a7d317aa3e..425200ff523 100644
--- a/app/views/profiles/passwords/edit.html.haml
+++ b/app/views/profiles/passwords/edit.html.haml
@@ -24,7 +24,7 @@
.form-group
= f.label :password, 'New password', class: 'control-label'
.col-sm-10
- = f.password_field :password, required: true, class: 'form-control'
+ = f.password_field :password, required: true, class: 'form-control', id: 'user_password_profile'
.form-group
= f.label :password_confirmation, class: 'control-label'
.col-sm-10
diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml
index aef7348fd20..42d2d0db29c 100644
--- a/app/views/profiles/passwords/new.html.haml
+++ b/app/views/profiles/passwords/new.html.haml
@@ -16,7 +16,7 @@
.col-sm-10= f.password_field :current_password, required: true, class: 'form-control'
.form-group
= f.label :password, class: 'control-label'
- .col-sm-10= f.password_field :password, required: true, class: 'form-control'
+ .col-sm-10= f.password_field :password, required: true, class: 'form-control', id: 'user_password_profile'
.form-group
= f.label :password_confirmation, class: 'control-label'
.col-sm-10
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index d6b52f86154..640104fdad1 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -83,7 +83,7 @@
&nbsp;
%span.file_name.js-avatar-filename File name...
= f.file_field :avatar, class: "js-user-avatar-input hidden"
- .light The maximum file size allowed is 100KB.
+ .light The maximum file size allowed is 200KB.
- if @user.avatar?
%hr
= link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar"
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 672a91e0eef..30d063c7a36 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -16,11 +16,11 @@
- unless @project.empty_repo?
.fork-buttons
- if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace
- - if current_user.already_forked?(@project)
+ - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
= link_to project_path(current_user.fork_of(@project)), title: 'Go to my fork' do
= link_to_toggle_fork
- else
- = link_to fork_project_path(@project), title: "Fork project", method: "POST" do
+ = link_to new_project_fork_path(@project), title: "Fork project" do
= link_to_toggle_fork
.star-buttons
@@ -31,7 +31,7 @@
- else
= link_to_toggle_star('You must sign in to star a project.', false, false)
- .project-home-row
+ .project-home-row.hidden-xs
- if current_user && !empty_repo
.project-home-dropdown
= render "dropdown"
diff --git a/app/views/projects/_issuable_filter.html.haml b/app/views/projects/_issuable_filter.html.haml
new file mode 100644
index 00000000000..b3e5efd938f
--- /dev/null
+++ b/app/views/projects/_issuable_filter.html.haml
@@ -0,0 +1,72 @@
+.issues-filters
+ .dropdown.inline
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-user
+ %span.light assignee:
+ - if @assignee.present?
+ %strong= @assignee.name
+ - elsif params[:assignee_id] == "0"
+ Unassigned
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to project_filter_path(assignee_id: nil) do
+ Any
+ = link_to project_filter_path(assignee_id: 0) do
+ Unassigned
+ - @assignees.sort_by(&:name).each do |user|
+ %li
+ = link_to project_filter_path(assignee_id: user.id) do
+ = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
+ = user.name
+
+ .dropdown.inline.prepend-left-10
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-user
+ %span.light author:
+ - if @author.present?
+ %strong= @author.name
+ - elsif params[:author_id] == "0"
+ Unassigned
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to project_filter_path(author_id: nil) do
+ Any
+ = link_to project_filter_path(author_id: 0) do
+ Unassigned
+ - @authors.sort_by(&:name).each do |user|
+ %li
+ = link_to project_filter_path(author_id: user.id) do
+ = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
+ = user.name
+
+ .dropdown.inline.prepend-left-10
+ %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-clock-o
+ %span.light milestone:
+ - if @milestone.present?
+ %strong= @milestone.title
+ - elsif params[:milestone_id] == "0"
+ None (backlog)
+ - else
+ Any
+ %b.caret
+ %ul.dropdown-menu
+ %li
+ = link_to project_filter_path(milestone_id: nil) do
+ Any
+ = link_to project_filter_path(milestone_id: 0) do
+ None (backlog)
+ - project_active_milestones.each do |milestone|
+ %li
+ = link_to project_filter_path(milestone_id: milestone.id) do
+ %strong= milestone.title
+ %small.light= milestone.expires_at
+
+ .pull-right
+ = render 'shared/sort_dropdown'
diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml
index 675b73a59cb..b02f52a5aff 100644
--- a/app/views/projects/_issuable_form.html.haml
+++ b/app/views/projects/_issuable_form.html.haml
@@ -52,7 +52,7 @@
- else
%span.light No open milestones available.
&nbsp;
- = link_to 'Create new milestone', new_project_milestone_path(issuable.project)
+ = link_to 'Create new milestone', new_project_milestone_path(issuable.project), target: :blank
.form-group
= f.label :label_ids, class: 'control-label' do
%i.icon-tag
@@ -64,7 +64,7 @@
- else
%span.light No labels yet.
&nbsp;
- = link_to 'Create new label', new_project_label_path(issuable.project)
+ = link_to 'Create new label', new_project_label_path(issuable.project), target: :blank
.form-actions
- if issuable.new_record?
diff --git a/app/views/projects/_issues_nav.html.haml b/app/views/projects/_issues_nav.html.haml
new file mode 100644
index 00000000000..18628eb6207
--- /dev/null
+++ b/app/views/projects/_issues_nav.html.haml
@@ -0,0 +1,55 @@
+%ul.nav.nav-tabs
+ - if project_nav_tab? :issues
+ = nav_link(controller: :issues) do
+ = link_to project_issues_path(@project), class: "tab" do
+ Issues
+ - if project_nav_tab? :merge_requests
+ = nav_link(controller: :merge_requests) do
+ = link_to project_merge_requests_path(@project), class: "tab" do
+ Merge Requests
+ = nav_link(controller: :milestones) do
+ = link_to 'Milestones', project_milestones_path(@project), class: "tab"
+ = nav_link(controller: :labels) do
+ = link_to 'Labels', project_labels_path(@project), class: "tab"
+
+ - if current_controller?(:milestones)
+ %li.pull-right
+ %button.btn.btn-default.sidebar-expand-button
+ %i.icon.fa.fa-list
+
+ - if current_controller?(:issues)
+ - if current_user
+ %li.hidden-xs
+ = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do
+ %i.fa.fa-rss
+
+ %li.pull-right
+ .pull-right
+ %button.btn.btn-default.sidebar-expand-button
+ %i.icon.fa.fa-list
+
+ .pull-left
+ = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do
+ .append-right-10.hidden-xs.hidden-sm
+ = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' }
+ = hidden_field_tag :state, params['state']
+ = hidden_field_tag :scope, params['scope']
+ = hidden_field_tag :assignee_id, params['assignee_id']
+ = hidden_field_tag :milestone_id, params['milestone_id']
+ = hidden_field_tag :label_id, params['label_id']
+
+ - if can? current_user, :write_issue, @project
+ = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
+ %i.fa.fa-plus
+ New Issue
+
+ - if current_controller?(:merge_requests)
+ %li.pull-right
+ .pull-right
+ %button.btn.btn-default.sidebar-expand-button
+ %i.icon.fa.fa-list
+
+ - if can? current_user, :write_merge_request, @project
+ = link_to new_project_merge_request_path(@project), class: "btn btn-new pull-left", title: "New Merge Request" do
+ %i.fa.fa-plus
+ New Merge Request
diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml
index 64c19a57803..812d88a8730 100644
--- a/app/views/projects/blob/_actions.html.haml
+++ b/app/views/projects/blob/_actions.html.haml
@@ -23,5 +23,6 @@
tree_join(@commit.sha, @path)), class: 'btn btn-small'
- if allowed_tree_edit?
- = link_to '#modal-remove-blob', class: "remove-blob btn btn-small btn-remove", "data-toggle" => "modal" do
+ = button_tag class: 'remove-blob btn btn-small btn-remove',
+ 'data-toggle' => 'modal', 'data-target' => '#modal-remove-blob' do
Remove
diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml
index da84dc4b6cf..c5568315cb1 100644
--- a/app/views/projects/blob/_remove.html.haml
+++ b/app/views/projects/blob/_remove.html.haml
@@ -15,7 +15,7 @@
.form-group
.col-sm-2
.col-sm-10
- = submit_tag 'Remove file', class: 'btn btn-remove btn-remove-file'
+ = button_tag 'Remove file', class: 'btn btn-remove btn-remove-file'
= link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal"
:javascript
diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml
index 9f2b1b59292..d2aefd815a1 100644
--- a/app/views/projects/branches/index.html.haml
+++ b/app/views/projects/branches/index.html.haml
@@ -20,9 +20,9 @@
= link_to project_branches_path(sort: nil) do
Name
= link_to project_branches_path(sort: 'recently_updated') do
- Recently updated
+ = sort_title_recently_updated
= link_to project_branches_path(sort: 'last_updated') do
- Last updated
+ = sort_title_oldest_updated
%hr
- unless @branches.empty?
%ul.bordered-list.top-list.all-branches
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index f5a530d95f2..a6623240da1 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -15,7 +15,7 @@
.col-sm-10
= text_field_tag :ref, params[:ref], placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control'
.form-actions
- = submit_tag 'Create branch', class: 'btn btn-create', tabindex: 3
+ = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel'
:javascript
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index da6157cf1b6..cb0a3747f7d 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -12,7 +12,7 @@
%span.input-group-addon to
= text_field_tag :to, params[:to], class: "form-control"
&nbsp;
- = submit_tag "Compare", class: "btn btn-create commits-compare-btn"
+ = button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if compare_to_mr_button?
= link_to compare_mr_path, class: 'prepend-left-10 btn' do
%strong Make a merge request
diff --git a/app/views/projects/create.js.haml b/app/views/projects/create.js.haml
deleted file mode 100644
index 89710d3a09a..00000000000
--- a/app/views/projects/create.js.haml
+++ /dev/null
@@ -1,13 +0,0 @@
-- if @project.saved?
- - if @project.import?
- :plain
- location.href = "#{import_project_path(@project)}";
- - else
- :plain
- location.href = "#{project_path(@project)}";
-- else
- :plain
- $(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
- $('.project-submit').enable();
- $('.save-project-loader').hide();
- $('.project-edit-container').show();
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index c415ae2ddc8..bf7770ceedf 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -10,7 +10,10 @@
- if @commit.parent_ids.present?
= view_file_btn(@commit.parent_id, diff_file, project)
- else
- %span= diff_file.new_path
+ - if diff_file.renamed_file
+ %span= "#{diff_file.old_path} renamed to #{diff_file.new_path}"
+ - else
+ %span= diff_file.new_path
- if diff_file.mode_changed?
%span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}"
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index f48f4bb2953..b85cf7d8d37 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -13,7 +13,7 @@
= f.label :name, class: 'control-label' do
Project name
.col-sm-10
- = f.text_field :name, placeholder: "Example Project", class: "form-control"
+ = f.text_field :name, placeholder: "Example Project", class: "form-control", id: "project_name_edit"
.form-group
@@ -124,6 +124,12 @@
.errors-holder
.panel-body
= form_for(@project, html: { class: 'form-horizontal' }) do |f|
+ .form-group.project_name_holder
+ = f.label :name, class: 'control-label' do
+ Project name
+ .col-sm-9
+ .form-group
+ = f.text_field :name, placeholder: "Example Project", class: "form-control"
.form-group
= f.label :path, class: 'control-label' do
%span Path
diff --git a/app/views/projects/fork.html.haml b/app/views/projects/fork.html.haml
deleted file mode 100644
index d8f5c7b98d6..00000000000
--- a/app/views/projects/fork.html.haml
+++ /dev/null
@@ -1,19 +0,0 @@
-.alert.alert-danger.alert-block
- %h4
- %i.fa.fa-code-fork
- Fork Error!
- %p
- You tried to fork
- = link_to_project @project
- but it failed for the following reason:
-
-
- - if @forked_project && @forked_project.errors.any?
- %p
- &ndash;
- = @forked_project.errors.full_messages.first
-
- %p
- = link_to fork_project_path(@project), title: "Fork", class: "btn", method: "POST" do
- %i.fa.fa-code-fork
- Try to Fork again
diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml
new file mode 100644
index 00000000000..76d3aa5bf00
--- /dev/null
+++ b/app/views/projects/forks/error.html.haml
@@ -0,0 +1,20 @@
+- if @forked_project && !@forked_project.saved?
+ .alert.alert-danger.alert-block
+ %h4
+ %i.fa.fa-code-fork
+ Fork Error!
+ %p
+ You tried to fork
+ = link_to_project @project
+ but it failed for the following reason:
+
+
+ - if @forked_project && @forked_project.errors.any?
+ %p
+ &ndash;
+ = @forked_project.errors.full_messages.first
+
+ %p
+ = link_to new_project_fork_path(@project), title: "Fork", class: "btn" do
+ %i.fa.fa-code-fork
+ Try to Fork again
diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml
new file mode 100644
index 00000000000..54f2cef023b
--- /dev/null
+++ b/app/views/projects/forks/new.html.haml
@@ -0,0 +1,38 @@
+%h3.page-title Fork project
+%p.lead Select namespace where to fork this project
+%hr
+
+.fork-namespaces
+ - @namespaces.in_groups_of(6, false) do |group|
+ .row
+ - group.each do |namespace|
+ .col-md-2.col-sm-3
+ - if fork = namespace.find_fork_of(@project)
+ .thumbnail.fork-exists-thumbnail
+ = link_to project_path(fork), title: "Visit project fork", class: 'has_tooltip' do
+ = image_tag namespace_icon(namespace, 200)
+ .caption
+ %h4=namespace.human_name
+ %p
+ = namespace.path
+ - else
+ .thumbnail.fork-thumbnail
+ = link_to project_fork_path(@project, namespace_id: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do
+ = image_tag namespace_icon(namespace, 200)
+ .caption
+ %h4=namespace.human_name
+ %p
+ = namespace.path
+
+ %p.light
+ Fork is a copy of a project repository.
+ %br
+ Forking a repository allows you to do changes without affecting the original project.
+
+.save-project-loader.hide
+ .center
+ %h2
+ %i.fa.fa-spinner.fa-spin
+ Forking repository
+ %p Please wait a moment, this page will automatically refresh when ready.
+
diff --git a/app/views/projects/import.html.haml b/app/views/projects/import.html.haml
deleted file mode 100644
index 4513c89e784..00000000000
--- a/app/views/projects/import.html.haml
+++ /dev/null
@@ -1,31 +0,0 @@
-- if @project.import_in_progress?
- .save-project-loader
- .center
- %h2
- %i.fa.fa-spinner.fa-spin
- Import in progress.
- %p.monospace git clone --bare #{hidden_pass_url(@project.import_url)}
- %p Please wait while we import the repository for you. Refresh at will.
- :javascript
- new ProjectImport();
-
-- elsif @project.import_failed?
- .save-project-loader
- .center
- %h2
- Import failed. Retry?
- %hr
- - if can?(current_user, :admin_project, @project)
- = form_for @project, url: retry_import_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f|
- .form-group.import-url-data
- = f.label :import_url, class: 'control-label' do
- %span Import existing git repo
- .col-sm-10
- = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
- .bs-callout.bs-callout-info
- This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
- %br
- The import will time out after 4 minutes. For big repositories, use a clone/push combination.
- For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
- .form-actions
- = f.submit 'Retry import', class: "btn btn-create", tabindex: 4
diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml
new file mode 100644
index 00000000000..6c3083e49f5
--- /dev/null
+++ b/app/views/projects/imports/new.html.haml
@@ -0,0 +1,21 @@
+%h3.page-title
+ - if @project.import_failed?
+ Import failed. Retry?
+ - else
+ Import repository
+
+%hr
+
+= form_for @project, url: project_import_path(@project), method: :post, html: { class: 'form-horizontal' } do |f|
+ .form-group.import-url-data
+ = f.label :import_url, class: 'control-label' do
+ %span Import existing git repo
+ .col-sm-10
+ = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git'
+ .bs-callout.bs-callout-info
+ This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git.
+ %br
+ The import will time out after 4 minutes. For big repositories, use a clone/push combination.
+ For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}
+ .form-actions
+ = f.submit 'Start import', class: "btn btn-create", tabindex: 4
diff --git a/app/views/projects/imports/show.html.haml b/app/views/projects/imports/show.html.haml
new file mode 100644
index 00000000000..2d1fdafed24
--- /dev/null
+++ b/app/views/projects/imports/show.html.haml
@@ -0,0 +1,9 @@
+.save-project-loader
+ .center
+ %h2
+ %i.fa.fa-spinner.fa-spin
+ Import in progress.
+ %p.monospace git clone --bare #{hidden_pass_url(@project.import_url)}
+ %p Please wait while we import the repository for you. Refresh at will.
+ :javascript
+ new ProjectImport();
diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml
deleted file mode 100644
index 1d2f3ed8118..00000000000
--- a/app/views/projects/issues/_head.html.haml
+++ /dev/null
@@ -1,36 +0,0 @@
-%ul.nav.nav-tabs
- = nav_link(controller: :issues) do
- = link_to project_issues_path(@project), class: "tab" do
- Browse Issues
- = nav_link(controller: :milestones) do
- = link_to 'Milestones', project_milestones_path(@project), class: "tab"
- = nav_link(controller: :labels) do
- = link_to 'Labels', project_labels_path(@project), class: "tab"
-
- - if current_controller?(:milestones)
- %li.pull-right
- %button.btn.btn-default.sidebar-expand-button
- %i.icon.fa.fa-list
-
- - if current_controller?(:issues)
- - if current_user
- %li
- = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do
- %i.fa.fa-rss
-
- %li.pull-right
- .pull-right
- %button.btn.btn-default.sidebar-expand-button
- %i.icon.fa.fa-list
- = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do
- .append-right-10.hidden-xs.hidden-sm
- = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' }
- = hidden_field_tag :state, params['state']
- = hidden_field_tag :scope, params['scope']
- = hidden_field_tag :assignee_id, params['assignee_id']
- = hidden_field_tag :milestone_id, params['milestone_id']
- = hidden_field_tag :label_id, params['label_id']
- - if can? current_user, :write_issue, @project
- = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do
- %i.fa.fa-plus
- New Issue
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index b125706781c..dc6510be858 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -4,7 +4,6 @@
= check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue)
.issue-title
- %span.light= "##{issue.iid}"
%span.str-truncated
= link_to_gfm issue.title, project_issue_path(issue.project, issue), class: "row_title"
- if issue.closed?
@@ -12,10 +11,9 @@
CLOSED
.issue-info
+ %span.light= "##{issue.iid}"
- if issue.assignee
assigned to #{link_to_member(@project, issue.assignee)}
- - else
- unassigned
- if issue.votes_count > 0
= render 'votes/votes_inline', votable: issue
- if issue.notes.any?
@@ -30,7 +28,7 @@
%span.task-status
= issue.task_status
- .pull-right
+ .pull-right.issue-updated-at
%small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')}
.issue-labels
diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml
index 8c3f0823386..648f459dc9e 100644
--- a/app/views/projects/issues/_issue_context.html.haml
+++ b/app/views/projects/issues/_issue_context.html.haml
@@ -19,6 +19,7 @@
= hidden_field_tag :issue_context
= f.submit class: 'btn'
- elsif issue.milestone
- = link_to issue.milestone.title, project_milestone_path
+ = link_to project_milestone_path(@project, @issue.milestone) do
+ = @issue.milestone.title
- else
None
diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml
index 0bff8bdbead..15c84c7ced2 100644
--- a/app/views/projects/issues/_issues.html.haml
+++ b/app/views/projects/issues/_issues.html.haml
@@ -1,55 +1,7 @@
.append-bottom-10
.check-all-holder
= check_box_tag "check_all_issues", nil, false, class: "check_all_issues left"
- .issues-filters
- .dropdown.inline
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-user
- %span.light assignee:
- - if @assignee.present?
- %strong= @assignee.name
- - elsif params[:assignee_id] == "0"
- Unassigned
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(assignee_id: nil) do
- Any
- = link_to project_filter_path(assignee_id: 0) do
- Unassigned
- - @assignees.sort_by(&:name).each do |user|
- %li
- = link_to project_filter_path(assignee_id: user.id) do
- = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
- = user.name
-
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-clock-o
- %span.light milestone:
- - if @milestone.present?
- %strong= @milestone.title
- - elsif params[:milestone_id] == "0"
- None (backlog)
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(milestone_id: nil) do
- Any
- = link_to project_filter_path(milestone_id: 0) do
- None (backlog)
- - project_active_milestones.each do |milestone|
- %li
- = link_to project_filter_path(milestone_id: milestone.id) do
- %strong= milestone.title
- %small.light= milestone.expires_at
-
- .pull-right
- = render 'shared/sort_dropdown'
+ = render 'projects/issuable_filter'
.clearfix
.issues_bulk_update.hide
diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder
index 012ba235951..61e651da932 100644
--- a/app/views/projects/issues/index.atom.builder
+++ b/app/views/projects/issues/index.atom.builder
@@ -7,17 +7,6 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any?
@issues.each do |issue|
- xml.entry do
- xml.id project_issue_url(@project, issue)
- xml.link :href => project_issue_url(@project, issue)
- xml.title truncate(issue.title, :length => 80)
- xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ")
- xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(issue.author_email)
- xml.author do |author|
- xml.name issue.author_name
- xml.email issue.author_email
- end
- xml.summary issue.title
- end
+ issue_to_atom(xml, issue)
end
end
diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml
index 4ec362b3063..8db6241f21f 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -1,4 +1,4 @@
-= render "head"
+= render "projects/issues_nav"
.row
.fixed.fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs
%i.fa.fa-list.fa-2x
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index 71eb0d5c866..01a1fabda26 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -38,8 +38,12 @@
- else
Open
+ .cross-project-ref
+ %i.fa.fa-link.has_tooltip{:"data-original-title" => 'Cross-project reference'}
+ = cross_project_reference(@project, @issue)
+
.creator
- Created by #{link_to_member(@project, @issue.author)} #{time_ago_with_tooltip(@issue.created_at)}
+ Created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)}
%h4.title
= gfm escape_once(@issue.title)
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 06568278de8..c7c17c7797e 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -1,4 +1,4 @@
-= render "projects/issues/head"
+= render "projects/issues_nav"
- if can? current_user, :admin_label, @project
= link_to new_project_label_path(@project), class: "pull-right btn btn-new" do
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 1ee2e1bdae8..dedb060a231 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,13 +1,12 @@
%li{ class: mr_css_classes(merge_request) }
.merge-request-title
- %span.light= "##{merge_request.iid}"
= link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.target_project, merge_request), class: "row_title"
- if merge_request.merged?
%small.pull-right
%i.fa.fa-check
MERGED
- else
- %span.pull-right
+ %span.pull-right.hidden-xs
- if merge_request.for_fork?
%span.light
#{merge_request.source_project_namespace}:
@@ -15,6 +14,7 @@
%i.fa.fa-angle-right.light
= merge_request.target_branch
.merge-request-info
+ %span.light= "##{merge_request.iid}"
- if merge_request.author
authored by #{link_to_member(merge_request.source_project, merge_request.author)}
- if merge_request.votes_count > 0
@@ -31,7 +31,7 @@
%span.task-status
= merge_request.task_status
- .pull-right
+ .pull-right.hidden-xs
%small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')}
.merge-request-labels
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index be638d7cac1..b93e0f9da3e 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,67 +1,12 @@
-- if can? current_user, :write_merge_request, @project
- = link_to new_project_merge_request_path(@project), class: "pull-right btn btn-new", title: "New Merge Request" do
- %i.fa.fa-plus
- New Merge Request
-%h3.page-title
- Merge Requests
-%hr
+= render "projects/issues_nav"
+
.row
- .fixed.sidebar-expand-button.hidden-lg.hidden-md
- %i.fa.fa-list.fa-2x
.col-md-3.responsive-side
= render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project),
labels: true, redirect: 'merge_requests', entity: 'merge_request'
.col-md-9
- .mr-filters.append-bottom-10
- .dropdown.inline
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-user
- %span.light assignee:
- - if @assignee.present?
- %strong= @assignee.name
- - elsif params[:assignee_id] == "0"
- Unassigned
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(assignee_id: nil) do
- Any
- = link_to project_filter_path(assignee_id: 0) do
- Unassigned
- - @assignees.sort_by(&:name).each do |user|
- %li
- = link_to project_filter_path(assignee_id: user.id) do
- = image_tag avatar_icon(user.email), class: "avatar s16", alt: ''
- = user.name
-
- .dropdown.inline.prepend-left-10
- %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"}
- %i.fa.fa-clock-o
- %span.light milestone:
- - if @milestone.present?
- %strong= @milestone.title
- - elsif params[:milestone_id] == "0"
- None (backlog)
- - else
- Any
- %b.caret
- %ul.dropdown-menu
- %li
- = link_to project_filter_path(milestone_id: nil) do
- Any
- = link_to project_filter_path(milestone_id: 0) do
- None (backlog)
- - project_active_milestones.each do |milestone|
- %li
- = link_to project_filter_path(milestone_id: milestone.id) do
- %strong= milestone.title
- %small.light= milestone.expires_at
-
- .pull-right
- = render 'shared/sort_dropdown'
-
+ .append-bottom-10
+ = render 'projects/issuable_filter'
.panel.panel-default
%ul.well-list.mr-list
= render @merge_requests
diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
index 9540453ce3e..63db4b30968 100644
--- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml
+++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml
@@ -10,11 +10,11 @@
- target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path
%p
%strong Step 1.
- Checkout the branch we are going to merge and pull in the code
+ Fetch the code and create a new branch pointing to it
%pre.dark
:preserve
- git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} #{@merge_request.target_branch}
- git pull #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch}
+ git fetch #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch}
+ git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} FETCH_HEAD
%p
%strong Step 2.
Merge the branch and push the changes to GitLab
diff --git a/app/views/projects/merge_requests/show/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index 7e5a4eda508..866b236d827 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -8,6 +8,10 @@
- else
Open
+ .cross-project-ref
+ %i.fa.fa-link.has_tooltip{:"data-original-title" => 'Cross-project reference'}
+ = cross_project_reference(@project, @merge_request)
+
.creator
Created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)}
diff --git a/app/views/projects/merge_requests/show/_state_widget.html.haml b/app/views/projects/merge_requests/show/_state_widget.html.haml
index 87dad6140be..a4f2a890969 100644
--- a/app/views/projects/merge_requests/show/_state_widget.html.haml
+++ b/app/views/projects/merge_requests/show/_state_widget.html.haml
@@ -11,14 +11,18 @@
- if @merge_request.closed?
%h4
- Closed by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)}
- #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
+ Closed
+ - if @merge_request.closed_event
+ by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)}
+ #{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
%p Changes were not merged into target branch
- if @merge_request.merged?
%h4
- Merged by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)}
- #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
+ Merged
+ - if @merge_request.merge_event
+ by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)}
+ #{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
= render "projects/merge_requests/show/remove_source_branch"
- if @merge_request.locked?
@@ -44,4 +48,3 @@
Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'}
= succeed '.' do
!= gfm(issues_sentence(@closes_issues))
-
diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml
index 03367b7cdbf..0db0b114d63 100644
--- a/app/views/projects/milestones/index.html.haml
+++ b/app/views/projects/milestones/index.html.haml
@@ -1,4 +1,4 @@
-= render "projects/issues/head"
+= render "projects/issues_nav"
.milestones_content
%h3.page-title
Milestones
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 8263f7530a2..f08ccc1d570 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -1,4 +1,4 @@
-= render "projects/issues/head"
+= render "projects/issues_nav"
%h3.page-title
Milestone ##{@milestone.iid}
.pull-right
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index f5cd0f21e01..e77ef84f51c 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -3,7 +3,7 @@
= render 'projects/errors'
.project-edit-content
- = form_for @project, remote: true, html: { class: 'new_project form-horizontal' } do |f|
+ = form_for @project, html: { class: 'new_project form-horizontal' } do |f|
.form-group.project-name-holder
= f.label :name, class: 'control-label' do
%strong Project name
diff --git a/app/views/projects/new_tree/show.html.haml b/app/views/projects/new_tree/show.html.haml
index c47c0a3f642..f09d3659774 100644
--- a/app/views/projects/new_tree/show.html.haml
+++ b/app/views/projects/new_tree/show.html.haml
@@ -19,14 +19,14 @@
Encoding
.col-sm-10
= select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
- = render 'shared/commit_message_container', params: params,
- placeholder: 'Add new file'
.file-holder
.file-title
%i.fa.fa-file
.file-content.code
%pre#editor= params[:content]
+ = render 'shared/commit_message_container', params: params,
+ placeholder: 'Add new file'
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
cancel_path: project_tree_path(@project, @id)
diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml
new file mode 100644
index 00000000000..dd576243510
--- /dev/null
+++ b/app/views/projects/no_repo.html.haml
@@ -0,0 +1,22 @@
+%h2
+ %i.fa.fa-warning
+ No repository
+
+%p.slead
+ The repository for this project does not exist.
+ %br
+ This means you can not push code until you create an empty repository or import existing one.
+%hr
+
+.no-repo-actions
+ = link_to project_repository_path(@project), method: :post, class: 'btn btn-primary' do
+ Create empty bare repository
+
+ %strong.prepend-left-10.append-right-10 or
+
+ = link_to new_project_import_path(@project), class: 'btn' do
+ Import repository
+
+- if can? current_user, :remove_project, @project
+ .prepend-top-20
+ = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right"
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 05946162d3b..47ffe1fd2f3 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -19,7 +19,7 @@
= yield(:note_actions)
%a.btn.grouped.js-close-discussion-note-form Cancel
- .note-form-option
+ .note-form-option.hidden-xs
%a.choose-btn.btn.js-choose-note-attachment-button
%i.fa.fa-paperclip
%span Choose File ...
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index aa52ff35d0c..354afd3e2c9 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -18,6 +18,8 @@
%i.fa.fa-trash-o.cred
Remove
= link_to_member(@project, note.author, avatar: false)
+ %span.author-username
+ = '@' + note.author.username
%span.note-last-update
= note_timestamp(note)
@@ -60,7 +62,7 @@
- if note.attachment.image?
= link_to note.attachment.secure_url, target: '_blank' do
= image_tag note.attachment.secure_url, class: 'note-image-attach'
- .attachment.pull-right
+ .attachment
= link_to note.attachment.secure_url, target: "_blank" do
%i.fa.fa-paperclip
= note.attachment_identifier
diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml
index 16d59d1fe9d..1151f22c7e8 100644
--- a/app/views/projects/services/_form.html.haml
+++ b/app/views/projects/services/_form.html.haml
@@ -28,7 +28,7 @@
- @service.fields.each do |field|
- name = field[:name]
- - value = @service.send(name)
+ - value = @service.send(name) unless field[:type] == 'password'
- type = field[:type]
- placeholder = field[:placeholder]
- choices = field[:choices]
@@ -45,6 +45,8 @@
= f.check_box name
- elsif type == 'select'
= f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
+ - elsif type == 'password'
+ = f.password_field name, class: 'form-control'
.form-actions
= f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml
index bce105a033b..4ab102ba96c 100644
--- a/app/views/projects/tags/_tag.html.haml
+++ b/app/views/projects/tags/_tag.html.haml
@@ -4,6 +4,9 @@
= link_to project_commits_path(@project, tag.name), class: "" do
%i.fa.fa-tag
= tag.name
+ - if tag.message.present?
+ &nbsp;
+ = strip_gpg_signature(tag.message)
.pull-right
- if can? current_user, :download_code, @project
= render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small'
diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml
index aa08b397763..ad7ff8d3db8 100644
--- a/app/views/projects/tags/new.html.haml
+++ b/app/views/projects/tags/new.html.haml
@@ -21,7 +21,7 @@
= text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control'
.light (Optional) Entering a message will create an annotated tag.
.form-actions
- = submit_tag 'Create tag', class: 'btn btn-create', tabindex: 3
+ = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3
= link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel'
:javascript
diff --git a/app/views/projects/team_members/_team_member.html.haml b/app/views/projects/team_members/_team_member.html.haml
index 5f29b58de32..7a9c0939ba0 100644
--- a/app/views/projects/team_members/_team_member.html.haml
+++ b/app/views/projects/team_members/_team_member.html.haml
@@ -5,7 +5,7 @@
- unless @project.personal? && user == current_user
.pull-left
= form_for(member, as: :project_member, url: project_team_member_path(@project, member.user)) do |f|
- = f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level), {}, class: "medium project-access-select span2 trigger-submit"
+ = f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level), {}, class: "trigger-submit"
&nbsp;
= link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do
%i.fa.fa-minus.fa-inverse
diff --git a/app/views/projects/team_members/import.html.haml b/app/views/projects/team_members/import.html.haml
index 510b579fe2f..d1f46c61b2e 100644
--- a/app/views/projects/team_members/import.html.haml
+++ b/app/views/projects/team_members/import.html.haml
@@ -9,6 +9,6 @@
.col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true)
.form-actions
- = submit_tag 'Import project members', class: "btn btn-create"
+ = button_tag 'Import project members', class: "btn btn-create"
= link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel"
diff --git a/app/views/search/_project_filter.html.haml b/app/views/search/_project_filter.html.haml
index c201b3d6c47..ad933502a28 100644
--- a/app/views/search/_project_filter.html.haml
+++ b/app/views/search/_project_filter.html.haml
@@ -25,6 +25,7 @@
= @search_results.notes_count
%li{class: ("active" if @scope == 'wiki_blobs')}
= link_to search_filter_path(scope: 'wiki_blobs') do
+ %i.fa.fa-book
Wiki
.pull-right
= @search_results.wiki_blobs_count
diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml
index bae57917a4c..5b4816e4c40 100644
--- a/app/views/search/show.html.haml
+++ b/app/views/search/show.html.haml
@@ -6,7 +6,7 @@
.col-sm-6
= search_field_tag :search, params[:search], placeholder: "issue 143", class: "form-control search-text-input", id: "dashboard_search"
.col-sm-4
- = submit_tag 'Search', class: "btn btn-create"
+ = button_tag 'Search', class: "btn btn-create"
.form-group
.col-sm-2
- unless params[:snippets].eql? 'true'
diff --git a/app/views/shared/_choose_group_avatar_button.html.haml b/app/views/shared/_choose_group_avatar_button.html.haml
new file mode 100644
index 00000000000..299c0bd42a2
--- /dev/null
+++ b/app/views/shared/_choose_group_avatar_button.html.haml
@@ -0,0 +1,7 @@
+%a.choose-btn.btn.btn-small.js-choose-group-avatar-button
+ %i.fa.fa-paperclip
+ %span Choose File ...
+&nbsp;
+%span.file_name.js-avatar-filename File name...
+= f.file_field :avatar, class: 'js-group-avatar-input hidden'
+.light The maximum file size allowed is 200KB.
diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml
new file mode 100644
index 00000000000..93294e42505
--- /dev/null
+++ b/app/views/shared/_group_form.html.haml
@@ -0,0 +1,12 @@
+.form-group
+ = f.label :name, class: 'control-label' do
+ Group name
+ .col-sm-10
+ = f.text_field :name, placeholder: 'Example Group', class: 'form-control',
+ autofocus: local_assigns[:autofocus] || false
+
+.form-group.group-description-holder
+ = f.label :description, 'Details', class: 'control-label'
+ .col-sm-10
+ = f.text_area :description, maxlength: 250,
+ class: 'form-control js-gfm-input', rows: 4
diff --git a/app/views/shared/_group_tips.html.haml b/app/views/shared/_group_tips.html.haml
new file mode 100644
index 00000000000..e5cf783beb7
--- /dev/null
+++ b/app/views/shared/_group_tips.html.haml
@@ -0,0 +1,6 @@
+%ul
+ %li A group is a collection of several projects
+ %li Groups are private by default
+ %li Members of a group may only view projects they have permission to access
+ %li Group project URLs are prefixed with the group namespace
+ %li Existing projects may be moved into a group
diff --git a/app/views/shared/_promo.html.haml b/app/views/shared/_promo.html.haml
index 3400c345c4c..3596aabe309 100644
--- a/app/views/shared/_promo.html.haml
+++ b/app/views/shared/_promo.html.haml
@@ -1,5 +1,5 @@
.gitlab-promo
= link_to 'Homepage', promo_url
= link_to "Blog", promo_url + '/blog/'
- = link_to "@gitlabhq", "https://twitter.com/gitlabhq"
+ = link_to "@gitlab", "https://twitter.com/gitlab"
= link_to "Requests", "http://feedback.gitlab.com/"
diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml
index 7b37b39780e..54f59245690 100644
--- a/app/views/shared/_sort_dropdown.html.haml
+++ b/app/views/shared/_sort_dropdown.html.haml
@@ -9,13 +9,13 @@
%ul.dropdown-menu
%li
= link_to project_filter_path(sort: 'newest') do
- Newest
+ = sort_title_recently_created
= link_to project_filter_path(sort: 'oldest') do
- Oldest
+ = sort_title_oldest_created
= link_to project_filter_path(sort: 'recently_updated') do
- Recently updated
+ = sort_title_recently_updated
= link_to project_filter_path(sort: 'last_updated') do
- Last updated
+ = sort_title_oldest_updated
= link_to project_filter_path(sort: 'milestone_due_soon') do
Milestone due soon
= link_to project_filter_path(sort: 'milestone_due_later') do
diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml
index 09b2985d498..ea008c2dede 100644
--- a/app/views/users/_groups.html.haml
+++ b/app/views/users/_groups.html.haml
@@ -1,3 +1,3 @@
- groups.each do |group|
= link_to group, class: 'profile-groups-avatars', :title => group.name do
- = image_tag group_icon(group.path)
+ - image_tag group_icon(group.path)
diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder
new file mode 100644
index 00000000000..b7216a88765
--- /dev/null
+++ b/app/views/users/show.atom.builder
@@ -0,0 +1,12 @@
+xml.instruct!
+xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlnsmedia" => "http://search.yahoo.com/mrss/" do
+ xml.title "Activity feed for #{@user.name}"
+ 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 projects_url
+ xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any?
+
+ @events.each do |event|
+ event_to_atom(xml, event)
+ end
+end
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index cb49c030af2..54f2666ce5d 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -18,7 +18,15 @@
%h4 Groups:
= render 'groups', groups: @groups
%hr
- %h4 User Activity:
+ %h4
+ User Activity:
+
+ - if current_user
+ %span.rss-icon.pull-right
+ = link_to user_path(@user, :atom, { private_token: current_user.private_token }) do
+ %strong
+ %i.fa.fa-rss
+
= render @events
.col-md-4
= render 'profile', user: @user
diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb
index 2947c8e3ecd..e3f6f3a6aef 100644
--- a/app/workers/emails_on_push_worker.rb
+++ b/app/workers/emails_on_push_worker.rb
@@ -21,5 +21,8 @@ class EmailsOnPushWorker
recipients.split(" ").each do |recipient|
Notify.repository_push_email(project_id, recipient, author_id, branch, compare).deliver
end
+ ensure
+ compare = nil
+ GC.start
end
end
diff --git a/app/workers/project_service_worker.rb b/app/workers/project_service_worker.rb
new file mode 100644
index 00000000000..cc0a7f25664
--- /dev/null
+++ b/app/workers/project_service_worker.rb
@@ -0,0 +1,9 @@
+class ProjectServiceWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :project_web_hook
+
+ def perform(hook_id, data)
+ Service.find(hook_id).execute(data)
+ end
+end
diff --git a/config/application.rb b/config/application.rb
index e36df913d0b..8a280de6fac 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -13,7 +13,6 @@ module Gitlab
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/lib
- #{config.root}/app/finders
#{config.root}/app/models/hooks
#{config.root}/app/models/concerns
#{config.root}/app/models/project_services
@@ -23,10 +22,6 @@ module Gitlab
# :all can be used as a placeholder for all plugins not explicitly named.
# config.plugins = [ :exception_notification, :ssl_requirement, :all ]
- # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
- # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
- # config.time_zone = 'Central Time (US & Canada)'
-
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
# config.i18n.default_locale = :de
@@ -97,5 +92,8 @@ module Gitlab
redis_config_hash[:namespace] = 'cache:gitlab'
config.cache_store = :redis_store, redis_config_hash
+
+ # This is needed for gitlab-shell
+ ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH']
end
end
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index e7a8d08dc83..7b4c180fccc 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -33,7 +33,14 @@ production: &base
# Uncomment and customize if you can't use the default user to run GitLab (default: 'git')
# user: git
+ ## Date & Time settings
+ # Uncomment and customize if you want to change the default time zone of GitLab application.
+ # To see all available zones, run `bundle exec rake time:zones:all RAILS_ENV=production`
+ # time_zone: 'UTC'
+
## Email settings
+ # Uncomment and set to false if you need to disable email sending from GitLab (default: true)
+ # email_enabled: true
# Email address used in the "From" field in mails sent by GitLab
email_from: example@example.com
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 7e7c91ced77..27bb83784ba 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -61,7 +61,6 @@ Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil?
if Settings.ldap['enabled'] || Rails.env.test?
if Settings.ldap['host'].present?
server = Settings.ldap.except('sync_time')
- server['label'] = 'LDAP'
server['provider_name'] = 'ldap'
Settings.ldap['servers'] = {
'ldap' => server
@@ -69,6 +68,7 @@ if Settings.ldap['enabled'] || Rails.env.test?
end
Settings.ldap['servers'].each do |key, server|
+ server['label'] ||= 'LDAP'
server['allow_username_or_email_login'] = false if server['allow_username_or_email_login'].nil?
server['active_directory'] = true if server['active_directory'].nil?
server['provider_name'] ||= "ldap#{key}".downcase
@@ -95,6 +95,7 @@ Settings.gitlab['https'] = false if Settings.gitlab['https'].nil?
Settings.gitlab['port'] ||= Settings.gitlab.https ? 443 : 80
Settings.gitlab['relative_url_root'] ||= ENV['RAILS_RELATIVE_URL_ROOT'] || ''
Settings.gitlab['protocol'] ||= Settings.gitlab.https ? "https" : "http"
+Settings.gitlab['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil?
Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}"
Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url)
Settings.gitlab['user'] ||= 'git'
@@ -103,6 +104,7 @@ Settings.gitlab['user_home'] ||= begin
rescue ArgumentError # no user configured
'/home/' + Settings.gitlab['user']
end
+Settings.gitlab['time_zone'] ||= nil
Settings.gitlab['signup_enabled'] ||= false
Settings.gitlab['signin_enabled'] ||= true if Settings.gitlab['signin_enabled'].nil?
Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
diff --git a/config/initializers/4_sidekiq.rb b/config/initializers/4_sidekiq.rb
index 228b14cb526..75c543c0f47 100644
--- a/config/initializers/4_sidekiq.rb
+++ b/config/initializers/4_sidekiq.rb
@@ -14,7 +14,8 @@ Sidekiq.configure_server do |config|
}
config.server_middleware do |chain|
- chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger
+ chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS']
+ chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MAX_RSS']
end
end
diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb
index b8ac87fbd5a..18759f0cfb0 100644
--- a/config/initializers/7_omniauth.rb
+++ b/config/initializers/7_omniauth.rb
@@ -1,7 +1,8 @@
if Gitlab::LDAP::Config.enabled?
module OmniAuth::Strategies
server = Gitlab.config.ldap.servers.values.first
- const_set(server['provider_class'], Class.new(LDAP))
+ klass = server['provider_class']
+ const_set(klass, Class.new(LDAP)) unless klass == 'LDAP'
end
OmniauthCallbacksController.class_eval do
diff --git a/config/initializers/disable_email_interceptor.rb b/config/initializers/disable_email_interceptor.rb
new file mode 100644
index 00000000000..c76a6b8b19f
--- /dev/null
+++ b/config/initializers/disable_email_interceptor.rb
@@ -0,0 +1,2 @@
+# Interceptor in lib/disable_email_interceptor.rb
+ActionMailer::Base.register_interceptor(DisableEmailInterceptor) unless Gitlab.config.gitlab.email_enabled
diff --git a/config/initializers/gitlab_shell_secret_token.rb b/config/initializers/gitlab_shell_secret_token.rb
new file mode 100644
index 00000000000..8d2b771e535
--- /dev/null
+++ b/config/initializers/gitlab_shell_secret_token.rb
@@ -0,0 +1,19 @@
+# Be sure to restart your server when you modify this file.
+
+require 'securerandom'
+
+# Your secret key for verifying the gitlab_shell.
+
+
+secret_file = Rails.root.join('.gitlab_shell_secret')
+gitlab_shell_symlink = File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret')
+
+unless File.exist? secret_file
+ # Generate a new token of 16 random hexadecimal characters and store it in secret_file.
+ token = SecureRandom.hex(16)
+ File.write(secret_file, token)
+end
+
+if File.exist?(Gitlab.config.gitlab_shell.path) && !File.exist?(gitlab_shell_symlink)
+ FileUtils.symlink(secret_file, gitlab_shell_symlink)
+end \ No newline at end of file
diff --git a/config/initializers/time_zone.rb b/config/initializers/time_zone.rb
new file mode 100644
index 00000000000..ee246e67d66
--- /dev/null
+++ b/config/initializers/time_zone.rb
@@ -0,0 +1 @@
+Time.zone = Gitlab.config.gitlab.time_zone || Time.zone
diff --git a/config/routes.rb b/config/routes.rb
index 3edc78cee33..7480f7cdc85 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -137,7 +137,8 @@ Gitlab::Application.routes.draw do
end
end
- match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }, via: :get
+ match "/u/:username" => "users#show", as: :user,
+ constraints: {username: /(?:[^.]|\.(?!atom$))+/, format: /atom/}, via: :get
#
# Dashboard Area
@@ -181,15 +182,12 @@ Gitlab::Application.routes.draw do
resources :projects, constraints: { id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/ }, except: [:new, :create, :index], path: "/" do
member do
put :transfer
- post :fork
post :archive
post :unarchive
post :upload_image
post :toggle_star
get :markdown_preview
get :autocomplete_sources
- get :import
- put :retry_import
end
scope module: :projects do
@@ -215,11 +213,11 @@ Gitlab::Application.routes.draw do
match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/}
- resources :snippets, constraints: {id: /\d+/} do
- member do
- get "raw"
- end
+ resources :snippets, constraints: {id: /\d+/} do
+ member do
+ get "raw"
end
+ end
resources :wikis, only: [:show, :edit, :destroy, :create], constraints: {id: /[a-zA-Z.0-9_\-\/]+/} do
collection do
@@ -233,7 +231,10 @@ Gitlab::Application.routes.draw do
end
end
- resource :repository, only: [:show] do
+ resource :fork, only: [:new, :create]
+ resource :import, only: [:new, :create, :show]
+
+ resource :repository, only: [:show, :create] do
member do
get "archive", constraints: { format: Gitlab::Regex.archive_formats_regex }
end
diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example
index 6833082d68b..ea22744fd90 100644
--- a/config/unicorn.rb.example
+++ b/config/unicorn.rb.example
@@ -15,6 +15,7 @@
# Use at least one worker per core if you're on a dedicated server,
# more will usually help for _short_ waits on databases/caches.
+# The minimum is 2
worker_processes 2
# Since Unicorn is never exposed to outside clients, it does not need to
diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb
index ff91e8430a4..b3a6f39c7d5 100644
--- a/db/fixtures/development/12_snippets.rb
+++ b/db/fixtures/development/12_snippets.rb
@@ -1,9 +1,26 @@
Gitlab::Seeder.quiet do
- contents = [
- `curl https://gist.githubusercontent.com/randx/4275756/raw/da2f262920c96d1a970d48bf2e99147954b1f4bd/glus1204.sh`,
- `curl https://gist.githubusercontent.com/randx/3754594/raw/11026a295e6ef3a151c635707a3e1e8e15fc4725/gitlab_setup.sh`,
- `curl https://gist.githubusercontent.com/randx/3065552/raw/29fbd09f4605a5ea22a5a9095e35fd1938dea4d6/gistfile1.sh`,
- ]
+ content =<<eos
+class Member < ActiveRecord::Base
+ include Notifiable
+ include Gitlab::Access
+
+ belongs_to :user
+ belongs_to :source, polymorphic: true
+
+ validates :user, presence: true
+ validates :source, presence: true
+ validates :user_id, uniqueness: { scope: [:source_type, :source_id], message: "already exists in source" }
+ validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
+
+ scope :guests, -> { where(access_level: GUEST) }
+ scope :reporters, -> { where(access_level: REPORTER) }
+ scope :developers, -> { where(access_level: DEVELOPER) }
+ scope :masters, -> { where(access_level: MASTER) }
+ scope :owners, -> { where(access_level: OWNER) }
+
+ delegate :name, :username, :email, to: :user, prefix: true
+end
+eos
(1..50).each do |i|
user = User.all.sample
@@ -12,10 +29,11 @@ Gitlab::Seeder.quiet do
id: i,
author_id: user.id,
title: Faker::Lorem.sentence(3),
- file_name: Faker::Internet.domain_word + '.sh',
- private: [true, false].sample,
- content: contents.sample,
+ file_name: Faker::Internet.domain_word + '.rb',
+ visibility_level: Gitlab::VisibilityLevel.values.sample,
+ content: content,
}])
+
print('.')
end
end
diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb
index e0b13db020d..0755ac714e1 100644
--- a/db/fixtures/production/001_admin.rb
+++ b/db/fixtures/production/001_admin.rb
@@ -1,8 +1,10 @@
-password = if ENV['GITLAB_ROOT_PASSWORD'].blank?
- "5iveL!fe"
- else
- ENV['GITLAB_ROOT_PASSWORD']
- end
+if ENV['GITLAB_ROOT_PASSWORD'].blank?
+ password = '5iveL!fe'
+ expire_time = Time.now
+else
+ password = ENV['GITLAB_ROOT_PASSWORD']
+ expire_time = nil
+end
admin = User.create(
email: "admin@example.com",
@@ -10,7 +12,7 @@ admin = User.create(
username: 'root',
password: password,
password_confirmation: password,
- password_expires_at: Time.now,
+ password_expires_at: expire_time,
theme_id: Gitlab::Theme::MARS
)
diff --git a/db/migrate/20140907220153_serialize_service_properties.rb b/db/migrate/20140907220153_serialize_service_properties.rb
index b95f5b82e03..bd75ab1eacb 100644
--- a/db/migrate/20140907220153_serialize_service_properties.rb
+++ b/db/migrate/20140907220153_serialize_service_properties.rb
@@ -23,7 +23,7 @@ class SerializeServiceProperties < ActiveRecord::Migration
associations[service.type.to_sym].each do |attribute|
service.send("#{attribute}=", service.attributes[attribute.to_s])
end
- service.save!
+ service.save
end
remove_column :services, :project_url, :string
diff --git a/db/migrate/20141006143943_move_slack_service_to_webhook.rb b/db/migrate/20141006143943_move_slack_service_to_webhook.rb
index 4b62b223cbf..a8e07033a5d 100644
--- a/db/migrate/20141006143943_move_slack_service_to_webhook.rb
+++ b/db/migrate/20141006143943_move_slack_service_to_webhook.rb
@@ -10,7 +10,7 @@ class MoveSlackServiceToWebhook < ActiveRecord::Migration
slack_service.properties.delete('subdomain')
# Room is configured on the Slack side
slack_service.properties.delete('room')
- slack_service.save!
+ slack_service.save
end
end
end
diff --git a/db/migrate/20141121133009_add_timestamps_to_members.rb b/db/migrate/20141121133009_add_timestamps_to_members.rb
new file mode 100644
index 00000000000..ef6d4dedf32
--- /dev/null
+++ b/db/migrate/20141121133009_add_timestamps_to_members.rb
@@ -0,0 +1,15 @@
+# In 20140914145549_migrate_to_new_members_model.rb we forgot to set the
+# created_at and updated_at times for new records in the 'members' table. This
+# became a problem after commit c8e78d972a5a628870eefca0f2ccea0199c55bda which
+# was added in GitLab 7.5. With this migration we ensure that all rows in
+# 'members' have at least some created_at and updated_at timestamp.
+class AddTimestampsToMembers < ActiveRecord::Migration
+ def up
+ execute "UPDATE members SET created_at = NOW() WHERE created_at is NULL"
+ execute "UPDATE members SET updated_at = NOW() WHERE updated_at is NULL"
+ end
+
+ def down
+ # no change
+ end
+end
diff --git a/db/migrate/20141121161704_add_identity_table.rb b/db/migrate/20141121161704_add_identity_table.rb
new file mode 100644
index 00000000000..243958039af
--- /dev/null
+++ b/db/migrate/20141121161704_add_identity_table.rb
@@ -0,0 +1,32 @@
+class AddIdentityTable < ActiveRecord::Migration
+ def up
+ create_table :identities do |t|
+ t.string :extern_uid
+ t.string :provider
+ t.references :user
+ end
+
+ add_index :identities, :user_id
+
+ User.where("provider IS NOT NULL").find_each do |user|
+ execute "INSERT INTO identities(provider, extern_uid, user_id) VALUES('#{user.provider}', '#{user.extern_uid}', '#{user.id}')"
+ end
+
+ remove_column :users, :extern_uid
+ remove_column :users, :provider
+ end
+
+ def down
+ add_column :users, :extern_uid, :string
+ add_column :users, :provider, :string
+
+ User.where("id IN(SELECT user_id FROM identities)").find_each do |user|
+ identity = user.identities.last
+ user.extern_uid = identity.extern_uid
+ user.provider = identity.provider
+ user.save
+ end
+
+ drop_table :identities
+ end
+end
diff --git a/db/migrate/20141205134006_add_locked_at_to_merge_request.rb b/db/migrate/20141205134006_add_locked_at_to_merge_request.rb
new file mode 100644
index 00000000000..49651c44a82
--- /dev/null
+++ b/db/migrate/20141205134006_add_locked_at_to_merge_request.rb
@@ -0,0 +1,5 @@
+class AddLockedAtToMergeRequest < ActiveRecord::Migration
+ def change
+ add_column :merge_requests, :locked_at, :datetime
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8ddebc5132a..b8335c5841b 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: 20141007100818) do
+ActiveRecord::Schema.define(version: 20141205134006) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -74,6 +74,14 @@ ActiveRecord::Schema.define(version: 20141007100818) 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: true do |t|
+ t.string "extern_uid"
+ t.string "provider"
+ t.integer "user_id"
+ end
+
+ add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree
+
create_table "issues", force: true do |t|
t.string "title"
t.integer "assignee_id"
@@ -173,6 +181,7 @@ ActiveRecord::Schema.define(version: 20141007100818) do
t.integer "iid"
t.text "description"
t.integer "position", default: 0
+ t.datetime "locked_at"
end
add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree
@@ -350,8 +359,6 @@ ActiveRecord::Schema.define(version: 20141007100818) do
t.string "bio"
t.integer "failed_attempts", default: 0
t.datetime "locked_at"
- t.string "extern_uid"
- t.string "provider"
t.string "username"
t.boolean "can_create_group", default: true, null: false
t.boolean "can_create_team", default: true, null: false
@@ -360,6 +367,7 @@ ActiveRecord::Schema.define(version: 20141007100818) do
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.datetime "confirmed_at"
@@ -367,7 +375,6 @@ ActiveRecord::Schema.define(version: 20141007100818) do
t.string "unconfirmed_email"
t.boolean "hide_no_ssh_key", default: false
t.string "website_url", default: "", null: false
- t.datetime "last_credential_check_at"
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
@@ -375,7 +382,6 @@ ActiveRecord::Schema.define(version: 20141007100818) do
add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree
add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree
add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
- add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree
add_index "users", ["name"], name: "index_users_on_name", using: :btree
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree
add_index "users", ["username"], name: "index_users_on_username", using: :btree
diff --git a/doc/README.md b/doc/README.md
index a8e21f75714..896224fe930 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -2,20 +2,22 @@
## User documentation
-- [API](api/README.md) Explore how you can access GitLab via a simple and powerful API.
-- [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system.
+- [API](api/README.md) Automate GitLab via a simple and powerful API.
+- [Markdown](markdown/markdown.md) GitLab's advanced formatting system.
- [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do.
-- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project.
+- [Project Services](project_services/project_services.md) Integrate a project with external services, such as CI and chat.
+- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to projects.
- [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects.
- [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project.
-- [Workflow](workflow/README.md) Learn how to use Git and GitLab together.
+- [Workflow](workflow/README.md) Learn how to get the maximum out of GitLab.
## Administrator documentation
- [Install](install/README.md) Requirements, directory structures and manual installation.
- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
-- [Raketasks](raketasks/README.md) Explore what GitLab has in store for you to make administration easier.
-- [System hooks](system_hooks/system_hooks.md) Let GitLab notify you when certain management tasks need to be carried out.
+- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
+- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
+- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
- [Update](update/README.md) Update guides to upgrade your installation.
- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
diff --git a/doc/api/README.md b/doc/api/README.md
index f76a253083f..ffe250df3ff 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -21,13 +21,7 @@
## Clients
-- [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP
-- [Laravel API Wrapper for GitLab CE](https://github.com/adamgoose/gitlab) - PHP / [Laravel](http://laravel.com)
-- [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby
-- [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python
-- [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java
-- [node-gitlab](https://github.com/moul/node-gitlab) - Node.js
-- [NGitLab](https://github.com/Scooletz/NGitLab) - .NET
+Find API Clients for GitLab [on our website](https://about.gitlab.com/applications/#api-clients).
## Introduction
@@ -158,7 +152,7 @@ When an attribute is missing, you will get something like:
HTTP/1.1 400 Bad Request
Content-Type: application/json
-
+
{
"message":"400 (Bad request) \"title\" not given"
}
@@ -167,7 +161,7 @@ When a validation error occurs, error messages will be different. They will hold
HTTP/1.1 400 Bad Request
Content-Type: application/json
-
+
{
"message": {
"bio": [
diff --git a/doc/api/branches.md b/doc/api/branches.md
index 74386615545..319f0b47386 100644
--- a/doc/api/branches.md
+++ b/doc/api/branches.md
@@ -211,3 +211,11 @@ Parameters:
It return 200 if succeed, 404 if the branch to be deleted does not exist
or 400 for other reasons. In case of an error, an explaining message is provided.
+
+Success response:
+
+```json
+{
+ "branch_name": "my-removed-branch"
+}
+```
diff --git a/doc/api/commits.md b/doc/api/commits.md
index 9475ecbaa67..eb8d6a43592 100644
--- a/doc/api/commits.md
+++ b/doc/api/commits.md
@@ -93,3 +93,66 @@ Parameters:
}
]
```
+
+## Get the comments of a commit
+
+Get the comments of a commit in a project.
+
+```
+GET /projects/:id/repository/commits/:sha/comments
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `sha` (required) - The name of a repository branch or tag or if not given the default branch
+
+```json
+[
+ {
+ "note": "this code is really nice",
+ "author": {
+ "id": 11,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "state": "active",
+ "created_at": "2014-03-06T08:17:35.000Z"
+ }
+ }
+]
+```
+
+## Post comment to commit
+
+Adds a comment to a commit. Optionally you can post comments on a specific line of a commit. Therefor both `path`, `line_new` and `line_old` are required.
+
+```
+POST /projects/:id/repository/commits/:sha/comments
+```
+
+Parameters:
+
+- `id` (required) - The ID of a project
+- `sha` (required) - The name of a repository branch or tag or if not given the default branch
+- `note` (required) - Text of comment
+- `path` (optional) - The file path
+- `line` (optional) - The line number
+- `line_type` (optional) - The line type (new or old)
+
+```json
+{
+ "author": {
+ "id": 1,
+ "username": "admin",
+ "email": "admin@local.host",
+ "name": "Administrator",
+ "blocked": false,
+ "created_at": "2012-04-29T08:46:00Z"
+ },
+ "note": "text1",
+ "path": "example.rb",
+ "line": 5,
+ "line_type": "new"
+}
+```
diff --git a/doc/api/projects.md b/doc/api/projects.md
index dfe3502b6e4..0055e2e476f 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -186,6 +186,7 @@ Parameters:
"target_id": 830,
"target_type": "Issue",
"author_id": 1,
+ "author_username": "john",
"data": null,
"target_title": "Public project search field"
},
@@ -196,6 +197,7 @@ Parameters:
"target_id": null,
"target_type": null,
"author_id": 1,
+ "author_username": "john",
"data": {
"before": "50d4420237a9de7be1304607147aec22e4a14af7",
"after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428",
@@ -231,6 +233,7 @@ Parameters:
"target_id": 840,
"target_type": "Issue",
"author_id": 1,
+ "author_username": "john",
"data": null,
"target_title": "Finish & merge Code search PR"
}
diff --git a/doc/api/repositories.md b/doc/api/repositories.md
index a412f60c0d9..8acf85d21c8 100644
--- a/doc/api/repositories.md
+++ b/doc/api/repositories.md
@@ -56,6 +56,7 @@ Parameters:
[
{
"name": "v1.0.0",
+ "message": "Release 1.0.0",
"commit": {
"id": "2695effb5807a22ff3d138d593fd856244e155e7",
"parents": [],
@@ -67,10 +68,11 @@ Parameters:
"committed_date": "2012-05-28T04:42:42-07:00",
"committer_email": "jack@example.com"
},
- "protected": false
}
]
```
+The message will be `nil` when creating a lightweight tag otherwise
+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.
diff --git a/doc/api/services.md b/doc/api/services.md
new file mode 100644
index 00000000000..ab9f9c00c67
--- /dev/null
+++ b/doc/api/services.md
@@ -0,0 +1,46 @@
+# Services
+
+## GitLab CI
+
+### Edit GitLab CI service
+
+Set GitLab CI service for a project.
+
+```
+PUT /projects/:id/services/gitlab-ci
+```
+
+Parameters:
+
+- `token` (required) - CI project token
+- `project_url` (required) - CI project url
+
+### Delete GitLab CI service
+
+Delete GitLab CI service settings for a project.
+
+```
+DELETE /projects/:id/services/gitlab-ci
+```
+
+## Hipchat
+
+### Edit Hipchat service
+
+Set Hipchat service for project.
+
+```
+PUT /projects/:id/services/hipchat
+```
+Parameters:
+
+- `token` (required) - Hipchat token
+- `room` (required) - Hipchat room name
+
+### Delete Hipchat service
+
+Delete Hipchat service for a project.
+
+```
+DELETE /projects/:id/services/hipchat
+```
diff --git a/doc/api/users.md b/doc/api/users.md
index 3fdd3a75e88..b30a31deccc 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -78,7 +78,8 @@ GET /users
"is_admin": false,
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
"can_create_group": true,
- "can_create_project": true
+ "can_create_project": true,
+ "projects_limit": 100
}
]
```
@@ -140,7 +141,8 @@ Parameters:
"color_scheme_id": 2,
"is_admin": false,
"can_create_group": true,
- "can_create_project": true
+ "can_create_project": true,
+ "projects_limit": 100
}
```
@@ -240,7 +242,8 @@ GET /user
"color_scheme_id": 2,
"is_admin": false,
"can_create_group": true,
- "can_create_project": true
+ "can_create_project": true,
+ "projects_limit": 100
}
```
@@ -257,12 +260,14 @@ GET /user/keys
{
"id": 1,
"title": "Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "created_at": "2014-08-01T14:47:39.080Z"
},
{
"id": 3,
"title": "Another Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "created_at": "2014-08-01T14:47:39.080Z"
}
]
```
@@ -299,7 +304,8 @@ Parameters:
{
"id": 1,
"title": "Public key",
- "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0="
+ "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=",
+ "created_at": "2014-08-01T14:47:39.080Z"
}
```
diff --git a/doc/development/README.md b/doc/development/README.md
index 20db6662aca..c31e5d7ae97 100644
--- a/doc/development/README.md
+++ b/doc/development/README.md
@@ -4,3 +4,4 @@
- [Shell commands](shell_commands.md) in the GitLab codebase
- [Rake tasks](rake_tasks.md) for development
- [CI setup](ci_setup.md) for testing GitLab
+- [Sidekiq debugging](sidekiq_debugging.md)
diff --git a/doc/development/architecture.md b/doc/development/architecture.md
index c4813d22eaa..209182e7742 100644
--- a/doc/development/architecture.md
+++ b/doc/development/architecture.md
@@ -8,6 +8,38 @@ EE releases are available not long after CE releases. To obtain the GitLab EE th
Both EE and CE require an add-on component called gitlab-shell. It is obtained from the [gitlab-shell repository](https://gitlab.com/gitlab-org/gitlab-shell/tree/master). New versions are usually tags but staying on the master branch will give you the latest stable version. New releases are generally around the same time as GitLab CE releases with exception for informal security updates deemed critical.
+## Physical office analogy
+
+You can imagine GitLab as a physical office.
+
+**The repositories** are the goods GitLab handling.
+They can be stored in a warehouse.
+This can be either a hard disk, or something more complex, such as a NFS filesystem;
+
+**NginX** acts like the front-desk.
+Users come to NginX and request actions to be done by workers in the office;
+
+**The database** is a series of metal file cabinets with information on:
+ - The goods in the warehouse (metadata, issues, merge requests etc);
+ - The users coming to the front desk (permissions)
+
+**Redis** is a communication board with “cubby holes” that can contain tasks for office workers;
+
+**Sidekiq** is a worker that primarily handles sending out emails.
+It takes tasks from the Redis communication board;
+
+**A Unicorn worker** is a worker that handles quick/mundane tasks.
+They work with the communication board (Redis).
+Their job description:
+ - check permissions by checking the user session stored in a Redis “cubby hole”;
+ - make tasks for Sidekiq;
+ - fetch stuff from the warehouse or move things around in there;
+
+**Gitlab-shell** is a third kind of worker that takes orders from a fax machine (SSH) instead of the front desk (HTTP).
+Gitlab-shell communicates with Sidekiq via the “communication board” (Redis), and asks quick questions of the Unicorn workers either directly or via the front desk.
+
+**GitLab Enterprise Edition (the application)** is the collection of processes and business practices that the office is run by.
+
## System Layout
When referring to ~git in the pictures it means the home directory of the git user which is typically /home/git.
diff --git a/doc/development/ci_setup.md b/doc/development/ci_setup.md
index d74f4852a3d..ee16aedafe7 100644
--- a/doc/development/ci_setup.md
+++ b/doc/development/ci_setup.md
@@ -4,7 +4,7 @@ This document describes what services we use for testing GitLab and GitLab CI.
We currently use three CI services to test GitLab:
-1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/2/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce)
+1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/4/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce)
2. GitLab CI at ci.gitlab.org to test the private GitLab B.V. repo at dev.gitlab.org
3. [Semephore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
@@ -25,9 +25,9 @@ We use [these build scripts](https://gitlab.com/gitlab-org/gitlab-ci/blob/master
# Build configuration on [Semaphore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for testing the [GitHub.com repo](https://github.com/gitlabhq/gitlabhq)
-Language: Ruby
-Ruby verion: 2.1.2
-database.yml: pg
+- Language: Ruby
+- Ruby verion: 2.1.2
+- database.yml: pg
Build commands
diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md
index 6d9ac161e91..53f8095cb13 100644
--- a/doc/development/rake_tasks.md
+++ b/doc/development/rake_tasks.md
@@ -1,6 +1,6 @@
# Rake tasks for developers
-## Setup db with developer seeds:
+## Setup db with developer seeds
Note that if your db user does not have advanced privileges you must create the db manually before running this command.
@@ -8,6 +8,10 @@ Note that if your db user does not have advanced privileges you must create the
bundle exec rake setup
```
+The `setup` task is a alias for `gitlab:setup`.
+This tasks calls `db:setup` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and fianlly it calls `db:seed_fu` to seed the database.
+Note: `db:setup` calls `db:seed` but this does nothing.
+
## Run tests
This runs all test suites present in GitLab.
diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md
new file mode 100644
index 00000000000..00867ead80d
--- /dev/null
+++ b/doc/hooks/custom_hooks.md
@@ -0,0 +1,41 @@
+# Custom Git Hooks
+
+**Note: Custom git hooks must be configured on the filesystem of the GitLab
+server. Only GitLab server administrators will be able to complete these tasks.
+Please explore webhooks as an option if you do not have filesystem access.**
+
+Git natively supports hooks that are executed on different actions.
+Examples of server-side git hooks include pre-receive, post-receive, and update.
+See
+[Git SCM Server-Side Hooks](http://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks)
+for more information about each hook type.
+
+As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab
+administrators can add custom git hooks to any GitLab project.
+
+## Setup
+
+Normally, git hooks are placed in the repository or project's `hooks` directory.
+GitLab creates a symlink from each project's `hooks` directory to the
+gitlab-shell `hooks` directory for ease of maintenance between gitlab-shell
+upgrades. As such, custom hooks are implemented a little differently. Behavior
+is exactly the same once the hook is created, though. Follow these steps to
+set up a custom hook.
+
+1. Pick a project that needs a custom git hook.
+1. On the GitLab server, navigate to the project's repository directory.
+For a manual install the path is usually
+`/home/git/repositories/<group>/<project>.git`. For Omnibus installs the path is
+usually `/var/opt/gitlab/git-data/repositories/<group>/<project>.git`.
+1. Create a new directory in this location called `custom_hooks`.
+1. Inside the new `custom_hooks` directory, create a file with a name matching
+the hook type. For a pre-receive hook the file name should be `pre-receive` with
+no extension.
+1. Make the hook file executable and make sure it's owned by git.
+1. Write the code to make the git hook function as expected. Hooks can be
+in any language. Ensure the 'shebang' at the top properly reflects the language
+type. For example, if the script is in Ruby the shebang will probably be
+`#!/usr/bin/env ruby`.
+
+That's it! Assuming the hook code is properly implemented the hook will fire
+as appropriate.
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 821420e8633..263259bc2f9 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -6,9 +6,9 @@ Since a manual installation is a lot of work and error prone we strongly recomme
## Select Version to Install
-Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install. In most cases this should be the highest numbered stable branch (example shown below).
-
-![Select latest branch](https://i.imgur.com/Lrdxk1k.png)
+Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the tag (version) of GitLab you would like to install.
+In most cases this should be the highest numbered production tag (without rc in it).
+You can select the tag in the version dropdown in the top left corner of GitLab (below the menu bar).
If the highest number stable branch is unclear please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version.
@@ -74,8 +74,9 @@ Is the system packaged Git too old? Remove it and compile from source.
# Download and compile from source
cd /tmp
- curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.0.0.tar.gz | tar xz
- cd git-2.0.0/
+ curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.1.2.tar.gz | tar xz
+ cd git-2.1.2/
+ ./configure
make prefix=/usr/local all
# Install into /usr/local/bin
@@ -91,7 +92,7 @@ Then select 'Internet Site' and press enter to confirm the hostname.
## 2. Ruby
-The use of ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system ruby.
+The use of Ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby.
Remove the old Ruby 1.8 if present
@@ -100,8 +101,8 @@ Remove the old Ruby 1.8 if present
Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby
- curl -L --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz
- cd ruby-2.1.2
+ curl -L --progress http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.5.tar.gz | tar xz
+ cd ruby-2.1.5
./configure --disable-install-rdoc
make
sudo make install
@@ -126,7 +127,8 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Login to PostgreSQL
sudo -u postgres psql -d template1
- # Create a user for GitLab.
+ # Create a user for GitLab
+ # Do not type the 'template1=#', this is part of the prompt
template1=# CREATE USER git CREATEDB;
# Create the GitLab production database & grant all privileges on database
@@ -137,6 +139,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Try connecting to the new database with the new user
sudo -u git -H psql -d gitlabhq_production
+
+ # Quit the database session
+ gitlabhq_production> \q
## 5. Redis
@@ -150,6 +155,17 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Enable Redis socket for default Debian / Ubuntu path
echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
+ # Grant permission to the socket to all members of the redis group
+ echo 'unixsocketperm 770' | sudo tee -a /etc/redis/redis.conf
+
+ # Create the directory which contains the socket
+ mkdir /var/run/redis
+ chown redis:redis /var/run/redis
+ chmod 755 /var/run/redis
+ # Persist the directory which contains the socket, if applicable
+ if [ -d /etc/tmpfiles.d ]; then
+ echo 'd /var/run/redis 0755 redis redis 10d -' | sudo tee -a /etc/tmpfiles.d/redis.conf
+ fi
# Activate the changes to redis.conf
sudo service redis-server restart
@@ -165,9 +181,9 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
### Clone the Source
# Clone GitLab repository
- sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-3-stable gitlab
+ sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-5-stable gitlab
-**Note:** You can change `7-3-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
+**Note:** You can change `7-5-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server!
### Configure It
@@ -183,7 +199,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Make sure GitLab can write to the log/ and tmp/ directories
sudo chown -R git log/
sudo chown -R git tmp/
- sudo chmod -R u+rwX log/
+ sudo chmod -R u+rwX,go-w log/
sudo chmod -R u+rwX tmp/
# Create directory for satellites
@@ -262,7 +278,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
GitLab Shell is an SSH access and repository management software developed specially for GitLab.
# Run the installation task for gitlab-shell (replace `REDIS_URL` if needed):
- sudo -u git -H bundle exec rake gitlab:shell:install[v2.0.1] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
+ sudo -u git -H bundle exec rake gitlab:shell:install[v2.2.0] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production
# By default, the gitlab-shell config is generated from your main GitLab config.
# You can review (and modify) the gitlab-shell config as follows:
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 3e4c6a28c0e..660c1adb802 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -24,7 +24,7 @@ For the installations options please see [the installation page on the GitLab we
On the above unsupported distributions is still possible to install GitLab yourself.
Please see the [manual installation guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information.
-### Non Unix operating systems such as Windows
+### Non-Unix operating systems such as Windows
GitLab is developed for Unix operating systems.
GitLab does **not** run on Windows and we have no plans of supporting it in the near future.
@@ -50,11 +50,12 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab
### Memory
-- 512MB is the absolute minimum but we do not recommend this amount of memory.
-You will either need to configure 512MB or 1.5GB of swap space.
-With 512MB of swap space you must configure only one unicorn worker.
-With one unicorn worker only git over ssh access will work because the git over http access requires two running workers (one worker to receive the user request and one worker for the authorization check).
-If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow http access but it will still be slow.
+- 512MB is the absolute minimum but we strongly **advise against** this amount of memory.
+You will need to configure a minimum of 1.5GB of swap space to make the Omnibus package reconfigure run succeed.
+If you use a magnetic (non-SSD) swap drive we recommend to configure only one Unicorn worker.
+With one Unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check).
+If you use a SSD drive you can use two Unicorn workers, this will allow HTTP access although it will be slow.
+Consider installing GitLab on Ubuntu instead of CentOS because sometimes CentOS gives errors during installation and usage with this amount of memory.
- 1GB RAM + 1GB swap supports up to 100 users
- **2GB RAM** is the **recommended** memory size and supports up to 500 users
- 4GB RAM supports up to 2,000 users
@@ -67,7 +68,7 @@ Notice: The 25 workers of Sidekiq will show up as separate processes in your pro
### Storage
-The necessary hard drive space largely depends on the size of the repos you want to store in GitLab. But as a *rule of thumb* you should have at least twice as much free space as your all repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo.
+The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least twice as much free space as all your repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo.
If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them.
@@ -90,7 +91,7 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
## Supported web browsers
- Chrome (Latest stable version)
-- Firefox (Latest released version)
+- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/))
- Safari 7+ (known problem: required fields in html5 do not work)
- Opera (Latest released version)
- IE 10+
diff --git a/doc/integration/gitlab_actions.png b/doc/integration/gitlab_actions.png
new file mode 100644
index 00000000000..b08f54d137b
--- /dev/null
+++ b/doc/integration/gitlab_actions.png
Binary files differ
diff --git a/doc/integration/gitlab_buttons_in_gmail.md b/doc/integration/gitlab_buttons_in_gmail.md
new file mode 100644
index 00000000000..0816509c557
--- /dev/null
+++ b/doc/integration/gitlab_buttons_in_gmail.md
@@ -0,0 +1,28 @@
+# GitLab buttons in gmail
+
+GitLab supports [Google actions in email](https://developers.google.com/gmail/markup/actions/actions-overview).
+
+If correctly setup, emails that require an action will be marked in Gmail.
+
+![gitlab_actions](gitlab_actions.png)
+
+To get this functioning, you need to be registered with Google.
+[See how to register with google in this document.](https://developers.google.com/gmail/markup/registering-with-google)
+
+To aid the registering with google, GitLab offers a rake task that will send an email to google whitelisting email address from your GitLab server.
+
+To check what would be sent to the google email address, run the rake task:
+
+```bash
+bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production
+```
+
+**This will not send the email but give you the output of how the mail will look.**
+
+Copy the output of the rake task to [google email markup tester](https://www.google.com/webmasters/markup-tester/u/0/) and press "Validate".
+
+If you receive "No errors detected" message from the tester you can send the email using:
+
+```bash
+bundle exec rake gitlab:mail_google_schema_whitelisting RAILS_ENV=production SEND=true
+``
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index ee472ac3e3b..56b0d826adb 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -6,6 +6,95 @@ The first time a user signs in with LDAP credentials, GitLab will create a new G
GitLab user attributes such as nickname and email will be copied from the LDAP user entry.
+## Configuring GitLab for LDAP integration
+
+To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
+In GitLab Enterprise Edition you can have multiple LDAP servers connected to one GitLab server.
+
+Please note that before version 7.4, GitLab used a different syntax for configuring LDAP integration.
+The old LDAP integration syntax still works in GitLab 7.4.
+If your `gitlab.rb` or `gitlab.yml` file contains LDAP settings in both the old syntax and the new syntax, only the __old__ syntax will be used by GitLab.
+
+```ruby
+# For omnibus packages
+gitlab_rails['ldap_enabled'] = true
+gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below
+main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
+
+ host: '_your_ldap_server'
+ port: 636
+ uid: 'sAMAccountName'
+ method: 'ssl' # "tls" or "ssl" or "plain"
+ bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
+ password: '_the_password_of_the_bind_user'
+
+ # This setting specifies if LDAP server is Active Directory LDAP server.
+ # For non AD servers it skips the AD specific queries.
+ # If your LDAP server is not AD, set this to false.
+ active_directory: true
+
+ # If allow_username_or_email_login is enabled, GitLab will ignore everything
+ # after the first '@' in the LDAP username submitted by the user on login.
+ #
+ # Example:
+ # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
+ # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
+ #
+ # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
+ # disable this setting, because the userPrincipalName contains an '@'.
+ allow_username_or_email_login: false
+
+ # Base where we can search for users
+ #
+ # Ex. ou=People,dc=gitlab,dc=example
+ #
+ base: ''
+
+ # Filter LDAP users
+ #
+ # Format: RFC 4515 http://tools.ietf.org/search/rfc4515
+ # Ex. (employeeType=developer)
+ #
+ # Note: GitLab does not support omniauth-ldap's custom filter syntax.
+ #
+ user_filter: ''
+
+# GitLab EE only: add more LDAP servers
+# Choose an ID made of a-z and 0-9 . This ID will be stored in the database
+# so that GitLab can remember which LDAP server a user belongs to.
+# uswest2:
+# label:
+# host:
+# ....
+EOS
+```
+
+If you are using a GitLab installation from source you can find the LDAP settings in `/home/git/gitlab/config/gitlab.yml`:
+
+```
+production:
+ # snip...
+ ldap:
+ enabled: false
+ servers:
+ main: # 'main' is the GitLab 'provider ID' of this LDAP server
+ ## label
+ #
+ # A human-friendly name for your LDAP server. It is OK to change the label later,
+ # for instance if you find out it is too large to fit on the web page.
+ #
+ # Example: 'Paris' or 'Acme, Ltd.'
+ label: 'LDAP'
+ # snip...
+```
+
## Enabling LDAP sign-in for existing GitLab users
When a user signs in to GitLab with LDAP for the first time, and their LDAP email address is the primary email address of an existing GitLab user, then the LDAP DN will be associated with the existing user.
@@ -24,15 +113,22 @@ If you want to limit all GitLab access to a subset of the LDAP users on your LDA
The filter must comply with [RFC 4515](http://tools.ietf.org/search/rfc4515).
```ruby
-# For omnibus-gitlab
-gitlab_rails['ldap_user_filter'] = '(employeeType=developer)'
+# For omnibus packages; new LDAP server syntax
+gitlab_rails['ldap_servers'] = YAML.load <<-EOS
+main:
+ # snip...
+ user_filter: '(employeeType=developer)'
+EOS
```
```yaml
-# For installations from source
+# For installations from source; new LDAP server syntax
production:
ldap:
- user_filter: '(employeeType=developer)'
+ servers:
+ main:
+ # snip...
+ user_filter: '(employeeType=developer)'
```
Tip: if you want to limit access to the nested members of an Active Directory group you can use the following syntax:
diff --git a/doc/integration/slack.md b/doc/integration/slack.md
index 95cb0c6fae2..f2e73f272ef 100644
--- a/doc/integration/slack.md
+++ b/doc/integration/slack.md
@@ -4,15 +4,23 @@
To enable Slack integration you must create an Incoming WebHooks integration on Slack;
-1. Sign in to [Slack](https://slack.com) (https://YOURSUBDOMAIN.slack.com/services)
-1. Click on the Integrations menu at the top of the page.
-1. Add a new Integration.
+1. [Sign in to Slack](https://slack.com/signin)
+
+1. Select **Configure Integrations** from the dropdown next to your team name.
+
+1. Select the **All Services** tab
+
+1. Click **Add** next to Incoming Webhooks
+
1. Pick Incoming WebHooks
-1. Choose the channel name you want to send notifications to, in the Settings section
-1. Add Integrations.
- - Optional step; You can change bot's name and avatar by clicking "change the name of your bot", and "change the icon" after that you have to click "Save settings".
-Now, Slack is ready to get external hooks. Before you leave this page don't forget to get the Token that you'll need on GitLab. You can find it by clicking Expand button, located in the "Instructions for creating Incoming WebHooks" section. It's a random alpha-numeric text 24 characters long.
+1. Choose the channel name you want to send notifications to
+
+1. Click **Add Incoming WebHooks Integration**Add Integrations.
+ - Optional step; You can change bot's name and avatar by clicking modifying the bot name or avatar under **Integration Settings**.
+
+1. Copy the **Webhook URL**, we'll need this later for GitLab.
+
## On GitLab
@@ -26,10 +34,8 @@ After Slack is ready we need to setup GitLab. Here are the steps to achieve this
1. Fill in your Slack details
- - Mark as active it
- - Type your subdomain's prefix (If your subdomain is https://somedomain.slack.com you only have to type the somedomain)
- - Type in the token you got from Slack
- - Type in the channel name you want to use (eg. #announcements)
+ - Mark it as active
+ - Paste in the webhook url you got from Slack
Have fun :)
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index 6d96da76ad7..edb7a975503 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -510,6 +510,10 @@ Code above produces next output:
| cell 1 | cell 2 |
| cell 3 | cell 4 |
+**Note**
+
+The row of dashes between the table header and body must have at least three dashes in each column.
+
## References
- This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet).
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index d561868c8bb..e21384d21dc 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -19,6 +19,7 @@ If a user is a GitLab administrator they receive all permissions.
| Create new merge request | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ |
| Push to non-protected branches | | | ✓ | ✓ | ✓ |
+| Force push to non-protected branches | | | ✓ | ✓ | ✓ |
| Remove non-protected branches | | | ✓ | ✓ | ✓ |
| Add tags | | | ✓ | ✓ | ✓ |
| Write a wiki | | | ✓ | ✓ | ✓ |
@@ -35,6 +36,8 @@ If a user is a GitLab administrator they receive all permissions.
| Switch visibility level | | | | | ✓ |
| Transfer project to another namespace | | | | | ✓ |
| Remove project | | | | | ✓ |
+| Force push to protected branches | | | | | |
+| Remove protected branches | | | | | |
## Group
diff --git a/doc/project_services/bamboo.md b/doc/project_services/bamboo.md
new file mode 100644
index 00000000000..51668128c62
--- /dev/null
+++ b/doc/project_services/bamboo.md
@@ -0,0 +1,60 @@
+# Atlassian Bamboo CI Service
+
+GitLab provides integration with Atlassian Bamboo for continuous integration.
+When configured, pushes to a project will trigger a build in Bamboo automatically.
+Merge requests will also display CI status showing whether the build is pending,
+failed, or completed successfully. It also provides a link to the Bamboo build
+page for more information.
+
+Bamboo doesn't quite provide the same features as a traditional build system when
+it comes to accepting webhooks and commit data. There are a few things that
+need to be configured in a Bamboo build plan before GitLab can integrate.
+
+## Setup
+
+### Complete these steps in Bamboo:
+
+1. Navigate to a Bamboo build plan and choose 'Configure plan' from the 'Actions'
+dropdown.
+1. Select the 'Triggers' tab.
+1. Click 'Add trigger'.
+1. Enter a description such as 'GitLab trigger'
+1. Choose 'Repository triggers the build when changes are committed'
+1. Check one or more repositories checkboxes
+1. Enter the GitLab IP address in the 'Trigger IP addresses' box. This is a
+whitelist of IP addresses that are allowed to trigger Bamboo builds.
+1. Save the trigger.
+1. In the left pane, select a build stage. If you have multiple build stages
+you want to select the last stage that contains the git checkout task.
+1. Select the 'Miscellaneous' tab.
+1. Under 'Pattern Match Labelling' put '${bamboo.repository.revision.number}'
+in the 'Labels' box.
+1. Save
+
+Bamboo is now ready to accept triggers from GitLab. Next, set up the Bamboo
+service in GitLab
+
+### Complete these steps in GitLab:
+
+1. Navigate to the project you want to configure to trigger builds.
+1. Select 'Settings' in the top navigation.
+1. Select 'Services' in the left navigation.
+1. Click 'Atlassian Bamboo CI'
+1. Select the 'Active' checkbox.
+1. Enter the base URL of your Bamboo server. 'https://bamboo.example.com'
+1. Enter the build key from your Bamboo build plan. Build keys are a short,
+all capital letter, identifier that is unique. It will be something like PR-BLD
+1. If necessary, enter username and password for a Bamboo user that has
+access to trigger the build plan. Leave these fields blank if you do not require
+authentication.
+1. Save or optionally click 'Test Settings'. Please note that 'Test Settings'
+will actually trigger a build in Bamboo.
+
+## Troubleshooting
+
+If builds are not triggered, these are a couple of things to keep in mind.
+
+1. Ensure you entered the right GitLab IP address in Bamboo under 'Trigger
+IP addresses'.
+1. Remember that GitLab only triggers builds on push events. A commit via the
+web interface will not trigger CI currently.
diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md
new file mode 100644
index 00000000000..20a69a211dd
--- /dev/null
+++ b/doc/project_services/project_services.md
@@ -0,0 +1,18 @@
+# Project Services
+
+__Project integrations with external services for continuous integration and more.__
+
+## Services
+
+- Assemblia
+- [Atlassian Bamboo CI](bamboo.md) An Atlassian product for continous integration.
+- Build box
+- Campfire
+- Emails on push
+- Flowdock
+- Gemnasium
+- GitLab CI
+- Hipchat
+- PivotalTracker
+- Pushover
+- Slack
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index b4581e2a07a..79580029f80 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -14,7 +14,7 @@ You can only restore a backup to exactly the same version of GitLab that you cre
sudo gitlab-rake gitlab:backup:create
# if you've installed GitLab from source or using the cookbook
-bundle exec rake gitlab:backup:create RAILS_ENV=production
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
Example output:
@@ -203,5 +203,31 @@ Add the following lines at the bottom:
```
# Create a full backup of the GitLab repositories and SQL database every day at 4am
-0 4 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production
+0 4 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production CRON=1
```
+
+The `CRON=1` environment setting tells the backup script to suppress all progress output if there are no errors.
+This is recommended to reduce cron spam.
+
+## Alternative backup strategies
+
+If your GitLab server contains a lot of Git repository data you may find the GitLab backup script to be too slow.
+In this case you can consider using filesystem snapshots as part of your backup strategy.
+
+Example: Amazone EBS
+
+> A GitLab server using omnibus-gitlab hosted on Amazon AWS.
+> An EBS drive containing an ext4 filesystem is mounted at `/var/opt/gitlab`.
+> In this case you could make an application backup by taking an EBS snapshot.
+> The backup includes all repositories, uploads and Postgres data.
+
+Example: LVM snapshots + Rsync
+
+> A GitLab server using omnibus-gitlab, with an LVM logical volume mounted at `/var/opt/gitlab`.
+> Replicating the `/var/opt/gitlab` directory usign Rsync would not be reliable because too many files would change while Rsync is running.
+> Instead of rsync-ing `/var/opt/gitlab`, we create a temporary LVM snapshot, which we mount as a read-only filesystem at `/mnt/gitlab_backup`.
+> Now we can have a longer running Rsync job which will create a consistent replica on the remote server.
+> The replica includes all repositories, uploads and Postgres data.
+
+If you are running GitLab on a virtualized server you can possibly also create VM snapshots of the entire GitLab server.
+It is not uncommon however for a VM snapshot to require you to power down the server, so this approach is probably of limited practical use.
diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md
index 39b1a52a44d..bb229e8acbb 100644
--- a/doc/raketasks/import.md
+++ b/doc/raketasks/import.md
@@ -1,28 +1,45 @@
-# Import
+# Import bare repositories into your GitLab instance
-### Import bare repositories into GitLab project instance
+## Notes
-Notes:
+- The owner of the project will be the first admin
+- The groups will be created as needed
+- The owner of the group will be the first admin
+- Existing projects will be skipped
-* project owner will be a first admin
-* groups will be created as needed
-* group owner will be the first admin
-* existing projects will be skipped
+## How to use
-How to use:
+### Create a new folder inside the git repositories path. This will be the name of the new group.
-1. copy your bare repos under git repos_path (see `config/gitlab.yml` gitlab_shell -> repos_path)
-2. run the command below
+- For omnibus-gitlab, it is located at: `/var/opt/gitlab/git-data/repositories` by default, unless you changed
+it in the `/etc/gitlab/gitlab.rb` file.
+- For manual installations, it is usually located at: `/home/git/repositories` or you can see where
+your repositories are located by looking at `config/gitlab.yml` under the `gitlab_shell => repos_path` entry.
+### Copy your bare repositories inside this newly created folder:
+
+```
+$ cp -r /old/git/foo.git/ /home/git/repositories/new_group/
+```
+
+### Run the command below depending on your type of installation:
+
+#### Omnibus Installation
+
+```
+$ sudo gitlab-rake gitlab:import:repos
```
-# omnibus-gitlab
-sudo gitlab-rake gitlab:import:repos
-# installation from source or cookbook
-bundle exec rake gitlab:import:repos RAILS_ENV=production
+#### Manual Installation
+
+Before running this command you need to change the directory to where your GitLab installation is located:
+
+```
+$ cd /home/git/gitlab
+$ sudo -u git -H bundle exec rake gitlab:import:repos RAILS_ENV=production
```
-Example output:
+#### Example output
```
Processing abcd.git
diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md
index f6bd7565799..8bef92e55fe 100644
--- a/doc/raketasks/maintenance.md
+++ b/doc/raketasks/maintenance.md
@@ -122,3 +122,27 @@ sudo -u git -H mkdir -p /home/git/gitlab-satellites
sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production
sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites
```
+
+## Rebuild authorized_keys file
+
+In some case it is necessary to rebuild the `authorized_keys` file.
+
+
+For Omnibus-packages:
+```
+sudo gitlab-rake gitlab:shell:setup
+```
+
+For installations from source:
+```
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production
+```
+
+```
+This will rebuild an authorized_keys file.
+You will lose any data stored in authorized_keys file.
+Do you want to continue (yes/no)? yes
+
+............................
+```
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index c46a3ed9c93..0700f24ab76 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -2,84 +2,92 @@
NOTE: This is a guide for GitLab developers.
-# **15th - Code Freeze & Release Manager**
+# **7 workdays before release - Code Freeze & Release Manager**
-### **1. Stop merging in code, except for important bugfixes**
+### **1. Stop merging in code, except for important bug fixes**
### **2. Release Manager**
-A release manager is selected that coordinates the entire release of this version. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated.
+A release manager is selected that coordinates all releases the coming month, including the patch releases for previous releases.
+The release manager has to make sure all the steps below are done and delegated where necessary.
+This person should also make sure this document is kept up to date and issues are created and updated.
### **3. Create an overall issue**
-Name it "Release x.x.x" for easier searching.
+
+Create issue for GitLab CE project(internal). Name it "Release x.x.x" for easier searching.
+Replace the dates with actual dates based on the number of workdays before the release.
```
-15th:
+Xth:
-* Update the changelog (#LINK)
-* Triage the omnibus-gitlab milestone
+- [ ] Update the CE changelog (#LINK)
+- [ ] Update the EE changelog (#LINK)
+- [ ] Update the CI changelog (#LINK)
+- [ ] Triage the omnibus-gitlab milestone
-16th:
+Xth:
-* Merge CE in to EE (#LINK)
-* Close the omnibus-gitlab milestone
+- [ ] Merge CE in to EE (#LINK)
+- [ ] Close the omnibus-gitlab milestone
-17th:
+Xth:
-* Create x.x.0.rc1 (#LINK)
-* Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package)
+- [ ] Create x.x.0.rc1 (#LINK)
+- [ ] Create x.x.0.rc1-ee (#LINK)
+- [ ] Create CI y.y.0.rc1 (#LINK)
+- [ ] Build package for GitLab.com (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#build-a-package)
-18th:
+Xth:
-* Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package)
-* Regression issue and tweet about rc1 (#LINK)
-* Start blog post (#LINK)
+- [ ] Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package)
+- [ ] Regression issues (CE, CI) and tweet about rc1 (#LINK)
+- [ ] Start blog post (#LINK)
-21th:
+Xth:
-* Do QA and fix anything coming out of it (#LINK)
+- [ ] Do QA and fix anything coming out of it (#LINK)
22nd:
-* Release CE and EE (#LINK)
-
-23rd:
+- [ ] Release CE, EE and CI (#LINK)
-* Prepare package for GitLab.com release (#LINK)
+Xth:
-24th:
+- [ ] Deploy to GitLab.com (#LINK)
-* Deploy to GitLab.com (#LINK)
```
-### **4. Update Changelog**
+### **4. Update changelog**
Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is asked if there is anything missing.
+There are three changelogs that need to be updated: CE, EE and CI.
+
### **5. Take weekend and vacations into account**
Ensure that there is enough time to incorporate the findings of the release candidate, etc.
-# **16th - Merge the CE into EE**
+# **6 workdays before release- Merge the CE into EE**
Do this via a merge request.
-# **17th - Create RC1**
+# **5 workdays before release - Create RC1**
The RC1 release comes with the task to update the installation and upgrade docs. Be mindful that there might already be merge requests for this on GitLab or GitHub.
### **1. Update the installation guide**
1. Check if it references the correct branch `x-x-stable` (doesn't exist yet, but that is okay)
-1. Check the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782)
-1. Check the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794)
+1. Check the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782)
+1. Check the [Git version](/lib/tasks/gitlab/check.rake#L794)
1. There might be other changes. Ask around.
-### **2. Create an update guides**
+### **2. Create update guides**
-1. Create: CE update guide from previous version. Like `from-6-8-to-6.9`
+1. Create: CE update guide from previous version. Like `7.3-to-7.4.md`
1. Create: CE to EE update guide in EE repository for latest version.
-1. Update: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/6.0-to-6.x.md to latest version.
+1. Update: `6.x-or-7.x-to-7.x.md` to latest version.
+1. Create: CI update guide from previous version
It's best to copy paste the previous guide and make changes where necessary.
The typical steps are listed below with any points you should specifically look at.
@@ -98,9 +106,9 @@ List any major changes here, so the user is aware of them before starting to upg
#### 3. Do users need to update dependencies like `git`?
-- Check if the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782) changed since the last release.
+- Check if the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782) changed since the last release.
-- Check if the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794) changed since the last release.
+- Check if the [Git version](/lib/tasks/gitlab/check.rake#L794) changed since the last release.
#### 4. Get latest code
@@ -112,19 +120,19 @@ List any major changes here, so the user is aware of them before starting to upg
Check if any of these changed since last release:
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/nginx/gitlab>
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/nginx/gitlab-ssl>
+- [lib/support/nginx/gitlab](/lib/support/nginx/gitlab)
+- [lib/support/nginx/gitlab-ssl](/lib/support/nginx/gitlab-ssl)
- <https://gitlab.com/gitlab-org/gitlab-shell/commits/master/config.yml.example>
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/gitlab.yml.example>
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/unicorn.rb.example>
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/database.yml.mysql>
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/database.yml.postgresql>
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/initializers/rack_attack.rb.example>
-- <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/config/resque.yml.example>
+- [config/gitlab.yml.example](/config/gitlab.yml.example)
+- [config/unicorn.rb.example](/config/unicorn.rb.example)
+- [config/database.yml.mysql](/config/database.yml.mysql)
+- [config/database.yml.postgresql](/config/database.yml.postgresql)
+- [config/initializers/rack_attack.rb.example](/config/initializers/rack_attack.rb.example)
+- [config/resque.yml.example](/config/resque.yml.example)
#### 8. Need to update init script?
-Check if the `init.d/gitlab` script changed since last release: <https://gitlab.com/gitlab-org/gitlab-ce/commits/master/lib/support/init.d/gitlab>
+Check if the `init.d/gitlab` script changed since last release: [lib/support/init.d/gitlab](/lib/support/init.d/gitlab)
#### 9. Start application
@@ -144,38 +152,59 @@ Make sure the code quality indicators are green / good.
- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq)
-### **4. Set VERSION**
+### **4. Run release tool**
-Change version in VERSION to `x.x.0.rc1`.
+**Make sure EE `master` has latest changes from CE `master`**
-### **5. Tag**
-
-Create an annotated tag that points to the version change commit:
+Get release tools
```
-git tag -a vx.x.0.rc1 -m 'Version x.x.0.rc1'
+git clone git@dev.gitlab.org:gitlab/release-tools.git
+cd release-tools
```
-### **6. Create stable branches**
+Release candidate creates stable branch from master.
+So we need to sync master branch between all CE remotes. Also do same for EE.
-For GitLab EE, append `-ee` to the branch.
+```
+bundle exec rake sync
+```
-`x-x-stable-ee`
+Create release candidate and stable branch:
```
-git checkout master
-git pull
-git checkout -b x-x-stable
-git push <remote> x-x-stable
+bundle exec rake release["x.x.0.rc1"]
```
Now developers can use master for merging new features.
So you should use stable branch for future code chages related to release.
-# **18th - Release RC1**
+### 5. Release GitLab CI RC1
+
+Add to your local `gitlab-ci/.git/config`:
+
+```
+[remote "public"]
+ url = none
+ pushurl = git@dev.gitlab.org:gitlab/gitlab-ci.git
+ pushurl = git@gitlab.com:gitlab-org/gitlab-ci.git
+ pushurl = git@github.com:gitlabhq/gitlab-ci.git
+```
+
+* Create a stable branch `x-y-stable`
+* Bump VERSION to `x.y.0.rc1`
+* `git tag -a v$(cat VERSION) -m "Version $(cat VERSION)"
+* `git push public x-y-stable v$(cat VERSION)`
+
+
+# **4 workdays before release - Release RC1**
+
+### **1. Determine QA person
+
+Notify person of QA day.
-### **1. Update GitLab.com**
+### **2. Update GitLab.com**
Merge the RC1 EE code into GitLab.com.
Once the build is green, create a package.
@@ -183,18 +212,22 @@ If there are big database migrations consider testing them with the production d
Try to deploy in the morning.
It is important to do this as soon as possible, so we can catch any errors before we release the full version.
-### **2. Prepare the blog post**
+### **3. Prepare the blog post**
- Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out.
- Check the changelog of CE and EE for important changes.
+- Also check the CI changelog
+- Add a proposed tweet text to the blog post WIP MR description.
- Create a WIP MR for the blog post
- Ask Dmitriy to add screenshots to the WIP MR.
-- Decide with team who will be the MVP user.
+- Decide with team who will be the MVP user.
+- Create WIP MR for adding MVP to MVP page on website
- Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible.
+- Create a merge request on [GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com/tree/master)
- Assign to one reviewer who will fix spelling issues by editing the branch (can use the online editor)
- After the reviewer is finished the whole team will be mentioned to give their suggestions via line comments
-### **3. Create a regressions issue**
+### **4. Create a regressions issue**
On [the GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues/) create an issue titled "GitLab X.X regressions" add the following text:
@@ -211,7 +244,7 @@ Tweet about the RC release:
> GitLab x.x.0.rc1 is out. This release candidate is only suitable for testing. Please link regressions issues from LINK_TO_REGRESSION_ISSUE
-# **21st - Preparation**
+# **1 workdays before release - Preparation**
### **1. Pre QA merge**
@@ -232,93 +265,59 @@ Create an issue with description of a problem, if it is quick fix fix it yoursel
**NOTE** If there is a problem that cannot be fixed in a timely manner, reverting the feature is an option! If the feature is reverted,
create an issue about it in order to discuss the next steps after the release.
-# **22nd - Release CE and EE**
-
-For GitLab EE, append `-ee` to the branches and tags.
-
-`x-x-stable-ee`
-
-`v.x.x.0-ee`
-
-Note: Merge CE into EE if needed.
-
-### **1. Set VERSION to x.x.x and push**
+# **22nd - Release CE, EE and CI**
-- Change the GITLAB_SHELL_VERSION file in `master` of the CE repository if the version changed.
-- Change the GITLAB_SHELL_VERSION file in `master` of the EE repository if the version changed.
-- Change the VERSION file in `master` branch of the CE repository and commit and push to origin.
-- Change the VERSION file in `master` branch of the EE repository and commit and push to origin.
+**Make sure EE `x-x-stable-ee` has latest changes from CE `x-x-stable`**
-### **2. Update installation.md**
-Update [installation.md](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) to the newest version in master.
+### **1. Release code**
-### **3. Push latest changes from x-x-stable branch to dev.gitlab.org**
+Get release tools
```
-git checkout -b x-x-stable
-git push origin x-x-stable
+git clone git@dev.gitlab.org:gitlab/release-tools.git
+cd release-tools
```
-### **4. Build the Omnibus packages**
-
-Follow the [release doc in the Omnibus repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md).
-This can happen before tagging because Omnibus uses tags in its own repo and SHA1's to refer to the GitLab codebase.
-
-### **5. Create annotated tag vx.x.x**
-
-In `x-x-stable` branch check for the SHA-1 of the commit with VERSION file changed. Tag that commit,
+Bump version, create release tag and push to remotes:
```
-git tag -a vx.x.0 -m 'Version x.x.0' xxxxx
+bundle exec rake release["x.x.0"]
```
-where `xxxxx` is SHA-1.
+Also perform these steps for GitLab CI:
-### **6. Push the tag and x-x-stable branch to the remotes**
+- bump version in the stable branch
+- create annotated tag
+- push the stable branch and the annotated tag to the public repositories
-For GitLab CE, push to dev, GitLab.com and GitHub.
+### **2. Update installation.md**
-For GitLab EE, push to the subscribers repo.
+Update [installation.md](/doc/install/installation.md) to the newest version in master.
-Make sure the branch is marked 'protected' on each of the remotes you pushed to.
-```
-git push <remote> x-x-stable(-ee)
-git push <remote> vx.x.0
-```
+### **3. Build the Omnibus packages**
+
+Follow the [release doc in the Omnibus repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md).
+This can happen before tagging because Omnibus uses tags in its own repo and SHA1's to refer to the GitLab codebase.
+
-### **7. Publish packages for new release**
+### **4. Publish packages for new release**
Update `downloads/index.html` and `downloads/archive/index.html` in `www-gitlab-com` repository.
-### **8. Publish blog for new release**
+### **5. Publish blog for new release**
Merge the [blog merge request](#1-prepare-the-blog-post) in `www-gitlab-com` repository.
-### **9. Tweet to blog**
+### **6. Tweet to blog**
Send out a tweet to share the good news with the world.
List the most important features and link to the blog post.
Proposed tweet for CE "GitLab X.X is released! It brings *** <link-to-blogpost>"
-### **10. Send out the newsletter**
-
-Send out an email to the 'GitLab Newsletter' mailing list on MailChimp.
-Replicate the former release newsletter and modify it accordingly.
-**Do not forget to edit `Subject line` and regenerate `Plain-Text Email` from HTML source**
-
-Include a link to the blog post and keep it short.
-
-Proposed email text:
-"We have released a new version of GitLab. See our blog post(<link>) for more information."
-
-
-# **23rd - Optional Patch Release**
-
-# **24th - Update GitLab.com**
-
-Merge the stable release into GitLab.com. Once the build is green deploy the next morning.
+# **1 workday after release - Update GitLab.com**
-# **25th - Release GitLab CI**
+- Build a package for gitlab.com based on the official release instead of RC1
+- Deploy the package (should not need downtime because of the small difference with RC1)
diff --git a/doc/release/patch.md b/doc/release/patch.md
index bcc14568fc8..2bd34b7d822 100644
--- a/doc/release/patch.md
+++ b/doc/release/patch.md
@@ -10,22 +10,47 @@ Otherwise include it in the monthly release and note there was a regression fix
## Release Procedure
+### Preparation
+
1. Verify that the issue can be reproduced
1. Note in the 'GitLab X.X regressions' that you will create a patch
1. Create an issue on private GitLab development server
1. Name the issue "Release X.X.X CE and X.X.X EE", this will make searching easier
1. Fix the issue on a feature branch, do this on the private GitLab development server
+1. If it is a security issue, then assign it to the release manager and apply a 'security' label
+1. Build the package for GitLab.com and do a deploy
1. Consider creating and testing workarounds
1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch
+1. Make sure that the build has passed and all tests are passing
1. In a separate commit in the stable branch update the CHANGELOG
1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X"
-1. In a separate commit in the stable branch update the VERSION
-1. Create an annotated tag vX.X.X for CE and another patch release for EE `git tag -a vx.x.x -m 'Version x.x.x'`
-1. Make sure that the build has passed and all tests are passing
-1. Push the code and the tags to all the CE and EE repositories
+
+### Bump version
+
+Get release tools
+
+```
+git clone git@dev.gitlab.org:gitlab/release-tools.git
+cd release-tools
+```
+
+Bump version in stable branch, create release tag and push to remotes:
+
+```
+bundle exec rake release["x.x.x"]
+```
+
+Or if you need to release only EE:
+
+```
+CE=false be rake release['x.x.x']
+```
+
+### Release
+
1. Apply the patch to GitLab Cloud and the private GitLab development server
1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md)
1. Cherry-pick the changelog update back into master
-1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing as well as the link to the changelog
+1. Create and publish a blog post
+1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing and link to the blog post
1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only)
-1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the patch is EE only)
diff --git a/doc/release/security.md b/doc/release/security.md
index 79d23c02ea4..b67e0f37a04 100644
--- a/doc/release/security.md
+++ b/doc/release/security.md
@@ -14,11 +14,13 @@ Please report suspected security vulnerabilities in private to <support@gitlab.c
1. Verify that the issue can be reproduced
1. Acknowledge the issue to the researcher that disclosed it
+1. Inform the release manager that there needs to be a security release
1. Do the steps from [patch release document](doc/release/patch.md), starting with "Create an issue on private GitLab development server"
+1. The MR with the security fix should get a 'security' label and be assigned to the release manager
+1. Build the package for GitLab.com and do a deploy
1. Create feature branches for the blog post on GitLab.com and link them from the code branch
1. Merge and publish the blog posts
1. Send tweets about the release from `@gitlabhq`
-1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the security fix is for EE only)
1. Send out an email to [the community google mailing list](https://groups.google.com/forum/#!forum/gitlabhq)
1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number
1. Add the security researcher to the [Security Researcher Acknowledgments list](http://about.gitlab.com/vulnerability-acknowledgements/)
diff --git a/doc/sidekiq_debugging.md b/doc/sidekiq_debugging.md
new file mode 100644
index 00000000000..cea11e5f126
--- /dev/null
+++ b/doc/sidekiq_debugging.md
@@ -0,0 +1,14 @@
+# Sidekiq debugging
+
+## Log arguments to Sidekiq jobs
+
+If you want to see what arguments are being passed to Sidekiq jobs you can set
+the SIDEKIQ_LOG_ARGUMENTS environment variable.
+
+```
+SIDEKIQ_LOG_ARGUMENTS=1 bundle exec foreman start
+```
+
+It is not recommend to enable this setting in production because some Sidekiq
+jobs (such as sending a password reset email) take secret arguments (for
+example the password reset token).
diff --git a/doc/update/6.x-or-7.x-to-7.3.md b/doc/update/6.x-or-7.x-to-7.5.md
index fe3530ef9c1..c9b95c62611 100644
--- a/doc/update/6.x-or-7.x-to-7.3.md
+++ b/doc/update/6.x-or-7.x-to-7.5.md
@@ -1,6 +1,6 @@
-# From 6.x or 7.x to 7.3
+# From 6.x or 7.x to 7.5
-This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.3.
+This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.5.
## Global issue numbers
@@ -13,7 +13,11 @@ possible to edit the label text and color. The characters `?`, `&` and `,` are
no longer allowed however so those will be removed from your tags during the
database migrations for GitLab 7.2.
-## 0. Backup
+## 0. Stop server
+
+ sudo service gitlab stop
+
+## 1. Backup
It's useful to make a backup just in case things go south:
(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version)
@@ -23,10 +27,6 @@ cd /home/git/gitlab
sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
```
-## 1. Stop server
-
- sudo service gitlab stop
-
## 2. Update Ruby
If you are still using Ruby 1.9.3 or below, you will need to update Ruby.
@@ -34,7 +34,7 @@ You can check which version you are running with `ruby -v`.
If you are you running Ruby 2.0.x, you do not need to upgrade ruby, but can consider doing so for performance reasons.
-If you are running Ruby 2.1.1 consider upgrading to 2.1.2, because of the high memory usage of Ruby 2.1.1.
+If you are running Ruby 2.1.1 consider upgrading to 2.1.5, because of the high memory usage of Ruby 2.1.1.
Install, update dependencies:
@@ -46,8 +46,8 @@ Download and compile Ruby:
```bash
mkdir /tmp/ruby && cd /tmp/ruby
-curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz
-cd ruby-2.1.2
+curl --progress http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.5.tar.gz | tar xz
+cd ruby-2.1.5
./configure --disable-install-rdoc
make
sudo make install
@@ -70,7 +70,7 @@ sudo -u git -H git checkout -- db/schema.rb # local changes will be restored aut
For GitLab Community Edition:
```bash
-sudo -u git -H git checkout 7-3-stable
+sudo -u git -H git checkout 7-5-stable
```
OR
@@ -78,7 +78,7 @@ OR
For GitLab Enterprise Edition:
```bash
-sudo -u git -H git checkout 7-3-stable-ee
+sudo -u git -H git checkout 7-5-stable-ee
```
## 4. Install additional packages
@@ -99,6 +99,8 @@ sudo apt-get install pkg-config cmake
sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf
# Enable Redis socket for default Debian / Ubuntu path
echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
+ # Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0).
+ sudo sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf
# Activate the changes to redis.conf
sudo service redis-server restart
# Add git to the redis group
@@ -117,7 +119,7 @@ sudo apt-get install pkg-config cmake
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v2.0.1
+sudo -u git -H git checkout v2.2.0
```
## 7. Install libs, migrations, etc.
@@ -152,14 +154,14 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
TIP: to see what changed in `gitlab.yml.example` in this release use next command:
```
-git diff 6-0-stable:config/gitlab.yml.example 7-3-stable:config/gitlab.yml.example
+git diff 6-0-stable:config/gitlab.yml.example 7-5-stable:config/gitlab.yml.example
```
-* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/config/unicorn.rb.example but with your settings.
-* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.0.0/config.yml.example but with your settings.
-* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab-ssl but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/config/unicorn.rb.example but with your settings.
+* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.2.0/config.yml.example but with your settings.
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/lib/support/nginx/gitlab but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/lib/support/nginx/gitlab-ssl but with your settings.
* Copy rack attack middleware config
```bash
@@ -196,6 +198,77 @@ When using Google omniauth login, changes of the Google account required.
Ensure that `Contacts API` and the `Google+ API` are enabled in the [Google Developers Console](https://console.developers.google.com/).
More details can be found at the [integration documentation](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/google.md).
+## 12. Optional optimizations for GitLab setups with MySQL databases
+
+Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand.
+
+```
+# Stop GitLab
+sudo service gitlab stop
+
+# Secure your MySQL installation (added in GitLab 6.2)
+sudo mysql_secure_installation
+
+# Login to MySQL
+mysql -u root -p
+
+# do not type the 'mysql>', this is part of the prompt
+
+# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8)
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all outputed SQL statements
+
+# Convert all tables to correct character set
+SET foreign_key_checks = 0;
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `TABLE_COLLATION` <> 'utf8_unicode_ci' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all outputed SQL statements
+
+# turn foreign key checks back on
+SET foreign_key_checks = 1;
+
+# Find MySQL users
+mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%';
+
+# If git user exists and gitlab user does not exist
+# you are done with the database cleanup tasks
+mysql> \q
+
+# If both users exist skip to Delete gitlab user
+
+# Create new user for GitLab (changed in GitLab 6.4)
+# change $password in the command below to a real password you pick
+mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
+
+# Grant the git user necessary permissions on the database
+mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost';
+
+# Delete the old gitlab user
+mysql> DELETE FROM mysql.user WHERE user='gitlab';
+
+# Quit the database session
+mysql> \q
+
+# Try connecting to the new database with the new user
+sudo -u git -H mysql -u git -p -D gitlabhq_production
+
+# Type the password you replaced $password with earlier
+
+# You should now see a 'mysql>' prompt
+
+# Quit the database session
+mysql> \q
+
+# Update database configuration details
+# See config/database.yml.mysql for latest recommended configuration details
+# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8)
+# Set production -> pool: 10 (updated in GitLab 5.3)
+# Set production -> username: git
+# Set production -> password: the password your replaced $password with earlier
+sudo -u git -H editor /home/git/gitlab/config/database.yml
+```
+
## Things went south? Revert to previous version (6.0)
### 1. Revert the code to the previous version
diff --git a/doc/update/7.2-to-7.3.md b/doc/update/7.2-to-7.3.md
index 44f3f8f1a38..ebdd4ff60fa 100644
--- a/doc/update/7.2-to-7.3.md
+++ b/doc/update/7.2-to-7.3.md
@@ -74,7 +74,7 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
# Enable Redis socket for default Debian / Ubuntu path
echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf
# Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0).
- sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf
+ sudo sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf
# Activate the changes to redis.conf
sudo service redis-server restart
# Add git to the redis group
diff --git a/doc/update/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md
index ba3be5e53b6..2466050ea4c 100644
--- a/doc/update/7.3-to-7.4.md
+++ b/doc/update/7.3-to-7.4.md
@@ -1,18 +1,108 @@
# From 7.3 to 7.4
-## GitLab 7.4 has not been released yet!
+### 0. Stop server
-This document currently just serves as a place to keep track of updates that will be needed for the 7.4 update.
+ sudo service gitlab stop
-## Update config files
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-4-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-4-stable-ee
+```
+
+### 3. Install libs, migrations, etc.
+
+```bash
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 4. Update config files
+
+#### New configuration options for gitlab.yml
+
+There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml.
+
+```
+git diff origin/7-3-stable:config/gitlab.yml.example origin/7-4-stable:config/gitlab.yml.example
+```
+
+#### Change timeout for unicorn
+
+```
+# set timeout to 60
+sudo -u git -H editor config/unicorn.rb
+```
+
+#### Change nginx https settings
+
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your setting
+
+#### MySQL Databases: Update database.yml config file
* Add `collation: utf8_general_ci` to config/database.yml as seen in [config/database.yml.mysql](config/database.yml.mysql)
-## Optional optimizations for GitLab setups with MySQL databases
+
+### 5. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 6. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+
+### 7. Optional optimizations for GitLab setups with MySQL databases
Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand.
```
+# Stop GitLab
+sudo service gitlab stop
+
# Secure your MySQL installation (added in GitLab 6.2)
sudo mysql_secure_installation
@@ -74,4 +164,30 @@ mysql> \q
# Set production -> username: git
# Set production -> password: the password your replaced $password with earlier
sudo -u git -H editor /home/git/gitlab/config/database.yml
+
+# Start GitLab
+sudo service gitlab start
+sudo service nginx restart
+
+# Run thorough check
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
```
+
+
+## Things went south? Revert to previous version (7.3)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.2 to 7.3](7.2-to-7.3.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
+
+
+
+
diff --git a/doc/update/7.4-to-7.5.md b/doc/update/7.4-to-7.5.md
new file mode 100644
index 00000000000..c12becc1e14
--- /dev/null
+++ b/doc/update/7.4-to-7.5.md
@@ -0,0 +1,195 @@
+# From 7.4 to 7.5
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-5-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-5-stable-ee
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.2.0
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Update config files
+
+#### New configuration options for gitlab.yml
+
+There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml.
+
+```
+git diff origin/7-4-stable:config/gitlab.yml.example origin/7-5-stable:config/gitlab.yml.example
+```
+
+#### Change timeout for unicorn
+
+```
+# set timeout to 60
+sudo -u git -H editor config/unicorn.rb
+```
+
+#### Change nginx https settings
+
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-5-stable/lib/support/nginx/gitlab-ssl but with your setting
+
+#### MySQL Databases: Update database.yml config file
+
+* Add `collation: utf8_general_ci` to config/database.yml as seen in [config/database.yml.mysql](config/database.yml.mysql)
+
+
+### 6. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+
+### 8. Optional optimizations for GitLab setups with MySQL databases
+
+Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand.
+
+```
+# Stop GitLab
+sudo service gitlab stop
+
+# Secure your MySQL installation (added in GitLab 6.2)
+sudo mysql_secure_installation
+
+# Login to MySQL
+mysql -u root -p
+
+# do not type the 'mysql>', this is part of the prompt
+
+# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8)
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all outputed SQL statements
+
+# Convert all tables to correct character set
+SET foreign_key_checks = 0;
+SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `TABLE_COLLATION` <> 'utf8_unicode_ci' AND `TABLE_TYPE` = 'BASE TABLE';
+
+# If previous query returned results, copy & run all outputed SQL statements
+
+# turn foreign key checks back on
+SET foreign_key_checks = 1;
+
+# Find MySQL users
+mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%';
+
+# If git user exists and gitlab user does not exist
+# you are done with the database cleanup tasks
+mysql> \q
+
+# If both users exist skip to Delete gitlab user
+
+# Create new user for GitLab (changed in GitLab 6.4)
+# change $password in the command below to a real password you pick
+mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password';
+
+# Grant the git user necessary permissions on the database
+mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost';
+
+# Delete the old gitlab user
+mysql> DELETE FROM mysql.user WHERE user='gitlab';
+
+# Quit the database session
+mysql> \q
+
+# Try connecting to the new database with the new user
+sudo -u git -H mysql -u git -p -D gitlabhq_production
+
+# Type the password you replaced $password with earlier
+
+# You should now see a 'mysql>' prompt
+
+# Quit the database session
+mysql> \q
+
+# Update database configuration details
+# See config/database.yml.mysql for latest recommended configuration details
+# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8)
+# Set production -> pool: 10 (updated in GitLab 5.3)
+# Set production -> username: git
+# Set production -> password: the password your replaced $password with earlier
+sudo -u git -H editor /home/git/gitlab/config/database.yml
+
+# Run thorough check
+sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+```
+
+
+## Things went south? Revert to previous version (7.4)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.3 to 7.4](7.3-to-7.4.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/7.5-to-7.6.md b/doc/update/7.5-to-7.6.md
new file mode 100644
index 00000000000..deee73fe560
--- /dev/null
+++ b/doc/update/7.5-to-7.6.md
@@ -0,0 +1,114 @@
+# From 7.5 to 7.6
+
+**7.6 is not yet released. This is a preliminary upgrade guide.**
+
+### 0. Stop server
+
+ sudo service gitlab stop
+
+### 1. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 2. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-6-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-6-stable-ee
+```
+
+### 3. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.2.0
+```
+
+### 4. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 5. Update config files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+
+```
+git diff origin/7-5-stable:config/gitlab.yml.example origin/7-6-stable:config/gitlab.yml.example
+```
+
+#### Change Nginx settings
+
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting
+
+#### Setup time zone (optional)
+
+Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it.
+
+### 6. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 7. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations upgrade is complete!
+
+## Things went south? Revert to previous version (7.5)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.4 to 7.5](7.4-to-7.5.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index c4a77d12800..629c46ad030 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -26,16 +26,14 @@ sudo -u git -H git checkout LATEST_TAG
Replace LATEST_TAG with the latest GitLab tag you want to upgrade to, for example `v6.6.3`.
-### 3. Update gitlab-shell if it is not the latest version
+### 3. Update gitlab-shell to the corresponding version
```bash
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout LATEST_TAG
+sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`
```
-Replace LATEST_TAG with the latest GitLab Shell tag you want to upgrade to, for example `v1.7.9`.
-
### 4. Install libs, migrations, etc.
```bash
diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md
index cf59b0e461c..0a9f242d9ab 100644
--- a/doc/update/upgrader.md
+++ b/doc/update/upgrader.md
@@ -10,6 +10,8 @@ If you have local changes to your GitLab repository the script will stash them a
**GitLab Upgrader is available only for GitLab version 6.4.2 or higher.**
+**This script does NOT update gitlab-shell, it needs manual update. See step 5 below.**
+
## 0. Backup
cd /home/git/gitlab
@@ -43,28 +45,31 @@ Check if GitLab and its dependencies are configured correctly:
If all items are green, then congratulations upgrade is complete!
-## 5. Upgrade GitLab Shell (if needed)
-
-If the `gitlab:check` task reports an outdated version of `gitlab-shell` you should upgrade it.
+## 5. Upgrade GitLab Shell
-Upgrade it by running the commands below after replacing 2.0.1 with the correct version number:
+GitLab Shell might be outdated, running the commands below ensures you're using a compatible version:
```
cd /home/git/gitlab-shell
sudo -u git -H git fetch
-sudo -u git -H git checkout v2.0.1
+sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`
```
## One line upgrade command
You've read through the entire guide and probably already did all the steps one by one.
-Here is a one line command with step 1 to 4 for the next time you upgrade:
+Here is a one line command with step 1 to 5 for the next time you upgrade:
```bash
-cd /home/git/gitlab; sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \
+cd /home/git/gitlab; \
+ sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \
sudo service gitlab stop; \
if [ -f bin/upgrade.rb ]; then sudo -u git -H ruby bin/upgrade.rb -y; else sudo -u git -H ruby script/upgrade.rb -y; fi; \
+ cd /home/git/gitlab-shell; \
+ sudo -u git -H git fetch; \
+ sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`; \
+ cd /home/git/gitlab; \
sudo service gitlab start; \
sudo service nginx restart; sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
```
diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md
index 31791da8074..f19517c0f18 100644
--- a/doc/web_hooks/web_hooks.md
+++ b/doc/web_hooks/web_hooks.md
@@ -63,6 +63,11 @@ Triggered when a new issue is created or an existing issue was updated/closed/re
```json
{
"object_kind": "issue",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
"object_attributes": {
"id": 301,
"title": "New API: create/update/delete file",
@@ -92,6 +97,11 @@ Triggered when a new merge request is created or an existing merge request was u
```json
{
"object_kind": "merge_request",
+ "user": {
+ "name": "Administrator",
+ "username": "root",
+ "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon"
+ },
"object_attributes": {
"id": 99,
"target_branch": "master",
diff --git a/docker/Dockerfile b/docker/Dockerfile
new file mode 100644
index 00000000000..aea59916c7a
--- /dev/null
+++ b/docker/Dockerfile
@@ -0,0 +1,34 @@
+FROM ubuntu:14.04
+
+# Install required packages
+RUN apt-get update -q \
+ && DEBIAN_FRONTEND=noninteractive apt-get install -qy \
+ openssh-server \
+ wget \
+ && apt-get clean
+
+# Download & Install GitLab
+# If the Omnibus package version below is outdated please contribute a merge request to update it.
+# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
+RUN TMP_FILE=$(mktemp); \
+ wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.5.2-omnibus.5.2.1.ci-1_amd64.deb \
+ && dpkg -i $TMP_FILE \
+ && rm -f $TMP_FILE
+
+# Manage SSHD through runit
+RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
+ && mkfifo /opt/gitlab/sv/sshd/supervise/ok \
+ && printf "#!/bin/sh\nexec 2>&1\numask 077\nexec /usr/sbin/sshd -D" > /opt/gitlab/sv/sshd/run \
+ && chmod a+x /opt/gitlab/sv/sshd/run \
+ && ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \
+ && mkdir -p /var/run/sshd
+
+# Expose web & ssh
+EXPOSE 80 22
+
+# Volume & configuration
+VOLUME ["/var/opt/gitlab", "/var/log/gitlab", "/etc/gitlab"]
+ADD gitlab.rb /etc/gitlab/
+
+# Default is to run runit & reconfigure
+CMD gitlab-ctl reconfigure & /opt/gitlab/embedded/bin/runsvdir-start
diff --git a/docker/README.md b/docker/README.md
new file mode 100644
index 00000000000..1fbf703e25c
--- /dev/null
+++ b/docker/README.md
@@ -0,0 +1,68 @@
+What is GitLab?
+===============
+
+GitLab offers git repository management, code reviews, issue tracking, activity feeds, wikis. It has LDAP/AD integration, handles 25,000 users on a single server but can also run on a highly available active/active cluster. A subscription gives you access to our support team and to GitLab Enterprise Edition that contains extra features aimed at larger organizations.
+
+<https://about.gitlab.com>
+
+![GitLab Logo](https://gitlab.com/uploads/appearance/logo/1/brand_logo-c37eb221b456bb4b472cc1084480991f.png)
+
+
+How to use this image
+======================
+
+At this moment GitLab doesn't have official Docker images.
+Build your own based on the Omnibus packages with the following command (it assumes you're in the GitLab repo root directory):
+
+```bash
+sudo docker build --tag gitlab_image docker/
+```
+
+We assume using a data volume container, this will simplify migrations and backups.
+This empty container will exist to persist as volumes the 3 directories used by GitLab, so remember not to delete it.
+
+The directories on data container are:
+
+- `/var/opt/gitlab` for application data
+- `/var/log/gitlab` for logs
+- `/etc/gitlab` for configuration
+
+Create the data container with:
+
+```bash
+sudo docker run --name gitlab_data gitlab_image /bin/true
+```
+
+After creating this run GitLab:
+
+```bash
+sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image
+```
+
+It might take a while before the docker container is responding to queries. You can follow the configuration process with `docker logs -f gitlab`.
+
+You can then go to `http://localhost:8080/` (or `http://192.168.59.103:8080/` if you use boot2docker).
+You can login with username `root` and password `5iveL!fe`.
+Next time, you can just use `sudo docker start gitlab_app` and `sudo docker stop gitlab_app`.
+
+
+How to configure GitLab
+========================
+
+This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`.
+
+To access GitLab configuration, you can start an interactive command line in a new container using the shared data volume container, you will be able to browse the 3 directories and use your favorite text editor:
+
+```bash
+docker run -ti -e TERM=linux --rm --volumes-from gitlab_data ubuntu
+vi /etc/gitlab/gitlab.rb
+```
+
+**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab.
+
+You can find all available options in [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration).
+
+
+Troubleshooting
+=========================
+Please see the [troubleshooting](troubleshooting.md) file in this directory.
diff --git a/docker/gitlab.rb b/docker/gitlab.rb
new file mode 100644
index 00000000000..7fddf309c01
--- /dev/null
+++ b/docker/gitlab.rb
@@ -0,0 +1,37 @@
+# External URL should be your Docker instance.
+# By default, this example is the "standard" boot2docker IP.
+# Always use port 80 here to force the internal nginx to bind port 80,
+# even if you intend to use another port in Docker.
+external_url "http://192.168.59.103/"
+
+# Prevent Postgres from trying to allocate 25% of total memory
+postgresql['shared_buffers'] = '1MB'
+
+# Configure GitLab to redirect PostgreSQL logs to the data volume
+postgresql['log_directory'] = '/var/log/gitlab/postgresql'
+
+# Some configuration of GitLab
+# You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration
+gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
+gitlab_rails['gitlab_support_email'] = 'support@example.com'
+gitlab_rails['time_zone'] = 'Europe/Paris'
+
+# SMTP settings
+# You must use an external server, the Docker container does not install an SMTP server
+gitlab_rails['smtp_enable'] = true
+gitlab_rails['smtp_address'] = "smtp.example.com"
+gitlab_rails['smtp_port'] = 587
+gitlab_rails['smtp_user_name'] = "user"
+gitlab_rails['smtp_password'] = "password"
+gitlab_rails['smtp_domain'] = "example.com"
+gitlab_rails['smtp_authentication'] = "plain"
+gitlab_rails['smtp_enable_starttls_auto'] = true
+
+# Enable LDAP authentication
+# gitlab_rails['ldap_enabled'] = true
+# gitlab_rails['ldap_host'] = 'ldap.example.com'
+# gitlab_rails['ldap_port'] = 389
+# gitlab_rails['ldap_method'] = 'plain' # 'ssl' or 'plain'
+# gitlab_rails['ldap_allow_username_or_email_login'] = false
+# gitlab_rails['ldap_uid'] = 'uid'
+# gitlab_rails['ldap_base'] = 'ou=users,dc=example,dc=com'
diff --git a/docker/troubleshooting.md b/docker/troubleshooting.md
new file mode 100644
index 00000000000..b1b70de5997
--- /dev/null
+++ b/docker/troubleshooting.md
@@ -0,0 +1,63 @@
+# Troubleshooting
+
+This is to troubleshoot https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/245
+But it might contain useful commands for other cases as well.
+
+The configuration to add the postgres log in vim is:
+postgresql['log_directory'] = '/var/log/gitlab/postgresql'
+
+# Commands
+
+```bash
+sudo docker build --tag gitlab_image docker/
+
+sudo docker rm -f gitlab_app
+sudo docker rm -f gitlab_data
+
+sudo docker run --name gitlab_data gitlab_image /bin/true
+
+sudo docker run -ti --rm --volumes-from gitlab_data ubuntu apt-get update && sudo apt-get install -y vim && sudo vim /etc/gitlab/gitlab.rb
+
+sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image
+
+sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log
+
+sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/postgresql/current
+
+sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
+
+sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab.rb
+```
+
+# Interactively
+
+```bash
+# First start a GitLab container without starting GitLab
+# This is almost the same as starting the GitLab container except:
+# - we run interactively (-t -i)
+# - we define TERM=linux because it allows to use arrow keys in vi (!!!)
+# - we choose another startup command (bash)
+sudo docker run -ti -e TERM=linux --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image bash
+
+# Configure GitLab to redirect PostgreSQL logs
+echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb
+
+# Prevent Postgres from allocating 25% of total memory
+echo "postgresql['shared_buffers'] = '1MB'" >> /etc/gitlab/gitlab.rb
+
+# You can now start GitLab manually from Bash (in the background)
+# Maybe the command below is still missing something to run in the background
+gitlab-ctl reconfigure > /var/log/gitlab/reconfigure.log & /opt/gitlab/embedded/bin/runsvdir-start &
+
+# Inspect PostgreSQL config
+cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
+
+# And tail the logs (PostgreSQL log may not exist immediately)
+tail -f /var/log/gitlab/reconfigure.log /var/log/gitlab/postgresql/current
+
+# And get the memory
+cat /proc/meminfo
+head /proc/sys/kernel/shmmax /proc/sys/kernel/shmall
+free -m
+
+```
diff --git a/features/admin/groups.feature b/features/admin/groups.feature
index 1a465c1be55..aa365a6ea1a 100644
--- a/features/admin/groups.feature
+++ b/features/admin/groups.feature
@@ -20,3 +20,10 @@ Feature: Admin Groups
When I visit admin group page
When I select user "John Doe" from user list as "Reporter"
Then I should see "John Doe" in team list in every project as "Reporter"
+
+ @javascript
+ Scenario: Remove user from group
+ Given we have user "John Doe" in group
+ When I visit admin group page
+ And I remove user "John Doe" from group
+ Then I should not see "John Doe" in team list
diff --git a/features/profile/profile.feature b/features/profile/profile.feature
index d2125e013bc..d7fa370fe2a 100644
--- a/features/profile/profile.feature
+++ b/features/profile/profile.feature
@@ -83,3 +83,22 @@ Feature: Profile
Given I visit profile design page
When I change my code preview theme
Then I should receive feedback that the changes were saved
+
+ @javascript
+ Scenario: I see the password strength indicator
+ Given I visit profile password page
+ When I try to set a weak password
+ Then I should see the input field yellow
+
+ @javascript
+ Scenario: I see the password strength indicator error
+ Given I visit profile password page
+ When I try to set a short password
+ Then I should see the input field red
+ And I should see the password error message
+
+ @javascript
+ Scenario: I see the password strength indicator with success
+ Given I visit profile password page
+ When I try to set a strong password
+ Then I should see the input field green \ No newline at end of file
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
index 8d3e0bd967f..ed548177837 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -110,7 +110,7 @@ Feature: Project Active Tab
Scenario: On Project Issues/Browse
Given I visit my project's issues page
- Then the active sub tab should be Browse Issues
+ Then the active sub tab should be Issues
And no other sub tabs should be active
And the active main tab should be Issues
diff --git a/features/project/fork.feature b/features/project/fork.feature
index d3d1180db04..22f68e5b340 100644
--- a/features/project/fork.feature
+++ b/features/project/fork.feature
@@ -6,9 +6,11 @@ Feature: Project Fork
Scenario: User fork a project
Given I click link "Fork"
+ When I fork to my namespace
Then I should see the forked project page
Scenario: User already has forked the project
Given I already have a project named "Shop" in my namespace
And I click link "Fork"
+ When I fork to my namespace
Then I should see a "Name has already been taken" warning
diff --git a/features/project/service.feature b/features/project/service.feature
index af88eaefa8f..ed9e03b428d 100644
--- a/features/project/service.feature
+++ b/features/project/service.feature
@@ -19,6 +19,12 @@ Feature: Project Services
And I fill hipchat settings
Then I should see hipchat service settings saved
+ Scenario: Activate hipchat service with custom server
+ When I visit project "Shop" services page
+ And I click hipchat service link
+ And I fill hipchat settings with custom server
+ Then I should see hipchat service settings with custom server saved
+
Scenario: Activate pivotaltracker service
When I visit project "Shop" services page
And I click pivotaltracker service link
@@ -54,3 +60,9 @@ Feature: Project Services
And I click email on push service link
And I fill email on push settings
Then I should see email on push service settings saved
+
+ Scenario: Activate Atlassian Bamboo CI service
+ When I visit project "Shop" services page
+ And I click Atlassian Bamboo CI service link
+ And I fill Atlassian Bamboo CI settings
+ Then I should see Atlassian Bamboo CI service settings saved
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index aca255b9444..b7d70881d56 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -35,6 +35,16 @@ Feature: Project Source Browse Files
And I should see its new content
@javascript
+ Scenario: If I enter an illegal file name I see an error message
+ Given I click on "new file" link in repo
+ And I fill the new file name with an illegal name
+ And I edit code
+ And I fill the commit message
+ And I click on "Commit changes"
+ Then I am on the new file page
+ And I see a commit error message
+
+ @javascript
Scenario: I can edit file
Given I click on ".gitignore" file in repo
And I click button "Edit"
@@ -50,6 +60,16 @@ Feature: Project Source Browse Files
Then I am redirected to the ".gitignore"
And I should see its new content
+ @javascript @wip
+ Scenario: If I don't change the content of the file I see an error message
+ Given I click on ".gitignore" file in repo
+ And I click button "edit"
+ And I fill the commit message
+ And I click on "Commit changes"
+ # Test fails because carriage returns are added to the file.
+ Then I am on the ".gitignore" edit file page
+ And I see a commit error message
+
@javascript
Scenario: I can see editing preview
Given I click on ".gitignore" file in repo
diff --git a/features/snippets/public_snippets.feature b/features/snippets/public_snippets.feature
new file mode 100644
index 00000000000..c2afb63b6d8
--- /dev/null
+++ b/features/snippets/public_snippets.feature
@@ -0,0 +1,10 @@
+Feature: Public snippets
+ Scenario: Unauthenticated user should see public snippets
+ Given There is public "Personal snippet one" snippet
+ And I visit snippet page "Personal snippet one"
+ Then I should see snippet "Personal snippet one"
+
+ Scenario: Unauthenticated user should see raw public snippets
+ Given There is public "Personal snippet one" snippet
+ And I visit snippet raw page "Personal snippet one"
+ Then I should see raw snippet "Personal snippet one"
diff --git a/features/snippets/snippets.feature b/features/snippets/snippets.feature
index 4c4e3ee2cff..6e8019c326f 100644
--- a/features/snippets/snippets.feature
+++ b/features/snippets/snippets.feature
@@ -25,4 +25,4 @@ Feature: Snippets
Scenario: I destroy "Personal snippet one"
Given I visit snippet page "Personal snippet one"
And I click link "Destroy"
- Then I should not see "Personal snippet one" in snippets
+ Then I should not see "Personal snippet one" in snippets \ No newline at end of file
diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb
index 4f0ba05606d..d69a87cd07e 100644
--- a/features/steps/admin/groups.rb
+++ b/features/steps/admin/groups.rb
@@ -37,8 +37,7 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
end
When 'I select user "John Doe" from user list as "Reporter"' do
- user = User.find_by(name: "John Doe")
- select2(user.id, from: "#user_ids", multiple: true)
+ select2(user_john.id, from: "#user_ids", multiple: true)
within "#new_team_member" do
select "Reporter", from: "access_level"
end
@@ -58,9 +57,29 @@ class Spinach::Features::AdminGroups < Spinach::FeatureSteps
end
end
+ step 'we have user "John Doe" in group' do
+ current_group.add_user(user_john, Gitlab::Access::REPORTER)
+ end
+
+ step 'I remove user "John Doe" from group' do
+ within "#user_#{user_john.id}" do
+ click_link 'Remove user from group'
+ end
+ end
+
+ step 'I should not see "John Doe" in team list' do
+ within ".group-users-list" do
+ page.should_not have_content "John Doe"
+ end
+ end
+
protected
def current_group
@group ||= Group.first
end
+
+ def user_john
+ @user_john ||= User.find_by(name: "John Doe")
+ end
end
diff --git a/features/steps/dashboard/event_filters.rb b/features/steps/dashboard/event_filters.rb
index 332bfa95d97..3da3d62d0c0 100644
--- a/features/steps/dashboard/event_filters.rb
+++ b/features/steps/dashboard/event_filters.rb
@@ -29,7 +29,7 @@ class Spinach::Features::EventFilters < Spinach::FeatureSteps
step 'this project has push event' do
data = {
- before: "0000000000000000000000000000000000000000",
+ before: Gitlab::Git::BLANK_SHA,
after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
ref: "refs/heads/new_design",
user_id: @user.id,
diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb
index 6b5f88e5895..2a5850d091b 100644
--- a/features/steps/dashboard/issues.rb
+++ b/features/steps/dashboard/issues.rb
@@ -10,6 +10,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
step 'I should see issues authored by me' do
should_see(authored_issue)
+ should_see(authored_issue_on_public_project)
should_not_see(assigned_issue)
should_not_see(other_issue)
end
@@ -22,6 +23,7 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
step 'I have authored issues' do
authored_issue
+ authored_issue_on_public_project
end
step 'I have assigned issues' do
@@ -64,6 +66,10 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
@other_issue ||= create :issue, project: project
end
+ def authored_issue_on_public_project
+ @authored_issue_on_public_project ||= create :issue, author: current_user, project: public_project
+ end
+
def project
@project ||= begin
project =create :project
@@ -71,4 +77,8 @@ class Spinach::Features::DashboardIssues < Spinach::FeatureSteps
project
end
end
+
+ def public_project
+ @public_project ||= create :project, :public
+ end
end
diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb
index 95c378fa201..75e53173d3f 100644
--- a/features/steps/dashboard/merge_requests.rb
+++ b/features/steps/dashboard/merge_requests.rb
@@ -4,13 +4,17 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
step 'I should see merge requests assigned to me' do
should_see(assigned_merge_request)
+ should_see(assigned_merge_request_from_fork)
should_not_see(authored_merge_request)
+ should_not_see(authored_merge_request_from_fork)
should_not_see(other_merge_request)
end
step 'I should see merge requests authored by me' do
should_see(authored_merge_request)
+ should_see(authored_merge_request_from_fork)
should_not_see(assigned_merge_request)
+ should_not_see(assigned_merge_request_from_fork)
should_not_see(other_merge_request)
end
@@ -22,10 +26,12 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
step 'I have authored merge requests' do
authored_merge_request
+ authored_merge_request_from_fork
end
step 'I have assigned merge requests' do
assigned_merge_request
+ assigned_merge_request_from_fork
end
step 'I have other merge requests' do
@@ -53,15 +59,41 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
end
def assigned_merge_request
- @assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project, source_project: project
+ @assigned_merge_request ||= create :merge_request,
+ assignee: current_user,
+ target_project: project,
+ source_project: project
end
def authored_merge_request
- @authored_merge_request ||= create :merge_request, source_branch: 'simple_merge_request', author: current_user, target_project: project, source_project: project
+ @authored_merge_request ||= create :merge_request,
+ source_branch: 'simple_merge_request',
+ author: current_user,
+ target_project: project,
+ source_project: project
end
def other_merge_request
- @other_merge_request ||= create :merge_request, source_branch: '2_3_notes_fix', target_project: project, source_project: project
+ @other_merge_request ||= create :merge_request,
+ source_branch: '2_3_notes_fix',
+ target_project: project,
+ source_project: project
+ end
+
+ def authored_merge_request_from_fork
+ @authored_merge_request_from_fork ||= create :merge_request,
+ source_branch: 'basic_page',
+ author: current_user,
+ target_project: public_project,
+ source_project: forked_project
+ end
+
+ def assigned_merge_request_from_fork
+ @assigned_merge_request_from_fork ||= create :merge_request,
+ source_branch: 'basic_page_fix',
+ assignee: current_user,
+ target_project: public_project,
+ source_project: forked_project
end
def project
@@ -71,4 +103,12 @@ class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps
project
end
end
+
+ def public_project
+ @public_project ||= create :project, :public
+ end
+
+ def forked_project
+ @forked_project ||= Projects::ForkService.new(public_project, current_user).execute
+ end
end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index adfaefb1644..38aaadcd28d 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -58,16 +58,34 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step 'I try change my password w/o old one' do
within '.update-password' do
- fill_in "user_password", with: "22233344"
+ fill_in "user_password_profile", with: "22233344"
fill_in "user_password_confirmation", with: "22233344"
click_button "Save"
end
end
+ step 'I try to set a weak password' do
+ within '.update-password' do
+ fill_in "user_password_profile", with: "22233344"
+ end
+ end
+
+ step 'I try to set a short password' do
+ within '.update-password' do
+ fill_in "user_password_profile", with: "short"
+ end
+ end
+
+ step 'I try to set a strong password' do
+ within '.update-password' do
+ fill_in "user_password_profile", with: "Itulvo9z8uud%$"
+ end
+ end
+
step 'I change my password' do
within '.update-password' do
fill_in "user_current_password", with: "12345678"
- fill_in "user_password", with: "22233344"
+ fill_in "user_password_profile", with: "22233344"
fill_in "user_password_confirmation", with: "22233344"
click_button "Save"
end
@@ -76,7 +94,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step 'I unsuccessfully change my password' do
within '.update-password' do
fill_in "user_current_password", with: "12345678"
- fill_in "user_password", with: "password"
+ fill_in "user_password_profile", with: "password"
fill_in "user_password_confirmation", with: "confirmation"
click_button "Save"
end
@@ -86,6 +104,22 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
page.should have_content "You must provide a valid current password"
end
+ step 'I should see the input field yellow' do
+ page.should have_css 'div.has-warning'
+ end
+
+ step 'I should see the input field green' do
+ page.should have_css 'div.has-success'
+ end
+
+ step 'I should see the input field red' do
+ page.should have_css 'div.has-error'
+ end
+
+ step 'I should see the password error message' do
+ page.should have_content 'Your password is too short'
+ end
+
step "I should see a password error message" do
page.should have_content "Password confirmation doesn't match"
end
@@ -136,7 +170,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step "I am not an ldap user" do
- current_user.update_attributes(extern_uid: nil, provider: '')
+ current_user.identities.delete
current_user.ldap_user?.should be_false
end
@@ -146,7 +180,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step 'I submit new password' do
fill_in :user_current_password, with: '12345678'
- fill_in :user_password, with: '12345678'
+ fill_in :user_password_profile, with: '12345678'
fill_in :user_password_confirmation, with: '12345678'
click_button "Set new password"
end
diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb
index 83796b0ba88..bb42d15eae5 100644
--- a/features/steps/project/active_tab.rb
+++ b/features/steps/project/active_tab.rb
@@ -89,8 +89,8 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
click_link('Labels')
end
- step 'the active sub tab should be Browse Issues' do
- ensure_active_sub_tab('Browse Issues')
+ step 'the active sub tab should be Issues' do
+ ensure_active_sub_tab('Issues')
end
step 'the active sub tab should be Milestones' do
diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb
index da50ba9ced0..8e58597db20 100644
--- a/features/steps/project/fork.rb
+++ b/features/steps/project/fork.rb
@@ -25,4 +25,10 @@ class Spinach::Features::ProjectFork < Spinach::FeatureSteps
step 'I should see a "Name has already been taken" warning' do
page.should have_content "Name has already been taken"
end
+
+ step 'I fork to my namespace' do
+ within '.fork-namespaces' do
+ click_link current_user.name
+ end
+ end
end
diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb
index 89d7af3c9ee..cce87a6d981 100644
--- a/features/steps/project/issues/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -8,7 +8,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
milestone = @project.milestones.find_by(title: "v2.2")
page.should have_content(milestone.title[0..10])
page.should have_content(milestone.expires_at)
- page.should have_content("Browse Issues")
+ page.should have_content("Issues")
end
step 'I click link "v2.2"' do
@@ -28,7 +28,7 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
milestone = @project.milestones.find_by(title: "v2.3")
page.should have_content(milestone.title[0..10])
page.should have_content(milestone.expires_at)
- page.should have_content("Browse Issues")
+ page.should have_content("Issues")
end
step 'project "Shop" has milestone "v2.2"' do
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index f7fff8e64f9..5e7312d90ff 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -4,7 +4,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
include SharedPaths
step 'change project settings' do
- fill_in 'project_name', with: 'NewName'
+ fill_in 'project_name_edit', with: 'NewName'
uncheck 'project_issues_enabled'
end
diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb
index 5bd60f99c84..7a0b47a8fe5 100644
--- a/features/steps/project/services.rb
+++ b/features/steps/project/services.rb
@@ -10,10 +10,11 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
step 'I should see list of available services' do
page.should have_content 'Project services'
page.should have_content 'Campfire'
- page.should have_content 'Hipchat'
+ page.should have_content 'HipChat'
page.should have_content 'GitLab CI'
page.should have_content 'Assembla'
page.should have_content 'Pushover'
+ page.should have_content 'Atlassian Bamboo'
end
step 'I click gitlab-ci service link' do
@@ -32,7 +33,7 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
end
step 'I click hipchat service link' do
- click_link 'Hipchat'
+ click_link 'HipChat'
end
step 'I fill hipchat settings' do
@@ -46,6 +47,17 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
find_field('Room').value.should == 'gitlab'
end
+ step 'I fill hipchat settings with custom server' do
+ check 'Active'
+ fill_in 'Room', with: 'gitlab_custom'
+ fill_in 'Token', with: 'secretCustom'
+ fill_in 'Server', with: 'https://chat.example.com'
+ click_button 'Save'
+ end
+
+ step 'I should see hipchat service settings with custom server saved' do
+ find_field('Server').value.should == 'https://chat.example.com'
+ end
step 'I click pivotaltracker service link' do
click_link 'PivotalTracker'
@@ -108,12 +120,12 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
step 'I fill Slack settings' do
check 'Active'
- fill_in 'Webhook', with: 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI'
+ fill_in 'Webhook', with: 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685'
click_button 'Save'
end
step 'I should see Slack service settings saved' do
- find_field('Webhook').value.should == 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI'
+ find_field('Webhook').value.should == 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685'
end
step 'I click Pushover service link' do
@@ -137,4 +149,23 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps
find_field('Priority').find('option[selected]').value.should == '1'
find_field('Sound').find('option[selected]').value.should == 'bike'
end
+
+ step 'I click Atlassian Bamboo CI service link' do
+ click_link 'Atlassian Bamboo CI'
+ end
+
+ step 'I fill Atlassian Bamboo CI settings' do
+ check 'Active'
+ fill_in 'Bamboo url', with: 'http://bamboo.example.com'
+ fill_in 'Build key', with: 'KEY'
+ fill_in 'Username', with: 'user'
+ fill_in 'Password', with: 'verySecret'
+ click_button 'Save'
+ end
+
+ step 'I should see Atlassian Bamboo CI service settings saved' do
+ find_field('Bamboo url').value.should == 'http://bamboo.example.com'
+ find_field('Build key').value.should == 'KEY'
+ find_field('Username').value.should == 'user'
+ end
end
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 20f8f6c24ae..ddd501d4f88 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -61,6 +61,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
fill_in :file_name, with: new_file_name
end
+ step 'I fill the new file name with an illegal name' do
+ fill_in :file_name, with: '.git'
+ end
+
step 'I fill the commit message' do
fill_in :commit_message, with: 'Not yet a commit message.'
end
@@ -74,7 +78,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I click on "Remove"' do
- click_link 'Remove'
+ click_button 'Remove'
end
step 'I click on "Remove file"' do
@@ -151,6 +155,10 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
expect(page).not_to have_link('permalink')
end
+ step 'I see a commit error message' do
+ expect(page).to have_content('Your changes could not be committed')
+ end
+
private
def set_new_content
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 1f238f8befd..5f292255ce1 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -265,6 +265,15 @@ module SharedPaths
visit project_blob_path(@project, File.join(root_ref, '.gitignore'))
end
+ step 'I am on the new file page' do
+ current_path.should eq(project_new_tree_path(@project, root_ref))
+ end
+
+ step 'I am on the ".gitignore" edit file page' do
+ current_path.should eq(project_edit_tree_path(
+ @project, File.join(root_ref, '.gitignore')))
+ end
+
step 'I visit project source page for "6d39438"' do
visit project_tree_path(@project, "6d39438")
end
diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb
index 4b833850a1c..0bd5653538c 100644
--- a/features/steps/shared/project.rb
+++ b/features/steps/shared/project.rb
@@ -32,7 +32,7 @@ module SharedProject
@project = Project.find_by(name: "Shop")
data = {
- before: "0000000000000000000000000000000000000000",
+ before: Gitlab::Git::BLANK_SHA,
after: "6d394385cf567f80a8fd85055db1ab4c5295806f",
ref: "refs/heads/fix",
user_id: @user.id,
@@ -131,7 +131,7 @@ module SharedProject
end
step 'public empty project "Empty Public Project"' do
- create :empty_project, :public, name: "Empty Public Project"
+ create :project_empty_repo, :public, name: "Empty Public Project"
end
step 'project "Community" has comments' do
diff --git a/features/steps/shared/snippet.rb b/features/steps/shared/snippet.rb
index 432f32defce..bb596c1620a 100644
--- a/features/steps/shared/snippet.rb
+++ b/features/steps/shared/snippet.rb
@@ -51,4 +51,13 @@ module SharedSnippet
visibility_level: Snippet::PUBLIC,
author: current_user)
end
+
+ step 'There is public "Personal snippet one" snippet' do
+ create(:personal_snippet,
+ title: "Personal snippet one",
+ content: "Test content",
+ file_name: "snippet.rb",
+ visibility_level: Snippet::PUBLIC,
+ author: create(:user))
+ end
end
diff --git a/features/steps/snippets/public_snippets.rb b/features/steps/snippets/public_snippets.rb
new file mode 100644
index 00000000000..67669dc0a69
--- /dev/null
+++ b/features/steps/snippets/public_snippets.rb
@@ -0,0 +1,25 @@
+class Spinach::Features::PublicSnippets < Spinach::FeatureSteps
+ include SharedAuthentication
+ include SharedPaths
+ include SharedSnippet
+
+ step 'I should see snippet "Personal snippet one"' do
+ page.should have_no_xpath("//i[@class='public-snippet']")
+ end
+
+ step 'I should see raw snippet "Personal snippet one"' do
+ page.should have_text(snippet.content)
+ end
+
+ step 'I visit snippet page "Personal snippet one"' do
+ visit snippet_path(snippet)
+ end
+
+ step 'I visit snippet raw page "Personal snippet one"' do
+ visit raw_snippet_path(snippet)
+ end
+
+ def snippet
+ @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
+ end
+end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index 2c7cd9038c3..d26667ba3f7 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -27,6 +27,7 @@ module API
helpers APIHelpers
mount Groups
+ mount GroupMembers
mount Users
mount Projects
mount Repositories
diff --git a/lib/api/branches.rb b/lib/api/branches.rb
index 14f8b20f6b2..6ec1a753a69 100644
--- a/lib/api/branches.rb
+++ b/lib/api/branches.rb
@@ -82,6 +82,7 @@ module API
authorize_push_project
result = CreateBranchService.new(user_project, current_user).
execute(params[:branch_name], params[:ref])
+
if result[:status] == :success
present result[:branch],
with: Entities::RepoObject,
@@ -104,7 +105,9 @@ module API
execute(params[:branch])
if result[:status] == :success
- true
+ {
+ branch_name: params[:branch]
+ }
else
render_api_error!(result[:message], result[:return_code])
end
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 4a67313430a..6c5391b98c8 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -50,6 +50,67 @@ module API
not_found! "Commit" unless commit
commit.diffs
end
+
+ # Get a commit's comments
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit hash
+ # Examples:
+ # GET /projects/:id/repository/commits/:sha/comments
+ get ':id/repository/commits/:sha/comments' do
+ sha = params[:sha]
+ commit = user_project.repository.commit(sha)
+ not_found! 'Commit' unless commit
+ notes = Note.where(commit_id: commit.id)
+ present paginate(notes), with: Entities::CommitNote
+ end
+
+ # Post comment to commit
+ #
+ # Parameters:
+ # id (required) - The ID of a project
+ # sha (required) - The commit hash
+ # note (required) - Text of comment
+ # path (optional) - The file path
+ # line (optional) - The line number
+ # line_type (optional) - The type of line (new or old)
+ # Examples:
+ # POST /projects/:id/repository/commits/:sha/comments
+ post ':id/repository/commits/:sha/comments' do
+ required_attributes! [:note]
+
+ sha = params[:sha]
+ commit = user_project.repository.commit(sha)
+ not_found! 'Commit' unless commit
+ opts = {
+ note: params[:note],
+ noteable_type: 'Commit',
+ commit_id: commit.id
+ }
+
+ if params[:path] && params[:line] && params[:line_type]
+ commit.diffs.each do |diff|
+ next unless diff.new_path == params[:path]
+ lines = Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a)
+
+ lines.each do |line|
+ next unless line.new_pos == params[:line].to_i && line.type == params[:line_type]
+ break opts[:line_code] = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos)
+ end
+
+ break if opts[:line_code]
+ end
+ end
+
+ note = ::Notes::CreateService.new(user_project, current_user, opts).execute
+
+ if note.save
+ present note, with: Entities::CommitNote
+ else
+ not_found!
+ end
+ end
end
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 80e9470195e..2fea151aeb3 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -14,9 +14,14 @@ module API
expose :bio, :skype, :linkedin, :twitter, :website_url
end
+ class Identity < Grape::Entity
+ expose :provider, :extern_uid
+ end
+
class UserFull < User
expose :email
- expose :theme_id, :color_scheme_id, :extern_uid, :provider
+ expose :theme_id, :color_scheme_id, :projects_limit
+ expose :identities, using: Entities::Identity
expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project
end
@@ -73,6 +78,25 @@ module API
end
end
+ class RepoTag < Grape::Entity
+ expose :name
+ expose :message do |repo_obj, _options|
+ if repo_obj.respond_to?(:message)
+ repo_obj.message
+ else
+ nil
+ end
+ end
+
+ expose :commit do |repo_obj, options|
+ if repo_obj.respond_to?(:commit)
+ repo_obj.commit
+ elsif options[:project]
+ options[:project].repository.commit(repo_obj.target)
+ end
+ end
+ end
+
class RepoObject < Grape::Entity
expose :name
@@ -159,11 +183,25 @@ module API
expose :author, using: Entities::UserBasic
end
+ class CommitNote < Grape::Entity
+ expose :note
+ expose(:path) { |note| note.diff_file_name }
+ expose(:line) { |note| note.diff_new_line }
+ expose(:line_type) { |note| note.diff_line_type }
+ expose :author, using: Entities::UserBasic
+ end
+
class Event < Grape::Entity
expose :title, :project_id, :action_name
expose :target_id, :target_type, :author_id
expose :data, :target_title
expose :created_at
+
+ expose :author_username do |event, options|
+ if event.author
+ event.author.username
+ end
+ end
end
class Namespace < Grape::Entity
diff --git a/lib/api/files.rb b/lib/api/files.rb
index e63e635a4d3..84e1d311781 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -85,7 +85,7 @@ module API
branch_name: branch_name
}
else
- render_api_error!(result[:error], 400)
+ render_api_error!(result[:message], 400)
end
end
@@ -117,7 +117,7 @@ module API
branch_name: branch_name
}
else
- render_api_error!(result[:error], 400)
+ render_api_error!(result[:message], 400)
end
end
@@ -149,7 +149,7 @@ module API
branch_name: branch_name
}
else
- render_api_error!(result[:error], 400)
+ render_api_error!(result[:message], 400)
end
end
end
diff --git a/lib/api/group_members.rb b/lib/api/group_members.rb
new file mode 100644
index 00000000000..d596517c816
--- /dev/null
+++ b/lib/api/group_members.rb
@@ -0,0 +1,80 @@
+module API
+ class GroupMembers < Grape::API
+ before { authenticate! }
+
+ resource :groups do
+ helpers do
+ def find_group(id)
+ group = Group.find(id)
+
+ if can?(current_user, :read_group, group)
+ group
+ else
+ render_api_error!("403 Forbidden - #{current_user.username} lacks sufficient access to #{group.name}", 403)
+ end
+ end
+
+ def validate_access_level?(level)
+ Gitlab::Access.options_with_owner.values.include? level.to_i
+ end
+ end
+
+ # Get a list of group members viewable by the authenticated user.
+ #
+ # Example Request:
+ # GET /groups/:id/members
+ get ":id/members" do
+ group = find_group(params[:id])
+ members = group.group_members
+ users = (paginate members).collect(&:user)
+ present users, with: Entities::GroupMember, group: group
+ end
+
+ # Add a user to the list of group members
+ #
+ # Parameters:
+ # id (required) - group id
+ # user_id (required) - the users id
+ # access_level (required) - Project access level
+ # Example Request:
+ # POST /groups/:id/members
+ post ":id/members" do
+ group = find_group(params[:id])
+ authorize! :manage_group, group
+ required_attributes! [:user_id, :access_level]
+
+ unless validate_access_level?(params[:access_level])
+ render_api_error!("Wrong access level", 422)
+ end
+
+ if group.group_members.find_by(user_id: params[:user_id])
+ render_api_error!("Already exists", 409)
+ end
+
+ group.add_users([params[:user_id]], params[:access_level])
+ member = group.group_members.find_by(user_id: params[:user_id])
+ present member.user, with: Entities::GroupMember, group: group
+ end
+
+ # Remove member.
+ #
+ # Parameters:
+ # id (required) - group id
+ # user_id (required) - the users id
+ #
+ # Example Request:
+ # DELETE /groups/:id/members/:user_id
+ delete ":id/members/:user_id" do
+ group = find_group(params[:id])
+ authorize! :manage_group, group
+ member = group.group_members.find_by(user_id: params[:user_id])
+
+ if member.nil?
+ render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
+ else
+ member.destroy
+ end
+ end
+ end
+ end
+end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index 4841e04689d..f0ab6938b1c 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -97,57 +97,6 @@ module API
not_found!
end
end
-
- # Get a list of group members viewable by the authenticated user.
- #
- # Example Request:
- # GET /groups/:id/members
- get ":id/members" do
- group = find_group(params[:id])
- members = group.group_members
- users = (paginate members).collect(&:user)
- present users, with: Entities::GroupMember, group: group
- end
-
- # Add a user to the list of group members
- #
- # Parameters:
- # id (required) - group id
- # user_id (required) - the users id
- # access_level (required) - Project access level
- # Example Request:
- # POST /groups/:id/members
- post ":id/members" do
- required_attributes! [:user_id, :access_level]
- unless validate_access_level?(params[:access_level])
- render_api_error!("Wrong access level", 422)
- end
- group = find_group(params[:id])
- if group.group_members.find_by(user_id: params[:user_id])
- render_api_error!("Already exists", 409)
- end
- group.add_users([params[:user_id]], params[:access_level])
- member = group.group_members.find_by(user_id: params[:user_id])
- present member.user, with: Entities::GroupMember, group: group
- end
-
- # Remove member.
- #
- # Parameters:
- # id (required) - group id
- # user_id (required) - the users id
- #
- # Example Request:
- # DELETE /groups/:id/members/:user_id
- delete ":id/members/:user_id" do
- group = find_group(params[:id])
- member = group.group_members.find_by(user_id: params[:user_id])
- if member.nil?
- render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404)
- else
- member.destroy
- end
- end
end
end
end
diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb
index 3262884f6d3..027fb20ec46 100644
--- a/lib/api/helpers.rb
+++ b/lib/api/helpers.rb
@@ -67,6 +67,10 @@ module API
unauthorized! unless current_user
end
+ def authenticate_by_gitlab_shell_token!
+ unauthorized! unless secret_token == params['secret_token']
+ end
+
def authenticated_as_admin!
forbidden! unless current_user.is_admin?
end
@@ -193,5 +197,9 @@ module API
abilities
end
end
+
+ def secret_token
+ File.read(Rails.root.join('.gitlab_shell_secret'))
+ end
end
end
diff --git a/lib/api/internal.rb b/lib/api/internal.rb
index 9ac659f50fd..180e50611cf 100644
--- a/lib/api/internal.rb
+++ b/lib/api/internal.rb
@@ -1,6 +1,10 @@
module API
# Internal access API
class Internal < Grape::API
+ before {
+ authenticate_by_gitlab_shell_token!
+ }
+
namespace 'internal' do
# Check if git command is allowed to project
#
@@ -29,17 +33,22 @@ module API
end
project = Project.find_with_namespace(project_path)
- return false unless project
+
+ unless project
+ return Gitlab::GitAccessStatus.new(false, 'No such project')
+ end
actor = if params[:key_id]
- Key.find(params[:key_id])
+ Key.find_by(id: params[:key_id])
elsif params[:user_id]
- User.find(params[:user_id])
+ User.find_by(id: params[:user_id])
end
- return false unless actor
+ unless actor
+ return Gitlab::GitAccessStatus.new(false, 'No such user or key')
+ end
- access.allowed?(
+ access.check(
actor,
params[:action],
project,
diff --git a/lib/api/projects.rb b/lib/api/projects.rb
index 7f7d2f8e9a8..7fcf97d1ad6 100644
--- a/lib/api/projects.rb
+++ b/lib/api/projects.rb
@@ -178,7 +178,7 @@ module API
# DELETE /projects/:id
delete ":id" do
authorize! :remove_project, user_project
- user_project.destroy
+ ::Projects::DestroyService.new(user_project, current_user, {}).execute
end
# Mark this project as forked from another
diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb
index 626d99c2649..a1a7721b288 100644
--- a/lib/api/repositories.rb
+++ b/lib/api/repositories.rb
@@ -23,7 +23,8 @@ module API
# Example Request:
# GET /projects/:id/repository/tags
get ":id/repository/tags" do
- present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject, project: user_project
+ present user_project.repo.tags.sort_by(&:name).reverse,
+ with: Entities::RepoTag, project: user_project
end
# Create tag
@@ -43,7 +44,7 @@ module API
if result[:status] == :success
present result[:tag],
- with: Entities::RepoObject,
+ with: Entities::RepoTag,
project: user_project
else
render_api_error!(result[:message], 400)
diff --git a/lib/api/services.rb b/lib/api/services.rb
index bde502e32e1..3ad59cf3adf 100644
--- a/lib/api/services.rb
+++ b/lib/api/services.rb
@@ -28,7 +28,7 @@ module API
# Delete GitLab CI service settings
#
# Example Request:
- # DELETE /projects/:id/keys/:id
+ # DELETE /projects/:id/services/gitlab-ci
delete ":id/services/gitlab-ci" do
if user_project.gitlab_ci_service
user_project.gitlab_ci_service.update_attributes(
@@ -38,7 +38,41 @@ module API
)
end
end
+
+ # Set Hipchat service for project
+ #
+ # Parameters:
+ # token (required) - Hipchat token
+ # room (required) - Hipchat room name
+ #
+ # Example Request:
+ # PUT /projects/:id/services/hipchat
+ put ':id/services/hipchat' do
+ required_attributes! [:token, :room]
+ attrs = attributes_for_keys [:token, :room]
+ user_project.build_missing_services
+
+ if user_project.hipchat_service.update_attributes(
+ attrs.merge(active: true))
+ true
+ else
+ not_found!
+ end
+ end
+
+ # Delete Hipchat service settings
+ #
+ # Example Request:
+ # DELETE /projects/:id/services/hipchat
+ delete ':id/services/hipchat' do
+ if user_project.hipchat_service
+ user_project.hipchat_service.update_attributes(
+ active: false,
+ token: nil,
+ room: nil
+ )
+ end
+ end
end
end
end
-
diff --git a/lib/api/users.rb b/lib/api/users.rb
index d07815a8a97..37b36ddcf94 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -59,10 +59,16 @@ module API
post do
authenticated_as_admin!
required_attributes! [:email, :password, :name, :username]
- attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin]
+ attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :can_create_group, :admin]
user = User.build_user(attrs)
admin = attrs.delete(:admin)
user.admin = admin unless admin.nil?
+
+ identity_attrs = attributes_for_keys [:provider, :extern_uid]
+ if identity_attrs.any?
+ user.identities.build(identity_attrs)
+ end
+
if user.save
present user, with: Entities::UserFull
else
@@ -89,8 +95,6 @@ module API
# twitter - Twitter account
# website_url - Website url
# projects_limit - Limit projects each user can create
- # extern_uid - External authentication provider UID
- # provider - External provider
# bio - Bio
# admin - User is admin - true or false (default)
# can_create_group - User can create groups - true or false
@@ -99,7 +103,7 @@ module API
put ":id" do
authenticated_as_admin!
- attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin]
+ attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :can_create_group, :admin]
user = User.find(params[:id])
not_found!('User') unless user
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index d12d30a9110..9ab6aca276d 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -13,10 +13,10 @@ module Backup
def dump
success = case config["adapter"]
when /^mysql/ then
- print "Dumping MySQL database #{config['database']} ... "
+ $progress.print "Dumping MySQL database #{config['database']} ... "
system('mysqldump', *mysql_args, config['database'], out: db_file_name)
when "postgresql" then
- print "Dumping PostgreSQL database #{config['database']} ... "
+ $progress.print "Dumping PostgreSQL database #{config['database']} ... "
pg_env
system('pg_dump', config['database'], out: db_file_name)
end
@@ -27,13 +27,14 @@ module Backup
def restore
success = case config["adapter"]
when /^mysql/ then
- print "Restoring MySQL database #{config['database']} ... "
+ $progress.print "Restoring MySQL database #{config['database']} ... "
system('mysql', *mysql_args, config['database'], in: db_file_name)
when "postgresql" then
- print "Restoring PostgreSQL database #{config['database']} ... "
+ $progress.print "Restoring PostgreSQL database #{config['database']} ... "
# Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE
# statements like MySQL.
Rake::Task["gitlab:db:drop_all_tables"].invoke
+ Rake::Task["gitlab:db:drop_all_postgres_sequences"].invoke
pg_env
system('psql', config['database'], '-f', db_file_name)
end
@@ -68,9 +69,9 @@ module Backup
def report_success(success)
if success
- puts '[DONE]'.green
+ $progress.puts '[DONE]'.green
else
- puts '[FAILED]'.red
+ $progress.puts '[FAILED]'.red
end
end
end
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index 03fe0f0b02f..ab8db4e9837 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -18,11 +18,11 @@ module Backup
end
# create archive
- print "Creating backup archive: #{tar_file} ... "
+ $progress.print "Creating backup archive: #{tar_file} ... "
if Kernel.system('tar', '-cf', tar_file, *BACKUP_CONTENTS)
- puts "done".green
+ $progress.puts "done".green
else
- puts "failed".red
+ puts "creating archive #{tar_file} failed".red
abort 'Backup failed'
end
@@ -31,37 +31,37 @@ module Backup
def upload(tar_file)
remote_directory = Gitlab.config.backup.upload.remote_directory
- print "Uploading backup archive to remote storage #{remote_directory} ... "
+ $progress.print "Uploading backup archive to remote storage #{remote_directory} ... "
connection_settings = Gitlab.config.backup.upload.connection
if connection_settings.blank?
- puts "skipped".yellow
+ $progress.puts "skipped".yellow
return
end
connection = ::Fog::Storage.new(connection_settings)
directory = connection.directories.get(remote_directory)
if directory.files.create(key: tar_file, body: File.open(tar_file), public: false)
- puts "done".green
+ $progress.puts "done".green
else
- puts "failed".red
+ puts "uploading backup to #{remote_directory} failed".red
abort 'Backup failed'
end
end
def cleanup
- print "Deleting tmp directories ... "
+ $progress.print "Deleting tmp directories ... "
if Kernel.system('rm', '-rf', *BACKUP_CONTENTS)
- puts "done".green
+ $progress.puts "done".green
else
- puts "failed".red
+ puts "deleting tmp directory failed".red
abort 'Backup failed'
end
end
def remove_old
# delete backups
- print "Deleting old backups ... "
+ $progress.print "Deleting old backups ... "
keep_time = Gitlab.config.backup.keep_time.to_i
path = Gitlab.config.backup.path
@@ -76,9 +76,9 @@ module Backup
end
end
end
- puts "done. (#{removed} removed)".green
+ $progress.puts "done. (#{removed} removed)".green
else
- puts "skipping".yellow
+ $progress.puts "skipping".yellow
end
end
@@ -101,12 +101,12 @@ module Backup
exit 1
end
- print "Unpacking backup ... "
+ $progress.print "Unpacking backup ... "
unless Kernel.system(*%W(tar -xf #{tar_file}))
- puts "failed".red
+ puts "unpacking backup failed".red
exit 1
else
- puts "done".green
+ $progress.puts "done".green
end
settings = YAML.load_file("backup_information.yml")
diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb
index 4e99d4bbe5c..e18bc804437 100644
--- a/lib/backup/repository.rb
+++ b/lib/backup/repository.rb
@@ -8,19 +8,21 @@ module Backup
prepare
Project.find_each(batch_size: 1000) do |project|
- print " * #{project.path_with_namespace} ... "
+ $progress.print " * #{project.path_with_namespace} ... "
# Create namespace dir if missing
FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace
if project.empty_repo?
- puts "[SKIPPED]".cyan
+ $progress.puts "[SKIPPED]".cyan
else
- output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all))
+ cmd = %W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all)
+ output, status = Gitlab::Popen.popen(cmd)
if status.zero?
- puts "[DONE]".green
+ $progress.puts "[DONE]".green
else
puts "[FAILED]".red
+ puts "failed: #{cmd.join(' ')}"
puts output
abort 'Backup failed'
end
@@ -29,15 +31,17 @@ module Backup
wiki = ProjectWiki.new(project)
if File.exists?(path_to_repo(wiki))
- print " * #{wiki.path_with_namespace} ... "
- if wiki.empty?
- puts " [SKIPPED]".cyan
+ $progress.print " * #{wiki.path_with_namespace} ... "
+ if wiki.repository.empty?
+ $progress.puts " [SKIPPED]".cyan
else
- output, status = Gitlab::Popen.popen(%W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all))
+ cmd = %W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all)
+ output, status = Gitlab::Popen.popen(cmd)
if status.zero?
- puts " [DONE]".green
+ $progress.puts " [DONE]".green
else
puts " [FAILED]".red
+ puts "failed: #{cmd.join(' ')}"
abort 'Backup failed'
end
end
@@ -55,35 +59,52 @@ module Backup
FileUtils.mkdir_p(repos_path)
Project.find_each(batch_size: 1000) do |project|
- print "#{project.path_with_namespace} ... "
+ $progress.print " * #{project.path_with_namespace} ... "
project.namespace.ensure_dir_exist if project.namespace
- if system(*%W(git clone --bare #{path_to_bundle(project)} #{path_to_repo(project)}), silent)
- puts "[DONE]".green
+ if File.exists?(path_to_bundle(project))
+ cmd = %W(git clone --bare #{path_to_bundle(project)} #{path_to_repo(project)})
+ else
+ cmd = %W(git init --bare #{path_to_repo(project)})
+ end
+
+ if system(*cmd, silent)
+ $progress.puts "[DONE]".green
else
puts "[FAILED]".red
+ puts "failed: #{cmd.join(' ')}"
abort 'Restore failed'
end
wiki = ProjectWiki.new(project)
if File.exists?(path_to_bundle(wiki))
- print " * #{wiki.path_with_namespace} ... "
- if system(*%W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)}), silent)
- puts " [DONE]".green
+ $progress.print " * #{wiki.path_with_namespace} ... "
+
+ # If a wiki bundle exists, first remove the empty repo
+ # that was initialized with ProjectWiki.new() and then
+ # try to restore with 'git clone --bare'.
+ FileUtils.rm_rf(path_to_repo(wiki))
+ cmd = %W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)})
+
+ if system(*cmd, silent)
+ $progress.puts " [DONE]".green
else
puts " [FAILED]".red
+ puts "failed: #{cmd.join(' ')}"
abort 'Restore failed'
end
end
end
- print 'Put GitLab hooks in repositories dirs'.yellow
- if system("#{Gitlab.config.gitlab_shell.path}/bin/create-hooks")
- puts " [DONE]".green
+ $progress.print 'Put GitLab hooks in repositories dirs'.yellow
+ cmd = "#{Gitlab.config.gitlab_shell.path}/bin/create-hooks"
+ if system(cmd)
+ $progress.puts " [DONE]".green
else
puts " [FAILED]".red
+ puts "failed: #{cmd}"
end
end
@@ -91,7 +112,7 @@ module Backup
protected
def path_to_repo(project)
- File.join(repos_path, project.path_with_namespace + '.git')
+ project.repository.path_to_repo
end
def path_to_bundle(project)
diff --git a/lib/disable_email_interceptor.rb b/lib/disable_email_interceptor.rb
new file mode 100644
index 00000000000..1b80be112a4
--- /dev/null
+++ b/lib/disable_email_interceptor.rb
@@ -0,0 +1,8 @@
+# Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails
+class DisableEmailInterceptor
+
+ def self.delivering_email(message)
+ message.perform_deliveries = false
+ Rails.logger.info "Emails disabled! Interceptor prevented sending mail #{message.subject}"
+ end
+end
diff --git a/lib/gitlab/app_logger.rb b/lib/gitlab/app_logger.rb
index 8e4717b46e6..dddcb2538f9 100644
--- a/lib/gitlab/app_logger.rb
+++ b/lib/gitlab/app_logger.rb
@@ -1,7 +1,7 @@
module Gitlab
class AppLogger < Gitlab::Logger
- def self.file_name
- 'application.log'
+ def self.file_name_noext
+ 'application'
end
def format_message(severity, timestamp, progname, msg)
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index ae33c529b93..30509528b8b 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -1,7 +1,7 @@
module Gitlab
class Auth
def find(login, password)
- user = User.find_by(email: login) || User.find_by(username: login)
+ user = User.by_login(login)
# If no user is found, or it's an LDAP server, try LDAP.
# LDAP users are only authenticated via LDAP
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index c2f3b851c07..762639414e0 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -80,7 +80,7 @@ module Grack
case git_cmd
when *Gitlab::GitAccess::DOWNLOAD_COMMANDS
if user
- Gitlab::GitAccess.new.download_allowed?(user, project)
+ Gitlab::GitAccess.new.download_access_check(user, project).allowed?
elsif project.public?
# Allow clone/fetch for public projects
true
@@ -90,7 +90,7 @@ module Grack
when *Gitlab::GitAccess::PUSH_COMMANDS
if user
# Skip user authorization on upload request.
- # It will be serverd by update hook in repository
+ # It will be done by the pre-receive hook in the repository.
true
else
false
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index f95bbde5b39..aabc7f1e69a 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -8,6 +8,13 @@ module Gitlab
end
end
+ class << self
+ def version_required
+ @version_required ||= File.read(Rails.root.
+ join('GITLAB_SHELL_VERSION')).strip
+ end
+ end
+
# Init new repository
#
# name - project path with namespace
@@ -16,7 +23,8 @@ module Gitlab
# add_repository("gitlab/gitlab-ci")
#
def add_repository(name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "add-project", "#{name}.git"
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path,
+ 'add-project', "#{name}.git"])
end
# Import repository
@@ -27,7 +35,8 @@ module Gitlab
# import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git")
#
def import_repository(name, url)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "import-project", "#{name}.git", url, '240'
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'import-project',
+ "#{name}.git", url, '240'])
end
# Move repository
@@ -39,7 +48,8 @@ module Gitlab
# mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git")
#
def mv_repository(path, new_path)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git"
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project',
+ "#{path}.git", "#{new_path}.git"])
end
# Update HEAD for repository
@@ -51,7 +61,8 @@ module Gitlab
# update_repository_head("gitlab/gitlab-ci", "3-1-stable")
#
def update_repository_head(path, branch)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "update-head", "#{path}.git", branch
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'update-head',
+ "#{path}.git", branch])
end
# Fork repository to new namespace
@@ -63,7 +74,8 @@ module Gitlab
# fork_repository("gitlab/gitlab-ci", "randx")
#
def fork_repository(path, fork_namespace)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project',
+ "#{path}.git", fork_namespace])
end
# Remove repository from file system
@@ -74,7 +86,8 @@ module Gitlab
# remove_repository("gitlab/gitlab-ci")
#
def remove_repository(name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-project", "#{name}.git"
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path,
+ 'rm-project', "#{name}.git"])
end
# Add repository branch from passed ref
@@ -87,7 +100,8 @@ module Gitlab
# add_branch("gitlab/gitlab-ci", "4-0-stable", "master")
#
def add_branch(path, branch_name, ref)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'create-branch',
+ "#{path}.git", branch_name, ref])
end
# Remove repository branch
@@ -99,7 +113,8 @@ module Gitlab
# rm_branch("gitlab/gitlab-ci", "4-0-stable")
#
def rm_branch(path, branch_name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-branch',
+ "#{path}.git", branch_name])
end
# Add repository tag from passed ref
@@ -117,7 +132,7 @@ module Gitlab
cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git
#{tag_name} #{ref})
cmd << message unless message.nil? || message.empty?
- system *cmd
+ Gitlab::Utils.system_silent(cmd)
end
# Remove repository tag
@@ -129,7 +144,8 @@ module Gitlab
# rm_tag("gitlab/gitlab-ci", "v4.0")
#
def rm_tag(path, tag_name)
- system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name
+ Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-tag',
+ "#{path}.git", tag_name])
end
# Add new key to gitlab-shell
@@ -138,7 +154,8 @@ module Gitlab
# add_key("key-42", "sha-rsa ...")
#
def add_key(key_id, key_content)
- system "#{gitlab_shell_path}/bin/gitlab-keys", "add-key", key_id, key_content
+ Gitlab::Utils.system_silent([gitlab_shell_keys_path,
+ 'add-key', key_id, key_content])
end
# Batch-add keys to authorized_keys
@@ -157,7 +174,8 @@ module Gitlab
# remove_key("key-342", "sha-rsa ...")
#
def remove_key(key_id, key_content)
- system "#{gitlab_shell_path}/bin/gitlab-keys", "rm-key", key_id, key_content
+ Gitlab::Utils.system_silent([gitlab_shell_keys_path,
+ 'rm-key', key_id, key_content])
end
# Remove all ssh keys from gitlab shell
@@ -166,7 +184,7 @@ module Gitlab
# remove_all_keys
#
def remove_all_keys
- system "#{gitlab_shell_path}/bin/gitlab-keys", "clear"
+ Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'clear'])
end
# Add empty directory for storing repositories
@@ -249,5 +267,13 @@ module Gitlab
def exists?(dir_name)
File.exists?(full_path(dir_name))
end
+
+ def gitlab_shell_projects_path
+ File.join(gitlab_shell_path, 'bin', 'gitlab-projects')
+ end
+
+ def gitlab_shell_keys_path
+ File.join(gitlab_shell_path, 'bin', 'gitlab-keys')
+ end
end
end
diff --git a/lib/gitlab/force_push_check.rb b/lib/gitlab/force_push_check.rb
new file mode 100644
index 00000000000..6a52cdba608
--- /dev/null
+++ b/lib/gitlab/force_push_check.rb
@@ -0,0 +1,15 @@
+module Gitlab
+ class ForcePushCheck
+ def self.force_push?(project, oldrev, newrev)
+ return false if project.empty_repo?
+
+ if oldrev != Gitlab::Git::BLANK_SHA && newrev != Gitlab::Git::BLANK_SHA
+ missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read
+ missed_refs.split("\n").size > 0
+ else
+ false
+ end
+ end
+ end
+end
+
diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb
new file mode 100644
index 00000000000..67aca5e36e9
--- /dev/null
+++ b/lib/gitlab/git.rb
@@ -0,0 +1,5 @@
+module Gitlab
+ module Git
+ BLANK_SHA = '0' * 40
+ end
+end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index b768a99a0e8..875f8d8b3a3 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -5,61 +5,77 @@ module Gitlab
attr_reader :params, :project, :git_cmd, :user
- def allowed?(actor, cmd, project, changes = nil)
+ def check(actor, cmd, project, changes = nil)
case cmd
when *DOWNLOAD_COMMANDS
+ download_access_check(actor, project)
+ when *PUSH_COMMANDS
if actor.is_a? User
- download_allowed?(actor, project)
+ push_access_check(actor, project, changes)
elsif actor.is_a? DeployKey
- actor.projects.include?(project)
+ return build_status_object(false, "Deploy key not allowed to push")
elsif actor.is_a? Key
- download_allowed?(actor.user, project)
+ push_access_check(actor.user, project, changes)
else
raise 'Wrong actor'
end
- when *PUSH_COMMANDS
- if actor.is_a? User
- push_allowed?(actor, project, changes)
- elsif actor.is_a? DeployKey
- # Deploy key not allowed to push
- return false
- elsif actor.is_a? Key
- push_allowed?(actor.user, project, changes)
+ else
+ return build_status_object(false, "Wrong command")
+ end
+ end
+
+ def download_access_check(actor, project)
+ if actor.is_a?(User)
+ user_download_access_check(actor, project)
+ elsif actor.is_a?(DeployKey)
+ if actor.projects.include?(project)
+ build_status_object(true)
else
- raise 'Wrong actor'
+ build_status_object(false, "Deploy key not allowed to access this project")
end
+ elsif actor.is_a? Key
+ user_download_access_check(actor.user, project)
else
- false
+ raise 'Wrong actor'
end
end
- def download_allowed?(user, project)
- if user && user_allowed?(user)
- user.can?(:download_code, project)
+ def user_download_access_check(user, project)
+ if user && user_allowed?(user) && user.can?(:download_code, project)
+ build_status_object(true)
else
- false
+ build_status_object(false, "You don't have access")
end
end
- def push_allowed?(user, project, changes)
- return false unless user && user_allowed?(user)
- return true if changes.blank?
+ def push_access_check(user, project, changes)
+ unless user && user_allowed?(user)
+ return build_status_object(false, "You don't have access")
+ end
+
+ if changes.blank?
+ return build_status_object(true)
+ end
+
+ unless project.repository.exists?
+ return build_status_object(false, "Repository does not exist")
+ end
changes = changes.lines if changes.kind_of?(String)
# Iterate over all changes to find if user allowed all of them to be applied
changes.each do |change|
- unless change_allowed?(user, project, change)
+ status = change_access_check(user, project, change)
+ unless status.allowed?
# If user does not have access to make at least one change - cancel all push
- return false
+ return status
end
end
- # If user has access to make all changes
- true
+ return build_status_object(true)
end
- def change_allowed?(user, project, change)
+ def change_access_check(user, project, change)
oldrev, newrev, ref = change.split(' ')
action = if project.protected_branch?(branch_name(ref))
@@ -67,30 +83,27 @@ module Gitlab
if forced_push?(project, oldrev, newrev)
:force_push_code_to_protected_branches
# and we dont allow remove of protected branch
- elsif newrev =~ /0000000/
+ elsif newrev == Gitlab::Git::BLANK_SHA
:remove_protected_branches
else
:push_code_to_protected_branches
end
- elsif project.repository && project.repository.tag_names.include?(tag_name(ref))
+ elsif project.repository.tag_names.include?(tag_name(ref))
# Prevent any changes to existing git tag unless user has permissions
:admin_project
else
:push_code
end
- user.can?(action, project)
+ if user.can?(action, project)
+ build_status_object(true)
+ else
+ build_status_object(false, "You don't have permission")
+ end
end
def forced_push?(project, oldrev, newrev)
- return false if project.empty_repo?
-
- if oldrev !~ /00000000/ && newrev !~ /00000000/
- missed_refs = IO.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})).read
- missed_refs.split("\n").size > 0
- else
- false
- end
+ Gitlab::ForcePushCheck.force_push?(project, oldrev, newrev)
end
private
@@ -116,5 +129,11 @@ module Gitlab
nil
end
end
+
+ protected
+
+ def build_status_object(status, message = '')
+ GitAccessStatus.new(status, message)
+ end
end
end
diff --git a/lib/gitlab/git_access_status.rb b/lib/gitlab/git_access_status.rb
new file mode 100644
index 00000000000..3d451ecebee
--- /dev/null
+++ b/lib/gitlab/git_access_status.rb
@@ -0,0 +1,15 @@
+module Gitlab
+ class GitAccessStatus
+ attr_accessor :status, :message
+ alias_method :allowed?, :status
+
+ def initialize(status, message = '')
+ @status = status
+ @message = message
+ end
+
+ def to_json
+ {status: @status, message: @message}.to_json
+ end
+ end
+end \ No newline at end of file
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index 9f0eb3be20f..a2177c8d548 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -1,7 +1,11 @@
module Gitlab
class GitAccessWiki < GitAccess
- def change_allowed?(user, project, change)
- user.can?(:write_wiki, project)
+ def change_access_check(user, project, change)
+ if user.can?(:write_wiki, project)
+ build_status_object(true)
+ else
+ build_status_object(false, "You don't have access")
+ end
end
end
end
diff --git a/lib/gitlab/git_logger.rb b/lib/gitlab/git_logger.rb
index fbfed205a0f..9e02ccc0f44 100644
--- a/lib/gitlab/git_logger.rb
+++ b/lib/gitlab/git_logger.rb
@@ -1,7 +1,7 @@
module Gitlab
class GitLogger < Gitlab::Logger
- def self.file_name
- 'githost.log'
+ def self.file_name_noext
+ 'githost'
end
def format_message(severity, timestamp, progname, msg)
diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb
index 13cb08948bb..39d17def930 100644
--- a/lib/gitlab/git_ref_validator.rb
+++ b/lib/gitlab/git_ref_validator.rb
@@ -5,7 +5,8 @@ module Gitlab
#
# Returns true for a valid reference name, false otherwise
def validate(ref_name)
- system *%W(git check-ref-format refs/#{ref_name})
+ Gitlab::Utils.system_silent(
+ %W(git check-ref-format refs/#{ref_name}))
end
end
end
diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb
index 0d34976736f..1bec6088292 100644
--- a/lib/gitlab/issues_labels.rb
+++ b/lib/gitlab/issues_labels.rb
@@ -15,7 +15,6 @@ module Gitlab
{ title: "support", color: yellow },
{ title: "discussion", color: blue },
{ title: "suggestion", color: blue },
- { title: "feature", color: green },
{ title: "enhancement", color: green }
]
diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb
index eb2c4e48ff2..0c85acf7e69 100644
--- a/lib/gitlab/ldap/access.rb
+++ b/lib/gitlab/ldap/access.rb
@@ -8,7 +8,7 @@ module Gitlab
attr_reader :adapter, :provider, :user
def self.open(user, &block)
- Gitlab::LDAP::Adapter.open(user.provider) do |adapter|
+ Gitlab::LDAP::Adapter.open(user.ldap_identity.provider) do |adapter|
block.call(self.new(user, adapter))
end
end
@@ -28,13 +28,13 @@ module Gitlab
def initialize(user, adapter=nil)
@adapter = adapter
@user = user
- @provider = user.provider
+ @provider = user.ldap_identity.provider
end
def allowed?
- if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter)
+ if Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter)
return true unless ldap_config.active_directory
- !Gitlab::LDAP::Person.disabled_via_active_directory?(user.extern_uid, adapter)
+ !Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter)
else
false
end
diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb
index c4d0a20d89a..256cdb4c2f1 100644
--- a/lib/gitlab/ldap/adapter.rb
+++ b/lib/gitlab/ldap/adapter.rb
@@ -22,7 +22,7 @@ module Gitlab
Gitlab::LDAP::Config.new(provider)
end
- def users(field, value)
+ def users(field, value, limit = nil)
if field.to_sym == :dn
options = {
base: value,
@@ -45,6 +45,10 @@ module Gitlab
end
end
+ if limit.present?
+ options.merge!(size: limit)
+ end
+
entries = ldap_search(options).select do |entry|
entry.respond_to? config.uid
end
diff --git a/lib/gitlab/ldap/authentication.rb b/lib/gitlab/ldap/authentication.rb
index a5944f96983..8af2c74e959 100644
--- a/lib/gitlab/ldap/authentication.rb
+++ b/lib/gitlab/ldap/authentication.rb
@@ -42,7 +42,7 @@ module Gitlab
end
def adapter
- OmniAuth::LDAP::Adaptor.new(config.options)
+ OmniAuth::LDAP::Adaptor.new(config.options.symbolize_keys)
end
def config
@@ -68,4 +68,4 @@ module Gitlab
end
end
end
-end \ No newline at end of file
+end
diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb
index d41bfba9b0f..0cb24d0ccc1 100644
--- a/lib/gitlab/ldap/config.rb
+++ b/lib/gitlab/ldap/config.rb
@@ -16,10 +16,23 @@ module Gitlab
servers.map {|server| server['provider_name'] }
end
+ def self.valid_provider?(provider)
+ providers.include?(provider)
+ end
+
+ def self.invalid_provider(provider)
+ raise "Unknown provider (#{provider}). Available providers: #{providers}"
+ end
+
def initialize(provider)
- @provider = provider
- invalid_provider unless valid_provider?
- @options = config_for(provider)
+ if self.class.valid_provider?(provider)
+ @provider = provider
+ elsif provider == 'ldap'
+ @provider = self.class.providers.first
+ else
+ self.class.invalid_provider(provider)
+ end
+ @options = config_for(@provider) # Use @provider, not provider
end
def enabled?
@@ -89,14 +102,6 @@ module Gitlab
end
end
- def valid_provider?
- self.class.providers.include?(provider)
- end
-
- def invalid_provider
- raise "Unknown provider (#{provider}). Available providers: #{self.class.providers}"
- end
-
def auth_options
{
auth: {
diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb
index 3176e9790a7..3ef494ba137 100644
--- a/lib/gitlab/ldap/user.rb
+++ b/lib/gitlab/ldap/user.rb
@@ -12,9 +12,10 @@ module Gitlab
class << self
def find_by_uid_and_provider(uid, provider)
# LDAP distinguished name is case-insensitive
- ::User.
+ identity = ::Identity.
where(provider: [provider, :ldap]).
where('lower(extern_uid) = ?', uid.downcase).last
+ identity && identity.user
end
end
@@ -34,15 +35,13 @@ module Gitlab
end
def find_by_email
- model.find_by(email: auth_hash.email)
+ ::User.find_by(email: auth_hash.email)
end
def update_user_attributes
- gl_user.attributes = {
- extern_uid: auth_hash.uid,
- provider: auth_hash.provider,
- email: auth_hash.email
- }
+ gl_user.email = auth_hash.email
+ gl_user.identities.build(provider: auth_hash.provider, extern_uid: auth_hash.uid)
+ gl_user
end
def changed?
diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb
index 8a73ec5038a..59b21149a9a 100644
--- a/lib/gitlab/logger.rb
+++ b/lib/gitlab/logger.rb
@@ -1,5 +1,9 @@
module Gitlab
class Logger < ::Logger
+ def self.file_name
+ file_name_noext + '.log'
+ end
+
def self.error(message)
build.error(message)
end
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index ddcce7557a0..068c342398b 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -202,7 +202,7 @@ module Gitlab
if identifier == "all"
link_to("@all", project_url(project), options)
- elsif user = User.find_by(username: identifier)
+ elsif User.find_by(username: identifier)
link_to("@#{identifier}", user_url(identifier), options)
end
end
diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb
index 133445d3d05..6861427864e 100644
--- a/lib/gitlab/oauth/user.rb
+++ b/lib/gitlab/oauth/user.rb
@@ -5,6 +5,8 @@
#
module Gitlab
module OAuth
+ class ForbiddenAction < StandardError; end
+
class User
attr_accessor :auth_hash, :gl_user
@@ -13,22 +15,28 @@ module Gitlab
end
def persisted?
- gl_user.persisted?
+ gl_user.try(:persisted?)
end
def new?
- !gl_user.persisted?
+ !persisted?
end
def valid?
- gl_user.valid?
+ gl_user.try(:valid?)
end
def save
- gl_user.save!
- log.info "(OAuth) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
- gl_user.block if needs_blocking?
+ unauthorized_to_create unless gl_user
+ if needs_blocking?
+ gl_user.save!
+ gl_user.block
+ else
+ gl_user.save!
+ end
+
+ log.info "(OAuth) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}"
gl_user
rescue ActiveRecord::RecordInvalid => e
log.info "(OAuth) Error saving user: #{gl_user.errors.full_messages}"
@@ -36,33 +44,52 @@ module Gitlab
end
def gl_user
- @user ||= find_by_uid_and_provider || build_new_user
+ @user ||= find_by_uid_and_provider
+
+ if signup_enabled?
+ @user ||= build_new_user
+ end
+
+ @user
end
protected
+
+ def needs_blocking?
+ new? && block_after_signup?
+ end
+
+ def signup_enabled?
+ Gitlab.config.omniauth.allow_single_sign_on
+ end
+
+ def block_after_signup?
+ Gitlab.config.omniauth.block_auto_created_users
+ end
+
def auth_hash=(auth_hash)
@auth_hash = AuthHash.new(auth_hash)
end
def find_by_uid_and_provider
- model.where(provider: auth_hash.provider, extern_uid: auth_hash.uid).last
+ identity = Identity.find_by(provider: auth_hash.provider, extern_uid: auth_hash.uid)
+ identity && identity.user
end
def build_new_user
- model.new(user_attributes).tap do |user|
- user.skip_confirmation!
- end
+ user = ::User.new(user_attributes)
+ user.skip_confirmation!
+ user.identities.new(extern_uid: auth_hash.uid, provider: auth_hash.provider)
+ user
end
def user_attributes
{
- extern_uid: auth_hash.uid,
- provider: auth_hash.provider,
name: auth_hash.name,
username: auth_hash.username,
email: auth_hash.email,
password: auth_hash.password,
- password_confirmation: auth_hash.password,
+ password_confirmation: auth_hash.password
}
end
@@ -70,12 +97,8 @@ module Gitlab
Gitlab::AppLogger
end
- def needs_blocking?
- Gitlab.config.omniauth['block_auto_created_users']
- end
-
- def model
- ::User
+ def unauthorized_to_create
+ raise ForbiddenAction.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}")
end
end
end
diff --git a/lib/gitlab/production_logger.rb b/lib/gitlab/production_logger.rb
new file mode 100644
index 00000000000..89ce7144b1b
--- /dev/null
+++ b/lib/gitlab/production_logger.rb
@@ -0,0 +1,7 @@
+module Gitlab
+ class ProductionLogger < Gitlab::Logger
+ def self.file_name_noext
+ 'production'
+ end
+ end
+end
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 4b8038843b0..c4d0d85b7f5 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -67,8 +67,7 @@ module Gitlab
def default_regex_message
"can contain only letters, digits, '_', '-' and '.'. " \
- "It must start with letter, digit or '_', optionally preceeded by '.'. " \
- "It must not end in '.git'."
+ "Cannot start with '-' or end in '.git'" \
end
def default_regex
diff --git a/lib/gitlab/sidekiq_logger.rb b/lib/gitlab/sidekiq_logger.rb
new file mode 100644
index 00000000000..c1dab87a432
--- /dev/null
+++ b/lib/gitlab/sidekiq_logger.rb
@@ -0,0 +1,7 @@
+module Gitlab
+ class SidekiqLogger < Gitlab::Logger
+ def self.file_name_noext
+ 'sidekiq'
+ end
+ end
+end
diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb
new file mode 100644
index 00000000000..0fb09d3f228
--- /dev/null
+++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb
@@ -0,0 +1,48 @@
+module Gitlab
+ module SidekiqMiddleware
+ class MemoryKiller
+ # Wait 30 seconds for running jobs to finish during graceful shutdown
+ GRACEFUL_SHUTDOWN_WAIT = 30
+
+ def call(worker, job, queue)
+ yield
+ current_rss = get_rss
+ return unless max_rss > 0 && current_rss > max_rss
+
+ Sidekiq.logger.warn "current RSS #{current_rss} exceeds maximum RSS "\
+ "#{max_rss}"
+ Sidekiq.logger.warn "sending SIGUSR1 to PID #{Process.pid}"
+ # SIGUSR1 tells Sidekiq to stop accepting new jobs
+ Process.kill('SIGUSR1', Process.pid)
+
+ Sidekiq.logger.warn "spawning thread that will send SIGTERM to PID "\
+ "#{Process.pid} in #{graceful_shutdown_wait} seconds"
+ # Send the final shutdown signal to Sidekiq from a separate thread so
+ # that the current job can finish
+ Thread.new do
+ sleep(graceful_shutdown_wait)
+ Process.kill('SIGTERM', Process.pid)
+ end
+ end
+
+ private
+
+ def get_rss
+ output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{Process.pid}))
+ return 0 unless status.zero?
+
+ output.to_i
+ end
+
+ def max_rss
+ @max_rss ||= ENV['SIDEKIQ_MAX_RSS'].to_s.to_i
+ end
+
+ def graceful_shutdown_wait
+ @graceful_shutdown_wait ||= (
+ ENV['SIDEKIQ_GRACEFUL_SHUTDOWN_WAIT'] || GRACEFUL_SHUTDOWN_WAIT
+ ).to_i
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb
index de7e0404086..877488d8471 100644
--- a/lib/gitlab/url_builder.rb
+++ b/lib/gitlab/url_builder.rb
@@ -19,7 +19,7 @@ module Gitlab
issue = Issue.find(id)
project_issue_url(id: issue.iid,
project_id: issue.project,
- host: Settings.gitlab['url'])
+ host: Gitlab.config.gitlab['url'])
end
end
end
diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb
new file mode 100644
index 00000000000..bd184c27187
--- /dev/null
+++ b/lib/gitlab/utils.rb
@@ -0,0 +1,13 @@
+module Gitlab
+ module Utils
+ extend self
+
+ # Run system command without outputting to stdout.
+ #
+ # @param cmd [Array<String>]
+ # @return [Boolean]
+ def system_silent(cmd)
+ Popen::popen(cmd).last.zero?
+ end
+ end
+end
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 49a68c62293..c8b769ace8e 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -1,5 +1,5 @@
## GitLab
-## Maintainer: @randx
+## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller
##
## Lines starting with two hashes (##) are comments with information.
## Lines starting with one hash (#) are configuration parameters that can be uncommented.
@@ -15,7 +15,7 @@
## - installing an old version of Nginx with the chunkin module [2] compiled in, or
## - using a newer version of Nginx.
##
-## At the time of writing we do not know if either of these theoretical solutions works.
+## At the time of writing we do not know if either of these theoretical solutions works.
## As a workaround users can use Git over SSH to push large files.
##
## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99
@@ -26,6 +26,7 @@
## configuration ##
###################################
##
+## See installation.md#using-https for additional HTTPS configuration details.
upstream gitlab {
server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0;
@@ -33,7 +34,8 @@ upstream gitlab {
## Normal HTTP host
server {
- listen *:80 default_server;
+ listen 0.0.0.0:80 default_server;
+ listen [::]:80 default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
server_tokens off; ## Don't show the nginx version number, a security best practice
root /home/git/gitlab/public;
@@ -42,6 +44,8 @@ server {
## Or if you want to accept large git objects over http
client_max_body_size 20m;
+ ## See app/controllers/application_controller.rb for headers set
+
## Individual nginx logs for this GitLab vhost
access_log /var/log/nginx/gitlab_access.log;
error_log /var/log/nginx/gitlab_error.log;
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index d3fb467ef27..4e53d5e8b50 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -1,5 +1,5 @@
## GitLab
-## Contributors: randx, yin8086, sashkab, orkoden, axilleas
+## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller
##
## Modified from nginx http version
## Modified from http://blog.phusion.nl/2012/04/21/tutorial-setting-up-gitlab-on-debian-6/
@@ -26,9 +26,8 @@
## [1] https://github.com/agentzh/chunkin-nginx-module#status
## [2] https://github.com/agentzh/chunkin-nginx-module
##
-##
###################################
-## SSL configuration ##
+## configuration ##
###################################
##
## See installation.md#using-https for additional HTTPS configuration details.
@@ -37,22 +36,24 @@ upstream gitlab {
server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0;
}
-## Normal HTTP host
+## Redirects all HTTP traffic to the HTTPS host
server {
- listen *:80 default_server;
+ listen 0.0.0.0:80;
+ listen [::]:80 default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
server_tokens off; ## Don't show the nginx version number, a security best practice
-
- ## Redirects all traffic to the HTTPS host
- root /nowhere; ## root doesn't have to be a valid path since we are redirecting
- rewrite ^ https://$server_name$request_uri? permanent;
+ return 301 https://$server_name$request_uri;
+ access_log /var/log/nginx/gitlab_access.log;
+ error_log /var/log/nginx/gitlab_error.log;
}
+
## HTTPS host
server {
- listen 443 ssl;
+ listen 0.0.0.0:443 ssl;
+ listen [::]:443 ssl default_server;
server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com
- server_tokens off;
+ server_tokens off; ## Don't show the nginx version number, a security best practice
root /home/git/gitlab/public;
## Increase this if you want to upload large attachments
@@ -60,24 +61,19 @@ server {
client_max_body_size 20m;
## Strong SSL Security
- ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html
+ ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/
ssl on;
ssl_certificate /etc/nginx/ssl/gitlab.crt;
ssl_certificate_key /etc/nginx/ssl/gitlab.key;
# GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs
- ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4';
-
- ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
- ssl_session_cache builtin:1000 shared:SSL:10m;
-
- ssl_prefer_server_ciphers on;
+ ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
+ ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+ ssl_prefer_server_ciphers on;
+ ssl_session_cache shared:SSL:10m;
+ ssl_session_timeout 5m;
- ## [WARNING] The following header states that the browser should only communicate
- ## with your server over a secure connection for the next 24 months.
- add_header Strict-Transport-Security max-age=63072000;
- add_header X-Frame-Options SAMEORIGIN;
- add_header X-Content-Type-Options nosniff;
+ ## See app/controllers/application_controller.rb for headers set
## [Optional] If your certficate has OCSP, enable OCSP stapling to reduce the overhead and latency of running SSL.
## Replace with your ssl_trusted_certificate. For more info see:
@@ -88,11 +84,10 @@ server {
# ssl_stapling_verify on;
# ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt;
# resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired
- # resolver_timeout 10s;
+ # resolver_timeout 5s;
## [Optional] Generate a stronger DHE parameter:
- ## cd /etc/ssl/certs
- ## sudo openssl dhparam -out dhparam.pem 4096
+ ## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096
##
# ssl_dhparam /etc/ssl/certs/dhparam.pem;
diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake
index 2eff1260b61..0230fbb010b 100644
--- a/lib/tasks/gitlab/backup.rake
+++ b/lib/tasks/gitlab/backup.rake
@@ -6,6 +6,7 @@ namespace :gitlab do
desc "GITLAB | Create a backup of the GitLab system"
task create: :environment do
warn_user_is_not_gitlab
+ configure_cron_mode
Rake::Task["gitlab:backup:db:create"].invoke
Rake::Task["gitlab:backup:repo:create"].invoke
@@ -21,6 +22,7 @@ namespace :gitlab do
desc "GITLAB | Restore a previously created backup"
task restore: :environment do
warn_user_is_not_gitlab
+ configure_cron_mode
backup = Backup::Manager.new
backup.unpack
@@ -35,43 +37,54 @@ namespace :gitlab do
namespace :repo do
task create: :environment do
- puts "Dumping repositories ...".blue
+ $progress.puts "Dumping repositories ...".blue
Backup::Repository.new.dump
- puts "done".green
+ $progress.puts "done".green
end
task restore: :environment do
- puts "Restoring repositories ...".blue
+ $progress.puts "Restoring repositories ...".blue
Backup::Repository.new.restore
- puts "done".green
+ $progress.puts "done".green
end
end
namespace :db do
task create: :environment do
- puts "Dumping database ... ".blue
+ $progress.puts "Dumping database ... ".blue
Backup::Database.new.dump
- puts "done".green
+ $progress.puts "done".green
end
task restore: :environment do
- puts "Restoring database ... ".blue
+ $progress.puts "Restoring database ... ".blue
Backup::Database.new.restore
- puts "done".green
+ $progress.puts "done".green
end
end
namespace :uploads do
task create: :environment do
- puts "Dumping uploads ... ".blue
+ $progress.puts "Dumping uploads ... ".blue
Backup::Uploads.new.dump
- puts "done".green
+ $progress.puts "done".green
end
task restore: :environment do
- puts "Restoring uploads ... ".blue
+ $progress.puts "Restoring uploads ... ".blue
Backup::Uploads.new.restore
- puts "done".green
+ $progress.puts "done".green
+ end
+ end
+
+ def configure_cron_mode
+ if ENV['CRON']
+ # We need an object we can say 'puts' and 'print' to; let's use a
+ # StringIO.
+ require 'stringio'
+ $progress = StringIO.new
+ else
+ $progress = $stdout
end
end
end # namespace end: backup
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 9ec368254ac..7ff23a7600a 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -574,20 +574,16 @@ namespace :gitlab do
Gitlab::Shell.new.version
end
- def required_gitlab_shell_version
- File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip
- end
-
def gitlab_shell_major_version
- required_gitlab_shell_version.split(".")[0].to_i
+ Gitlab::Shell.version_required.split('.')[0].to_i
end
def gitlab_shell_minor_version
- required_gitlab_shell_version.split(".")[1].to_i
+ Gitlab::Shell.version_required.split('.')[1].to_i
end
def gitlab_shell_patch_version
- required_gitlab_shell_version.split(".")[2].to_i
+ Gitlab::Shell.version_required.split('.')[2].to_i
end
def has_gitlab_shell3?
@@ -664,7 +660,7 @@ namespace :gitlab do
warn_user_is_not_gitlab
start_checking "LDAP"
- if ldap_config.enabled
+ if Gitlab::LDAP::Config.enabled?
print_users(args.limit)
else
puts 'LDAP is disabled in config/gitlab.yml'
@@ -675,39 +671,19 @@ namespace :gitlab do
def print_users(limit)
puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)"
- ldap.search(attributes: attributes, filter: filter, size: limit, return_result: false) do |entry|
- puts "DN: #{entry.dn}\t#{ldap_config.uid}: #{entry[ldap_config.uid]}"
- end
- end
- def attributes
- [ldap_config.uid]
- end
+ servers = Gitlab::LDAP::Config.providers
- def filter
- uid_filter = Net::LDAP::Filter.present?(ldap_config.uid)
- if user_filter
- Net::LDAP::Filter.join(uid_filter, user_filter)
- else
- uid_filter
- end
- end
-
- def user_filter
- if ldap_config['user_filter'] && ldap_config.user_filter.present?
- Net::LDAP::Filter.construct(ldap_config.user_filter)
- else
- nil
+ servers.each do |server|
+ puts "Server: #{server}"
+ Gitlab::LDAP::Adapter.open(server) do |adapter|
+ users = adapter.users(adapter.config.uid, '*', 100)
+ users.each do |user|
+ puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}"
+ end
+ end
end
end
-
- def ldap
- @ldap ||= OmniAuth::LDAP::Adaptor.new(ldap_config).connection
- end
-
- def ldap_config
- @ldap_config ||= Gitlab.config.ldap
- end
end
# Helper methods
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index 63dcdc52370..189ad6090a4 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -92,11 +92,11 @@ namespace :gitlab do
User.ldap.each do |ldap_user|
print "#{ldap_user.name} (#{ldap_user.extern_uid}) ..."
- if Gitlab::LDAP::Access.open { |access| access.allowed?(ldap_user) }
+ if Gitlab::LDAP::Access.allowed?(ldap_user)
puts " [OK]".green
else
if block_flag
- ldap_user.block!
+ ldap_user.block! unless ldap_user.blocked?
puts " [BLOCKED]".red
else
puts " [NOT IN LDAP]".yellow
diff --git a/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake b/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake
new file mode 100644
index 00000000000..e9cf0a9b5e8
--- /dev/null
+++ b/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake
@@ -0,0 +1,10 @@
+namespace :gitlab do
+ namespace :db do
+ task drop_all_postgres_sequences: :environment do
+ connection = ActiveRecord::Base.connection
+ connection.execute("SELECT c.relname FROM pg_class c WHERE c.relkind = 'S';").each do |sequence|
+ connection.execute("DROP SEQUENCE #{sequence['relname']}")
+ end
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index b6ed874e11a..3c693546c09 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -15,26 +15,17 @@ namespace :gitlab do
git_base_path = Gitlab.config.gitlab_shell.repos_path
repos_to_import = Dir.glob(git_base_path + '/**/*.git')
- namespaces = Namespace.pluck(:path)
-
repos_to_import.each do |repo_path|
# strip repo base path
repo_path[0..git_base_path.length] = ''
path = repo_path.sub(/\.git$/, '')
- name = File.basename path
- group_name = File.dirname path
+ group_name, name = File.split(path)
group_name = nil if group_name == '.'
- # Skip if group or user
- if namespaces.include?(name)
- puts "Skipping #{project.name} due to namespace conflict with group or user".yellow
- next
- end
-
puts "Processing #{repo_path}".yellow
- if path =~ /.wiki\Z/
+ if path =~ /\.wiki\Z/
puts " * Skipping wiki repo"
next
end
@@ -53,9 +44,9 @@ namespace :gitlab do
# find group namespace
if group_name
- group = Group.find_by(path: group_name)
+ group = Namespace.find_by(path: group_name)
# create group namespace
- if !group
+ unless group
group = Group.new(:name => group_name)
group.path = group_name
group.owner = user
diff --git a/lib/tasks/gitlab/mail_google_schema_whitelisting.rake b/lib/tasks/gitlab/mail_google_schema_whitelisting.rake
new file mode 100644
index 00000000000..f40bba24da8
--- /dev/null
+++ b/lib/tasks/gitlab/mail_google_schema_whitelisting.rake
@@ -0,0 +1,73 @@
+require "#{Rails.root}/app/helpers/emails_helper"
+require 'action_view/helpers'
+extend ActionView::Helpers
+
+include ActionView::Context
+include EmailsHelper
+
+namespace :gitlab do
+ desc "Email google whitelisting email with example email for actions in inbox"
+ task mail_google_schema_whitelisting: :environment do
+ subject = "Rails | Implemented feature"
+ url = "#{Gitlab.config.gitlab.url}/base/rails-project/issues/#{rand(1..100)}#note_#{rand(10..1000)}"
+ schema = email_action(url)
+ body = email_template(schema, url)
+ mail = Notify.test_email("schema.whitelisting+sample@gmail.com", subject, body.html_safe)
+ if send_now
+ mail.deliver
+ else
+ puts "WOULD SEND:"
+ end
+ puts mail
+ end
+
+ def email_template(schema, url)
+ "<html lang='en'>
+ <head>
+ <meta content='text/html; charset=utf-8' http-equiv='Content-Type'>
+ <title>
+ GitLab
+ </title>
+ </meta>
+ </head>
+ <style>
+ img {
+ max-width: 100%;
+ height: auto;
+ }
+ p.details {
+ font-style:italic;
+ color:#777
+ }
+ .footer p {
+ font-size:small;
+ color:#777
+ }
+ </style>
+ <body>
+ <div class='content'>
+ <div>
+ <p>I like it :+1: </p>
+ </div>
+ </div>
+
+ <div class='footer' style='margin-top: 10px;'>
+ <p>
+ <br>
+ You're receiving this notification because you are a member of the Base / Rails Project project team.
+ <a href=\"#{url}\">View it on GitLab</a>
+ #{schema}
+ </p>
+ </div>
+ </body>
+ </html>"
+ end
+
+ def send_now
+ if ENV['SEND'] == "true"
+ true
+ else
+ false
+ end
+ end
+end
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index a8f26a7c029..202e55c89ad 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -4,20 +4,20 @@ namespace :gitlab do
task :install, [:tag, :repo] => :environment do |t, args|
warn_user_is_not_gitlab
- default_version = File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip
+ default_version = Gitlab::Shell.version_required
args.with_defaults(tag: 'v' + default_version, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git")
- user = Settings.gitlab.user
- home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Settings.gitlab.user_home
- gitlab_url = Settings.gitlab.url
+ user = Gitlab.config.gitlab.user
+ home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home
+ gitlab_url = Gitlab.config.gitlab.url
# gitlab-shell requires a / at the end of the url
- gitlab_url += "/" unless gitlab_url.match(/\/$/)
+ gitlab_url += '/' unless gitlab_url.end_with?('/')
repos_path = Gitlab.config.gitlab_shell.repos_path
target_dir = Gitlab.config.gitlab_shell.path
# Clone if needed
unless File.directory?(target_dir)
- sh "git clone '#{args.repo}' '#{target_dir}'"
+ sh(*%W(git clone #{args.repo} #{target_dir}))
end
# Make sure we're on the right tag
@@ -76,7 +76,7 @@ namespace :gitlab do
desc "GITLAB | Build missing projects"
task build_missing_projects: :environment do
Project.find_each(batch_size: 1000) do |project|
- path_to_repo = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
+ path_to_repo = project.repository.path_to_repo
if File.exists?(path_to_repo)
print '-'
else
diff --git a/spec/controllers/branches_controller_spec.rb b/spec/controllers/branches_controller_spec.rb
new file mode 100644
index 00000000000..610d7a84e31
--- /dev/null
+++ b/spec/controllers/branches_controller_spec.rb
@@ -0,0 +1,51 @@
+require 'spec_helper'
+
+describe Projects::BranchesController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+
+ before do
+ sign_in(user)
+
+ project.team << [user, :master]
+
+ project.stub(:branches).and_return(['master', 'foo/bar/baz'])
+ project.stub(:tags).and_return(['v1.0.0', 'v2.0.0'])
+ controller.instance_variable_set(:@project, project)
+ end
+
+ describe "POST create" do
+ render_views
+
+ before {
+ post :create,
+ project_id: project.to_param,
+ branch_name: branch,
+ ref: ref
+ }
+
+ context "valid branch name, valid source" do
+ let(:branch) { "merge_branch" }
+ let(:ref) { "master" }
+ it { should redirect_to("/#{project.path_with_namespace}/tree/merge_branch") }
+ end
+
+ context "invalid branch name, valid ref" do
+ let(:branch) { "<script>alert('merge');</script>" }
+ let(:ref) { "master" }
+ it { should redirect_to("/#{project.path_with_namespace}/tree/alert('merge');") }
+ end
+
+ context "valid branch name, invalid ref" do
+ let(:branch) { "merge_branch" }
+ let(:ref) { "<script>alert('ref');</script>" }
+ it { should render_template("new") }
+ end
+
+ context "invalid branch name, invalid ref" do
+ let(:branch) { "<script>alert('merge');</script>" }
+ let(:ref) { "<script>alert('ref');</script>" }
+ it { should render_template("new") }
+ end
+ end
+end
diff --git a/spec/factories.rb b/spec/factories.rb
index 15899d8c3c4..58060131638 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -18,15 +18,24 @@ FactoryGirl.define do
password "12345678"
password_confirmation { password }
confirmed_at { Time.now }
- confirmation_token { nil }
+ confirmation_token { nil }
trait :admin do
admin true
end
- trait :ldap do
- provider 'ldapmain'
- extern_uid 'my-ldap-id'
+ factory :omniauth_user do
+ ignore do
+ extern_uid '123456'
+ provider 'ldapmain'
+ end
+
+ after(:create) do |user, evaluator|
+ user.identities << create(:identity,
+ provider: evaluator.provider,
+ extern_uid: evaluator.extern_uid
+ )
+ end
end
factory :admin, traits: [:admin]
@@ -182,4 +191,9 @@ FactoryGirl.define do
deploy_key
project
end
+
+ factory :identity do
+ provider 'ldapmain'
+ extern_uid 'my-ldap-id'
+ end
end
diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb
index 23314b3b1a4..60eb73e4a95 100644
--- a/spec/factories/projects.rb
+++ b/spec/factories/projects.rb
@@ -27,6 +27,10 @@
#
FactoryGirl.define do
+ # Project without repository
+ #
+ # Project does not have bare repository.
+ # Use this factory if you dont need repository in tests
factory :empty_project, class: 'Project' do
sequence(:name) { |n| "project#{n}" }
path { name.downcase.gsub(/\s/, '_') }
@@ -47,6 +51,20 @@ FactoryGirl.define do
end
end
+ # Project with empty repository
+ #
+ # This is a case when you just created a project
+ # but not pushed any code there yet
+ factory :project_empty_repo, parent: :empty_project do
+ after :create do |project|
+ project.create_repository
+ end
+ end
+
+ # Project with test repository
+ #
+ # Test repository source can be found at
+ # https://gitlab.com/gitlab-org/gitlab-test
factory :project, parent: :empty_project do
path { 'gitlabhq' }
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
new file mode 100644
index 00000000000..746b6fc1ac9
--- /dev/null
+++ b/spec/features/atom/users_spec.rb
@@ -0,0 +1,43 @@
+require 'spec_helper'
+
+describe "User Feed", feature: true do
+ describe "GET /" do
+ let!(:user) { create(:user) }
+
+ context "user atom feed via private token" do
+ it "should render user atom feed" do
+ visit user_path(user, :atom, private_token: user.private_token)
+ body.should have_selector("feed title")
+ end
+ end
+
+ context 'feed content' do
+ let(:project) { create(:project) }
+ let(:issue) { create(:issue, project: project, author: user, description: '') }
+ let(:note) { create(:note, noteable: issue, author: user, note: 'Bug confirmed', project: project) }
+
+ before do
+ project.team << [user, :master]
+ issue_event(issue, user)
+ note_event(note, user)
+ visit user_path(user, :atom, private_token: user.private_token)
+ end
+
+ it "should have issue opened event" do
+ body.should have_content("#{user.name} opened issue ##{issue.iid}")
+ end
+
+ it "should have issue comment event" do
+ body.should have_content("#{user.name} commented on issue ##{issue.iid}")
+ end
+ end
+ end
+
+ def issue_event(issue, user)
+ EventCreateService.new.open_issue(issue, user)
+ end
+
+ def note_event(note, user)
+ EventCreateService.new.leave_note(note, user)
+ end
+end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index 98ba5a47ee5..d291621935b 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe "Projects", feature: true do
+describe "Projects", feature: true, js: true do
before { login_as :user }
describe "DELETE /projects/:id" do
@@ -10,12 +10,23 @@ describe "Projects", feature: true do
visit edit_project_path(@project)
end
- it "should be correct path", js: true do
- expect {
- click_link "Remove project"
- fill_in 'confirm_name_input', with: @project.path
- click_button 'Confirm'
- }.to change {Project.count}.by(-1)
+ it "should remove project" do
+ expect { remove_project }.to change {Project.count}.by(-1)
end
+
+ it 'should delete the project from disk' do
+ expect(GitlabShellWorker).to(
+ receive(:perform_async).with(:remove_repository,
+ /#{@project.path_with_namespace}/)
+ ).twice
+
+ remove_project
+ end
+ end
+
+ def remove_project
+ click_link "Remove project"
+ fill_in 'confirm_name_input', with: @project.path
+ click_button 'Confirm'
end
end
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index 7b831c48611..a1206989d39 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -11,7 +11,7 @@ describe 'Users', feature: true do
fill_in "user_name", with: "Name Surname"
fill_in "user_username", with: "Great"
fill_in "user_email", with: "name@mail.com"
- fill_in "user_password", with: "password1234"
+ fill_in "user_password_sign_up", with: "password1234"
fill_in "user_password_confirmation", with: "password1234"
expect { click_button "Sign up" }.to change {User.count}.by(1)
end
diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb
index 7489e56f423..06e247aea61 100644
--- a/spec/finders/issues_finder_spec.rb
+++ b/spec/finders/issues_finder_spec.rb
@@ -5,9 +5,10 @@ describe IssuesFinder do
let(:user2) { create :user }
let(:project1) { create(:project) }
let(:project2) { create(:project) }
- let(:issue1) { create(:issue, assignee: user, project: project1) }
- let(:issue2) { create(:issue, assignee: user, project: project2) }
- let(:issue3) { create(:issue, assignee: user2, project: project2) }
+ let(:milestone) { create(:milestone, project: project1) }
+ let(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone) }
+ let(:issue2) { create(:issue, author: user, assignee: user, project: project2) }
+ let(:issue3) { create(:issue, author: user2, assignee: user2, project: project2) }
before do
project1.team << [user, :master]
@@ -22,37 +23,59 @@ describe IssuesFinder do
issue3
end
- it 'should filter by all' do
- params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new.execute(user, params)
- issues.size.should == 3
- end
+ context 'scope: all' do
+ it 'should filter by all' do
+ params = { scope: "all", state: 'opened' }
+ issues = IssuesFinder.new.execute(user, params)
+ issues.size.should == 3
+ end
- it 'should filter by assignee' do
- params = { scope: "assigned-to-me", state: 'opened' }
- issues = IssuesFinder.new.execute(user, params)
- issues.size.should == 2
- end
+ it 'should filter by assignee id' do
+ params = { scope: "all", assignee_id: user.id, state: 'opened' }
+ issues = IssuesFinder.new.execute(user, params)
+ issues.size.should == 2
+ end
- it 'should filter by project' do
- params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id }
- issues = IssuesFinder.new.execute(user, params)
- issues.size.should == 1
- end
+ it 'should filter by author id' do
+ params = { scope: "all", author_id: user2.id, state: 'opened' }
+ issues = IssuesFinder.new.execute(user, params)
+ issues.should == [issue3]
+ end
- it 'should be empty for unauthorized user' do
- params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new.execute(nil, params)
- issues.size.should be_zero
+ it 'should filter by milestone id' do
+ params = { scope: "all", milestone_id: milestone.id, state: 'opened' }
+ issues = IssuesFinder.new.execute(user, params)
+ issues.should == [issue1]
+ end
+
+ it 'should be empty for unauthorized user' do
+ params = { scope: "all", state: 'opened' }
+ issues = IssuesFinder.new.execute(nil, params)
+ issues.size.should be_zero
+ end
+
+ it 'should not include unauthorized issues' do
+ params = { scope: "all", state: 'opened' }
+ issues = IssuesFinder.new.execute(user2, params)
+ issues.size.should == 2
+ issues.should_not include(issue1)
+ issues.should include(issue2)
+ issues.should include(issue3)
+ end
end
- it 'should not include unauthorized issues' do
- params = { scope: "all", state: 'opened' }
- issues = IssuesFinder.new.execute(user2, params)
- issues.size.should == 2
- issues.should_not include(issue1)
- issues.should include(issue2)
- issues.should include(issue3)
+ context 'personal scope' do
+ it 'should filter by assignee' do
+ params = { scope: "assigned-to-me", state: 'opened' }
+ issues = IssuesFinder.new.execute(user, params)
+ issues.size.should == 2
+ end
+
+ it 'should filter by project' do
+ params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id }
+ issues = IssuesFinder.new.execute(user, params)
+ issues.size.should == 1
+ end
end
end
end
diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb
index 5af76968183..c645cbc964c 100644
--- a/spec/finders/snippets_finder_spec.rb
+++ b/spec/finders/snippets_finder_spec.rb
@@ -64,6 +64,13 @@ describe SnippetsFinder do
snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user)
snippets.should include(@snippet1, @snippet2, @snippet3)
end
+
+ it "returns only public snippets if unauthenticated user" do
+ snippets = SnippetsFinder.new.execute(nil, filter: :by_user, user: user)
+ snippets.should include(@snippet3)
+ snippets.should_not include(@snippet2, @snippet1)
+ end
+
end
context 'by_project filter' do
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 2db67cfdf95..07dd33b211b 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -66,6 +66,16 @@ describe ApplicationHelper do
avatar_icon(user.email).to_s.should match("/uploads/user/avatar/#{ user.id }/gitlab_logo.png")
end
+ it "should return an url for the avatar with relative url" do
+ Gitlab.config.gitlab.stub(relative_url_root: "/gitlab")
+ Gitlab.config.gitlab.stub(url: Settings.send(:build_gitlab_url))
+
+ user = create(:user)
+ user.avatar = File.open(avatar_file_path)
+ user.save!
+ avatar_icon(user.email).to_s.should match("/gitlab//uploads/user/avatar/#{ user.id }/gitlab_logo.png")
+ end
+
it "should call gravatar_icon when no avatar is present" do
user = create(:user, email: 'test@example.com')
user.save!
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 61751a82369..3c636b747d1 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -594,7 +594,9 @@ describe GitlabMarkdownHelper do
end
it "should generate absolute urls for emoji" do
- markdown(":smile:").should include("src=\"http://localhost/assets/emoji/smile.png")
+ markdown(':smile:').should(
+ include(%(src="#{Gitlab.config.gitlab.url}/assets/emoji/smile.png))
+ )
end
it "should generate absolute urls for emoji if relative url is present" do
diff --git a/spec/helpers/oauth_helper_spec.rb b/spec/helpers/oauth_helper_spec.rb
new file mode 100644
index 00000000000..453699136e9
--- /dev/null
+++ b/spec/helpers/oauth_helper_spec.rb
@@ -0,0 +1,20 @@
+require "spec_helper"
+
+describe OauthHelper do
+ describe "additional_providers" do
+ it 'returns all enabled providers' do
+ allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :github] }
+ helper.additional_providers.should include(*[:twitter, :github])
+ end
+
+ it 'does not return ldap provider' do
+ allow(helper).to receive(:enabled_oauth_providers) { [:twitter, :ldapmain] }
+ helper.additional_providers.should include(:twitter)
+ end
+
+ it 'returns empty array' do
+ allow(helper).to receive(:enabled_oauth_providers) { [] }
+ helper.additional_providers.should == []
+ end
+ end
+end \ No newline at end of file
diff --git a/spec/lib/disable_email_interceptor_spec.rb b/spec/lib/disable_email_interceptor_spec.rb
new file mode 100644
index 00000000000..8bf6ee2ed50
--- /dev/null
+++ b/spec/lib/disable_email_interceptor_spec.rb
@@ -0,0 +1,26 @@
+require 'spec_helper'
+
+describe DisableEmailInterceptor do
+ before do
+ ActionMailer::Base.register_interceptor(DisableEmailInterceptor)
+ end
+
+ it 'should not send emails' do
+ Gitlab.config.gitlab.stub(:email_enabled).and_return(false)
+ expect {
+ deliver_mail
+ }.not_to change(ActionMailer::Base.deliveries, :count)
+ end
+
+ after do
+ # Removing interceptor from the list because unregister_interceptor is
+ # implemented in later version of mail gem
+ # See: https://github.com/mikel/mail/pull/705
+ Mail.class_variable_set(:@@delivery_interceptors, [])
+ end
+
+ def deliver_mail
+ key = create :personal_key
+ Notify.new_ssh_key_email(key.id)
+ end
+end
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 1f3e1a4a3c1..95fc7e16a11 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -10,13 +10,21 @@ describe Gitlab::Auth do
password: password,
password_confirmation: password)
end
- let(:username) { 'john' }
+ let(:username) { 'John' } # username isn't lowercase, test this
let(:password) { 'my-secret' }
it "should find user by valid login/password" do
expect( gl_auth.find(username, password) ).to eql user
end
+ it 'should find user by valid email/password with case-insensitive email' do
+ expect(gl_auth.find(user.email.upcase, password)).to eql user
+ end
+
+ it 'should find user by valid username/password with case-insensitive username' do
+ expect(gl_auth.find(username.upcase, password)).to eql user
+ end
+
it "should not find user with invalid password" do
password = 'wrong'
expect( gl_auth.find(username, password) ).to_not eql user
diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb
index 570b03827a8..66e87e57cbc 100644
--- a/spec/lib/gitlab/git_access_spec.rb
+++ b/spec/lib/gitlab/git_access_spec.rb
@@ -5,14 +5,14 @@ describe Gitlab::GitAccess do
let(:project) { create(:project) }
let(:user) { create(:user) }
- describe 'download_allowed?' do
+ describe 'download_access_check' do
describe 'master permissions' do
before { project.team << [user, :master] }
context 'pull code' do
- subject { access.download_allowed?(user, project) }
+ subject { access.download_access_check(user, project) }
- it { should be_true }
+ it { subject.allowed?.should be_true }
end
end
@@ -20,9 +20,9 @@ describe Gitlab::GitAccess do
before { project.team << [user, :guest] }
context 'pull code' do
- subject { access.download_allowed?(user, project) }
+ subject { access.download_access_check(user, project) }
- it { should be_false }
+ it { subject.allowed?.should be_false }
end
end
@@ -33,34 +33,54 @@ describe Gitlab::GitAccess do
end
context 'pull code' do
- subject { access.download_allowed?(user, project) }
+ subject { access.download_access_check(user, project) }
- it { should be_false }
+ it { subject.allowed?.should be_false }
end
end
describe 'without acccess to project' do
context 'pull code' do
- subject { access.download_allowed?(user, project) }
+ subject { access.download_access_check(user, project) }
- it { should be_false }
+ it { subject.allowed?.should be_false }
+ end
+ end
+
+ describe 'deploy key permissions' do
+ let(:key) { create(:deploy_key) }
+
+ context 'pull code' do
+ context 'allowed' do
+ before { key.projects << project }
+ subject { access.download_access_check(key, project) }
+
+ it { subject.allowed?.should be_true }
+ end
+
+ context 'denied' do
+ subject { access.download_access_check(key, project) }
+
+ it { subject.allowed?.should be_false }
+ end
end
end
end
- describe 'push_allowed?' do
+ describe 'push_access_check' do
def protect_feature_branch
create(:protected_branch, name: 'feature', project: project)
end
def changes
{
- push_new_branch: '000000000 570e7b2ab refs/heads/wow',
+ push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow",
push_master: '6f6d7e7ed 570e7b2ab refs/heads/master',
push_protected_branch: '6f6d7e7ed 570e7b2ab refs/heads/feature',
- push_remove_protected_branch: '570e7b2ab 000000000 refs/heads/feature',
+ push_remove_protected_branch: "570e7b2ab #{Gitlab::Git::BLANK_SHA} "\
+ 'refs/heads/feature',
push_tag: '6f6d7e7ed 570e7b2ab refs/tags/v1.0.0',
- push_new_tag: '000000000 570e7b2ab refs/tags/v7.8.9',
+ push_new_tag: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/tags/v7.8.9",
push_all: ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature']
}
end
@@ -116,9 +136,9 @@ describe Gitlab::GitAccess do
permissions_matrix[role].each do |action, allowed|
context action do
- subject { access.push_allowed?(user, project, changes[action]) }
+ subject { access.push_access_check(user, project, changes[action]) }
- it { should allowed ? be_true : be_false }
+ it { subject.allowed?.should allowed ? be_true : be_false }
end
end
end
diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb
index ed5785b31e6..4ff45c0c616 100644
--- a/spec/lib/gitlab/git_access_wiki_spec.rb
+++ b/spec/lib/gitlab/git_access_wiki_spec.rb
@@ -11,9 +11,9 @@ describe Gitlab::GitAccessWiki do
project.team << [user, :developer]
end
- subject { access.push_allowed?(user, project, changes) }
+ subject { access.push_access_check(user, project, changes) }
- it { should be_true }
+ it { subject.allowed?.should be_true }
end
def changes
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index f4d5a927396..4573b8696c4 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::LDAP::Access do
let(:access) { Gitlab::LDAP::Access.new user }
- let(:user) { create(:user, :ldap) }
+ let(:user) { create(:omniauth_user) }
describe :allowed? do
subject { access.allowed? }
diff --git a/spec/lib/gitlab/ldap/authentication_spec.rb b/spec/lib/gitlab/ldap/authentication_spec.rb
index 0eb7c443b8b..11fdf108756 100644
--- a/spec/lib/gitlab/ldap/authentication_spec.rb
+++ b/spec/lib/gitlab/ldap/authentication_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::LDAP::Authentication do
let(:klass) { Gitlab::LDAP::Authentication }
- let(:user) { create(:user, :ldap, extern_uid: dn) }
+ let(:user) { create(:omniauth_user, extern_uid: dn) }
let(:dn) { 'uid=john,ou=people,dc=example,dc=com' }
let(:login) { 'john' }
let(:password) { 'password' }
diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb
index 76cc7f95c47..3ebb8aae243 100644
--- a/spec/lib/gitlab/ldap/config_spec.rb
+++ b/spec/lib/gitlab/ldap/config_spec.rb
@@ -16,5 +16,19 @@ describe Gitlab::LDAP::Config do
it "raises an error if a unknow provider is used" do
expect{ Gitlab::LDAP::Config.new 'unknown' }.to raise_error
end
+
+ context "if 'ldap' is the provider name" do
+ let(:provider) { 'ldap' }
+
+ context "and 'ldap' is not in defined as a provider" do
+ before { Gitlab::LDAP::Config.stub(providers: %w{ldapmain}) }
+
+ it "uses the first provider" do
+ # Fetch the provider_name attribute from 'options' so that we know
+ # that the 'options' Hash is not empty/nil.
+ expect(config.options['provider_name']).to eq('ldapmain')
+ end
+ end
+ end
end
-end \ No newline at end of file
+end
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index 726c9764e3d..f73884e6441 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -15,18 +15,18 @@ describe Gitlab::LDAP::User do
describe :find_or_create do
it "finds the user if already existing" do
- existing_user = create(:user, extern_uid: 'my-uid', provider: 'ldapmain')
+ existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
expect{ gl_user.save }.to_not change{ User.count }
end
it "connects to existing non-ldap user if the email matches" do
- existing_user = create(:user, email: 'john@example.com')
+ existing_user = create(:omniauth_user, email: 'john@example.com', provider: "twitter")
expect{ gl_user.save }.to_not change{ User.count }
existing_user.reload
- expect(existing_user.extern_uid).to eql 'my-uid'
- expect(existing_user.provider).to eql 'ldapmain'
+ expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
+ expect(existing_user.ldap_identity.provider).to eql 'ldapmain'
end
it "creates a new user if not found" do
diff --git a/spec/lib/gitlab/oauth/user_spec.rb b/spec/lib/gitlab/oauth/user_spec.rb
index e4e96fd9f49..88307515789 100644
--- a/spec/lib/gitlab/oauth/user_spec.rb
+++ b/spec/lib/gitlab/oauth/user_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::OAuth::User do
end
describe :persisted? do
- let!(:existing_user) { create(:user, extern_uid: 'my-uid', provider: 'my-provider') }
+ let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
it "finds an existing user based on uid and provider (facebook)" do
auth = double(info: double(name: 'John'), uid: 'my-uid', provider: 'my-provider')
@@ -29,26 +29,80 @@ describe Gitlab::OAuth::User do
end
describe :save do
- context "LDAP" do
- let(:provider) { 'ldap' }
- it "creates a user from LDAP" do
- oauth_user.save
-
- expect(gl_user).to be_valid
- expect(gl_user.extern_uid).to eql uid
- expect(gl_user.provider).to eql 'ldap'
+ let(:provider) { 'twitter' }
+
+ describe 'signup' do
+ context "with allow_single_sign_on enabled" do
+ before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
+
+ it "creates a user from Omniauth" do
+ oauth_user.save
+
+ expect(gl_user).to be_valid
+ identity = gl_user.identities.first
+ expect(identity.extern_uid).to eql uid
+ expect(identity.provider).to eql 'twitter'
+ end
+ end
+
+ context "with allow_single_sign_on disabled (Default)" do
+ it "throws an error" do
+ expect{ oauth_user.save }.to raise_error StandardError
+ end
end
end
- context "twitter" do
+ describe 'blocking' do
let(:provider) { 'twitter' }
+ before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
+
+ context 'signup' do
+ context 'dont block on create' do
+ before { Gitlab.config.omniauth.stub block_auto_created_users: false }
+
+ it do
+ oauth_user.save
+ gl_user.should be_valid
+ gl_user.should_not be_blocked
+ end
+ end
+
+ context 'block on create' do
+ before { Gitlab.config.omniauth.stub block_auto_created_users: true }
+
+ it do
+ oauth_user.save
+ gl_user.should be_valid
+ gl_user.should be_blocked
+ end
+ end
+ end
+
+ context 'sign-in' do
+ before do
+ oauth_user.save
+ oauth_user.gl_user.activate
+ end
+
+ context 'dont block on create' do
+ before { Gitlab.config.omniauth.stub block_auto_created_users: false }
+
+ it do
+ oauth_user.save
+ gl_user.should be_valid
+ gl_user.should_not be_blocked
+ end
+ end
- it "creates a user from Omniauth" do
- oauth_user.save
+ context 'block on create' do
+ before { Gitlab.config.omniauth.stub block_auto_created_users: true }
- expect(gl_user).to be_valid
- expect(gl_user.extern_uid).to eql uid
- expect(gl_user.provider).to eql 'twitter'
+ it do
+ oauth_user.save
+ gl_user.should be_valid
+ gl_user.should_not be_blocked
+ end
+ end
end
end
end
diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb
index e06e8826e5c..a0c37587b23 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -46,7 +46,7 @@ describe Notify do
token = 'kETLwRaayvigPq_x3SNM'
- subject { Notify.new_user_email(new_user.id, new_user.password, token) }
+ subject { Notify.new_user_email(new_user.id, token) }
it_behaves_like 'an email sent from GitLab'
@@ -83,7 +83,7 @@ describe Notify do
let(:example_site_path) { root_path }
let(:new_user) { create(:user, email: 'newguy@example.com', password: "securePassword") }
- subject { Notify.new_user_email(new_user.id, new_user.password) }
+ subject { Notify.new_user_email(new_user.id) }
it_behaves_like 'an email sent from GitLab'
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
new file mode 100644
index 00000000000..ca6f11b2a4d
--- /dev/null
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -0,0 +1,14 @@
+require 'spec_helper'
+
+describe Issue, "Mentionable" do
+ describe :mentioned_users do
+ let!(:user) { create(:user, username: 'stranger') }
+ let!(:user2) { create(:user, username: 'john') }
+ let!(:issue) { create(:issue, description: '@stranger mentioned') }
+
+ subject { issue.mentioned_users }
+
+ it { should include(user) }
+ it { should_not include(user2) }
+ end
+end
diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb
index 1fdd959da9d..204ae9da704 100644
--- a/spec/models/event_spec.rb
+++ b/spec/models/event_spec.rb
@@ -36,7 +36,7 @@ describe Event do
@user = project.owner
data = {
- before: "0000000000000000000000000000000000000000",
+ before: Gitlab::Git::BLANK_SHA,
after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e",
ref: "refs/heads/master",
user_id: @user.id,
@@ -60,7 +60,6 @@ describe Event do
it { @event.push?.should be_true }
it { @event.proper?.should be_true }
- it { @event.new_branch?.should be_true }
it { @event.tag?.should be_false }
it { @event.branch_name.should == "master" }
it { @event.author.should == @user }
diff --git a/spec/models/gitlab_ci_service_spec.rb b/spec/models/gitlab_ci_service_spec.rb
index ebc377047be..83277058fbb 100644
--- a/spec/models/gitlab_ci_service_spec.rb
+++ b/spec/models/gitlab_ci_service_spec.rb
@@ -34,11 +34,11 @@ describe GitlabCiService do
end
describe :commit_status_path do
- it { @service.commit_status_path("2ab7834c").should == "http://ci.gitlab.org/projects/2/builds/2ab7834c/status.json?token=verySecret"}
+ it { @service.commit_status_path("2ab7834c").should == "http://ci.gitlab.org/projects/2/commits/2ab7834c/status.json?token=verySecret"}
end
describe :build_page do
- it { @service.build_page("2ab7834c").should == "http://ci.gitlab.org/projects/2/builds/2ab7834c"}
+ it { @service.build_page("2ab7834c").should == "http://ci.gitlab.org/projects/2/commits/2ab7834c"}
end
end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 2d839e9611b..6ab7162c15c 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -249,6 +249,12 @@ describe Note do
its(:note) { should == "_mentioned in merge request !#{mergereq.iid}_" }
end
+ context 'commit contained in a merge request' do
+ subject { Note.create_cross_reference_note(mergereq.commits.first, mergereq, author, project) }
+
+ it { should be_nil }
+ end
+
context 'commit from issue' do
subject { Note.create_cross_reference_note(commit, issue, author, project) }
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 48b58400a1e..70a15cac1a8 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -145,63 +145,6 @@ describe Project do
end
end
- describe 'comment merge requests with commits' do
- before do
- @user = create(:user)
- group = create(:group)
- group.add_owner(@user)
-
- @project = create(:project, namespace: group)
- @fork_project = Projects::ForkService.new(@project, @user).execute
- @merge_request = create(:merge_request, source_project: @project,
- source_branch: 'master',
- target_branch: 'feature',
- target_project: @project)
- @fork_merge_request = create(:merge_request, source_project: @fork_project,
- source_branch: 'master',
- target_branch: 'feature',
- target_project: @project)
-
- @commits = @merge_request.commits
- end
-
- context 'push to origin repo source branch' do
- before do
- @project.comment_mr_with_commits('master', @commits, @user)
- end
-
- it { @merge_request.notes.should_not be_empty }
- it { @fork_merge_request.notes.should be_empty }
- end
-
- context 'push to origin repo target branch' do
- before do
- @project.comment_mr_with_commits('feature', @commits, @user)
- end
-
- it { @merge_request.notes.should be_empty }
- it { @fork_merge_request.notes.should be_empty }
- end
-
- context 'push to fork repo source branch' do
- before do
- @fork_project.comment_mr_with_commits('master', @commits, @user)
- end
-
- it { @merge_request.notes.should be_empty }
- it { @fork_merge_request.notes.should_not be_empty }
- end
-
- context 'push to fork repo target branch' do
- before do
- @fork_project.comment_mr_with_commits('feature', @commits, @user)
- end
-
- it { @merge_request.notes.should be_empty }
- it { @fork_merge_request.notes.should be_empty }
- end
- end
-
describe :find_with_namespace do
context 'with namespace' do
before do
diff --git a/spec/models/slack_message_spec.rb b/spec/models/slack_message_spec.rb
index 1cd58534702..c530fad619b 100644
--- a/spec/models/slack_message_spec.rb
+++ b/spec/models/slack_message_spec.rb
@@ -1,4 +1,4 @@
-require_relative '../../app/models/project_services/slack_message'
+require 'spec_helper'
describe SlackMessage do
subject { SlackMessage.new(args) }
@@ -26,11 +26,11 @@ describe SlackMessage do
it 'returns a message regarding pushes' do
subject.pretext.should ==
- 'user_name pushed to branch <url/commits/master|master> of ' <<
+ 'user_name pushed to branch <url/commits/master|master> of '\
'<url|project_name> (<url/compare/before...after|Compare changes>)'
subject.attachments.should == [
{
- text: "<url1|abcdefghi>: message1 - author1\n" <<
+ text: "<url1|abcdefghi>: message1 - author1\n"\
"<url2|123456789>: message2 - author2",
color: color,
}
@@ -45,7 +45,7 @@ describe SlackMessage do
it 'returns a message regarding a new branch' do
subject.pretext.should ==
- 'user_name pushed new branch <url/commits/master|master> to ' <<
+ 'user_name pushed new branch <url/commits/master|master> to '\
'<url|project_name>'
subject.attachments.should be_empty
end
diff --git a/spec/models/slack_service_spec.rb b/spec/models/slack_service_spec.rb
index 95df38d9400..d4840391967 100644
--- a/spec/models/slack_service_spec.rb
+++ b/spec/models/slack_service_spec.rb
@@ -31,51 +31,27 @@ describe SlackService do
end
describe "Execute" do
- let(:slack) { SlackService.new }
- let(:slack_service) { SlackService.new }
- let(:user) { create(:user) }
+ let(:slack) { SlackService.new }
+ let(:user) { create(:user) }
let(:project) { create(:project) }
let(:sample_data) { GitPushService.new.sample_data(project, user) }
- let(:webhook) { 'https://gitlabhq.slack.com/services/hooks?token=cdIj4r4LfXUOySDUjp0tk3OI' }
- let(:new_webhook) { 'https://hooks.gitlabhq.slack.com/services/cdIj4r4LfXUOySDUjp0tk3OI' }
- let(:api_url) {
- 'https://gitlabhq.slack.com/services/hooks/incoming-webhook?token=cdIj4r4LfXUOySDUjp0tk3OI'
- }
+ let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' }
before do
slack.stub(
project: project,
project_id: project.id,
service_hook: true,
- webhook: webhook
+ webhook: webhook_url
)
- WebMock.stub_request(:post, api_url)
+ WebMock.stub_request(:post, webhook_url)
end
it "should call Slack API" do
slack.execute(sample_data)
- WebMock.should have_requested(:post, api_url).once
- end
-
- context 'with new webhook syntax' do
- before do
- slack_service.stub(
- project: project,
- project_id: project.id,
- service_hook: true,
- webhook: new_webhook
- )
-
- WebMock.stub_request(:post, api_url)
- end
-
- it "should call Slack API" do
- slack_service.execute(sample_data)
-
- WebMock.should have_requested(:post, api_url).once
- end
+ WebMock.should have_requested(:post, webhook_url).once
end
end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 6ad57b06e06..8be7f733a5b 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -62,6 +62,7 @@ describe User do
it { should have_many(:assigned_issues).dependent(:destroy) }
it { should have_many(:merge_requests).dependent(:destroy) }
it { should have_many(:assigned_merge_requests).dependent(:destroy) }
+ it { should have_many(:identities).dependent(:destroy) }
end
describe "Mass assignment" do
@@ -287,6 +288,20 @@ describe User do
end
end
+ describe '.by_login' do
+ let(:username) { 'John' }
+ let!(:user) { create(:user, username: username) }
+
+ it 'should get the correct user' do
+ expect(User.by_login(user.email.upcase)).to eq user
+ expect(User.by_login(user.email)).to eq user
+ expect(User.by_login(username.downcase)).to eq user
+ expect(User.by_login(username)).to eq user
+ expect(User.by_login(nil)).to be_nil
+ expect(User.by_login('')).to be_nil
+ end
+ end
+
describe 'all_ssh_keys' do
it { should have_many(:keys).dependent(:destroy) }
@@ -347,24 +362,29 @@ describe User do
end
describe :ldap_user? do
- let(:user) { build(:user, :ldap) }
-
it "is true if provider name starts with ldap" do
- user.provider = 'ldapmain'
+ user = create(:omniauth_user, provider: 'ldapmain')
expect( user.ldap_user? ).to be_true
end
it "is false for other providers" do
- user.provider = 'other-provider'
+ user = create(:omniauth_user, provider: 'other-provider')
expect( user.ldap_user? ).to be_false
end
it "is false if no extern_uid is provided" do
- user.extern_uid = nil
+ user = create(:omniauth_user, extern_uid: nil)
expect( user.ldap_user? ).to be_false
end
end
+ describe :ldap_identity do
+ it "returns ldap identity" do
+ user = create :omniauth_user
+ user.ldap_identity.provider.should_not be_empty
+ end
+ end
+
describe '#full_website_url' do
let(:user) { create(:user) }
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index 8834a6cfa83..b45572c39fd 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -146,6 +146,7 @@ describe API::API, api: true do
it "should remove branch" do
delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
response.status.should == 200
+ json_response['branch_name'].should == branch_name
end
it 'should return 404 if branch not exists' do
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 38e0a284c36..a3f58f50913 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -8,6 +8,7 @@ describe API::API, api: true do
let!(:project) { create(:project, creator_id: user.id) }
let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
+ let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
before { project.team << [user, :reporter] }
@@ -81,4 +82,68 @@ describe API::API, api: true do
end
end
end
+
+ describe 'GET /projects:id/repository/commits/:sha/comments' do
+ context 'authorized user' do
+ it 'should return merge_request comments' do
+ get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.length.should == 1
+ json_response.first['note'].should == 'a comment on a commit'
+ json_response.first['author']['id'].should == user.id
+ end
+
+ it 'should return a 404 error if merge_request_id not found' do
+ get api("/projects/#{project.id}/repository/commits/1234ab/comments", user)
+ response.status.should == 404
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not return the diff of the selected commit' do
+ get api("/projects/#{project.id}/repository/commits/1234ab/comments")
+ response.status.should == 401
+ end
+ end
+ end
+
+ describe 'POST /projects:id/repository/commits/:sha/comments' do
+ context 'authorized user' do
+ it 'should return comment' do
+ post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment'
+ response.status.should == 201
+ json_response['note'].should == 'My comment'
+ json_response['path'].should be_nil
+ json_response['line'].should be_nil
+ json_response['line_type'].should be_nil
+ end
+
+ it 'should return the inline comment' do
+ post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.diffs.first.new_path, line: 7, line_type: 'new'
+ response.status.should == 201
+ json_response['note'].should == 'My comment'
+ json_response['path'].should == project.repository.commit.diffs.first.new_path
+ json_response['line'].should == 7
+ json_response['line_type'].should == 'new'
+ end
+
+ it 'should return 400 if note is missing' do
+ post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
+ response.status.should == 400
+ end
+
+ it 'should return 404 if note is attached to non existent commit' do
+ post api("/projects/#{project.id}/repository/commits/1234ab/comments", user), note: 'My comment'
+ response.status.should == 404
+ end
+ end
+
+ context 'unauthorized user' do
+ it 'should not return the diff of the selected commit' do
+ post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments")
+ response.status.should == 401
+ end
+ end
+ end
end
diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb
new file mode 100644
index 00000000000..4957186f605
--- /dev/null
+++ b/spec/requests/api/group_members_spec.rb
@@ -0,0 +1,136 @@
+require 'spec_helper'
+
+describe API::API, api: true do
+ include ApiHelpers
+
+ let(:owner) { create(:user) }
+ let(:reporter) { create(:user) }
+ let(:developer) { create(:user) }
+ let(:master) { create(:user) }
+ let(:guest) { create(:user) }
+ let(:stranger) { create(:user) }
+
+ let!(:group_with_members) do
+ group = create(:group)
+ group.add_users([reporter.id], GroupMember::REPORTER)
+ group.add_users([developer.id], GroupMember::DEVELOPER)
+ group.add_users([master.id], GroupMember::MASTER)
+ group.add_users([guest.id], GroupMember::GUEST)
+ group
+ end
+
+ let!(:group_no_members) { create(:group) }
+
+ before do
+ group_with_members.add_owner owner
+ group_no_members.add_owner owner
+ end
+
+ describe "GET /groups/:id/members" do
+ context "when authenticated as user that is part or the group" do
+ it "each user: should return an array of members groups of group3" do
+ [owner, master, developer, reporter, guest].each do |user|
+ get api("/groups/#{group_with_members.id}/members", user)
+ response.status.should == 200
+ json_response.should be_an Array
+ json_response.size.should == 5
+ json_response.find { |e| e['id']==owner.id }['access_level'].should == GroupMember::OWNER
+ json_response.find { |e| e['id']==reporter.id }['access_level'].should == GroupMember::REPORTER
+ json_response.find { |e| e['id']==developer.id }['access_level'].should == GroupMember::DEVELOPER
+ json_response.find { |e| e['id']==master.id }['access_level'].should == GroupMember::MASTER
+ json_response.find { |e| e['id']==guest.id }['access_level'].should == GroupMember::GUEST
+ end
+ end
+
+ it "users not part of the group should get access error" do
+ get api("/groups/#{group_with_members.id}/members", stranger)
+ response.status.should == 403
+ end
+ end
+ end
+
+ describe "POST /groups/:id/members" do
+ context "when not a member of the group" do
+ it "should not add guest as member of group_no_members when adding being done by person outside the group" do
+ post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER
+ response.status.should == 403
+ end
+ end
+
+ context "when a member of the group" do
+ it "should return ok and add new member" do
+ new_user = create(:user)
+
+ expect {
+ post api("/groups/#{group_no_members.id}/members", owner),
+ user_id: new_user.id, access_level: GroupMember::MASTER
+ }.to change { group_no_members.members.count }.by(1)
+
+ response.status.should == 201
+ json_response['name'].should == new_user.name
+ json_response['access_level'].should == GroupMember::MASTER
+ end
+
+ it "should not allow guest to modify group members" do
+ new_user = create(:user)
+
+ expect {
+ post api("/groups/#{group_with_members.id}/members", guest),
+ user_id: new_user.id, access_level: GroupMember::MASTER
+ }.not_to change { group_with_members.members.count }
+
+ response.status.should == 403
+ end
+
+ it "should return error if member already exists" do
+ post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER
+ response.status.should == 409
+ end
+
+ it "should return a 400 error when user id is not given" do
+ post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER
+ response.status.should == 400
+ end
+
+ it "should return a 400 error when access level is not given" do
+ post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id
+ response.status.should == 400
+ end
+
+ it "should return a 422 error when access level is not known" do
+ post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234
+ response.status.should == 422
+ end
+ end
+ end
+
+ describe "DELETE /groups/:id/members/:user_id" do
+ context "when not a member of the group" do
+ it "should not delete guest's membership of group_with_members" do
+ random_user = create(:user)
+ delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user)
+ response.status.should == 403
+ end
+ end
+
+ context "when a member of the group" do
+ it "should delete guest's membership of group" do
+ expect {
+ delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner)
+ }.to change { group_with_members.members.count }.by(-1)
+
+ response.status.should == 200
+ end
+
+ it "should return a 404 error when user id is not known" do
+ delete api("/groups/#{group_with_members.id}/members/1328", owner)
+ response.status.should == 404
+ end
+
+ it "should not allow guest to modify group members" do
+ delete api("/groups/#{group_with_members.id}/members/#{master.id}", guest)
+ response.status.should == 403
+ end
+ end
+ end
+end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 42ccad71aaf..8dfd2cd650e 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -165,114 +165,4 @@ describe API::API, api: true do
end
end
end
-
- describe "members" do
- let(:owner) { create(:user) }
- let(:reporter) { create(:user) }
- let(:developer) { create(:user) }
- let(:master) { create(:user) }
- let(:guest) { create(:user) }
- let!(:group_with_members) do
- group = create(:group)
- group.add_users([reporter.id], GroupMember::REPORTER)
- group.add_users([developer.id], GroupMember::DEVELOPER)
- group.add_users([master.id], GroupMember::MASTER)
- group.add_users([guest.id], GroupMember::GUEST)
- group
- end
- let!(:group_no_members) { create(:group) }
-
- before do
- group_with_members.add_owner owner
- group_no_members.add_owner owner
- end
-
- describe "GET /groups/:id/members" do
- context "when authenticated as user that is part or the group" do
- it "each user: should return an array of members groups of group3" do
- [owner, master, developer, reporter, guest].each do |user|
- get api("/groups/#{group_with_members.id}/members", user)
- response.status.should == 200
- json_response.should be_an Array
- json_response.size.should == 5
- json_response.find { |e| e['id']==owner.id }['access_level'].should == GroupMember::OWNER
- json_response.find { |e| e['id']==reporter.id }['access_level'].should == GroupMember::REPORTER
- json_response.find { |e| e['id']==developer.id }['access_level'].should == GroupMember::DEVELOPER
- json_response.find { |e| e['id']==master.id }['access_level'].should == GroupMember::MASTER
- json_response.find { |e| e['id']==guest.id }['access_level'].should == GroupMember::GUEST
- end
- end
-
- it "users not part of the group should get access error" do
- get api("/groups/#{group_with_members.id}/members", user1)
- response.status.should == 403
- end
- end
- end
-
- describe "POST /groups/:id/members" do
- context "when not a member of the group" do
- it "should not add guest as member of group_no_members when adding being done by person outside the group" do
- post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER
- response.status.should == 403
- end
- end
-
- context "when a member of the group" do
- it "should return ok and add new member" do
- count_before=group_no_members.group_members.count
- new_user = create(:user)
- post api("/groups/#{group_no_members.id}/members", owner), user_id: new_user.id, access_level: GroupMember::MASTER
- response.status.should == 201
- json_response['name'].should == new_user.name
- json_response['access_level'].should == GroupMember::MASTER
- group_no_members.group_members.count.should == count_before + 1
- end
-
- it "should return error if member already exists" do
- post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER
- response.status.should == 409
- end
-
- it "should return a 400 error when user id is not given" do
- post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER
- response.status.should == 400
- end
-
- it "should return a 400 error when access level is not given" do
- post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id
- response.status.should == 400
- end
-
- it "should return a 422 error when access level is not known" do
- post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234
- response.status.should == 422
- end
- end
- end
-
- describe "DELETE /groups/:id/members/:user_id" do
- context "when not a member of the group" do
- it "should not delete guest's membership of group_with_members" do
- random_user = create(:user)
- delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user)
- response.status.should == 403
- end
- end
-
- context "when a member of the group" do
- it "should delete guest's membership of group" do
- count_before=group_with_members.group_members.count
- delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner)
- response.status.should == 200
- group_with_members.group_members.count.should == count_before - 1
- end
-
- it "should return a 404 error when user id is not known" do
- delete api("/groups/#{group_with_members.id}/members/1328", owner)
- response.status.should == 404
- end
- end
- end
- end
end
diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb
index 6df5ef38961..4faa1f9b964 100644
--- a/spec/requests/api/internal_spec.rb
+++ b/spec/requests/api/internal_spec.rb
@@ -5,10 +5,11 @@ describe API::API, api: true do
let(:user) { create(:user) }
let(:key) { create(:key, user: user) }
let(:project) { create(:project) }
+ let(:secret_token) { File.read Rails.root.join('.gitlab_shell_secret') }
describe "GET /internal/check", no_db: true do
it do
- get api("/internal/check")
+ get api("/internal/check"), secret_token: secret_token
response.status.should == 200
json_response['api_version'].should == API::API.version
@@ -17,7 +18,7 @@ describe API::API, api: true do
describe "GET /internal/discover" do
it do
- get(api("/internal/discover"), key_id: key.id)
+ get(api("/internal/discover"), key_id: key.id, secret_token: secret_token)
response.status.should == 200
@@ -25,7 +26,7 @@ describe API::API, api: true do
end
end
- describe "GET /internal/allowed" do
+ describe "POST /internal/allowed" do
context "access granted" do
before do
project.team << [user, :developer]
@@ -36,7 +37,7 @@ describe API::API, api: true do
pull(key, project)
response.status.should == 200
- response.body.should == 'true'
+ JSON.parse(response.body)["status"].should be_true
end
end
@@ -45,7 +46,7 @@ describe API::API, api: true do
push(key, project)
response.status.should == 200
- response.body.should == 'true'
+ JSON.parse(response.body)["status"].should be_true
end
end
end
@@ -60,7 +61,7 @@ describe API::API, api: true do
pull(key, project)
response.status.should == 200
- response.body.should == 'false'
+ JSON.parse(response.body)["status"].should be_false
end
end
@@ -69,7 +70,7 @@ describe API::API, api: true do
push(key, project)
response.status.should == 200
- response.body.should == 'false'
+ JSON.parse(response.body)["status"].should be_false
end
end
end
@@ -86,7 +87,7 @@ describe API::API, api: true do
pull(key, personal_project)
response.status.should == 200
- response.body.should == 'false'
+ JSON.parse(response.body)["status"].should be_false
end
end
@@ -95,7 +96,7 @@ describe API::API, api: true do
push(key, personal_project)
response.status.should == 200
- response.body.should == 'false'
+ JSON.parse(response.body)["status"].should be_false
end
end
end
@@ -113,7 +114,7 @@ describe API::API, api: true do
pull(key, project)
response.status.should == 200
- response.body.should == 'true'
+ JSON.parse(response.body)["status"].should be_true
end
end
@@ -122,7 +123,7 @@ describe API::API, api: true do
push(key, project)
response.status.should == 200
- response.body.should == 'false'
+ JSON.parse(response.body)["status"].should be_false
end
end
end
@@ -139,7 +140,7 @@ describe API::API, api: true do
archive(key, project)
response.status.should == 200
- response.body.should == 'true'
+ JSON.parse(response.body)["status"].should be_true
end
end
@@ -148,10 +149,28 @@ describe API::API, api: true do
archive(key, project)
response.status.should == 200
- response.body.should == 'false'
+ JSON.parse(response.body)["status"].should be_false
end
end
end
+
+ context 'project does not exist' do
+ it do
+ pull(key, OpenStruct.new(path_with_namespace: 'gitlab/notexists'))
+
+ response.status.should == 200
+ JSON.parse(response.body)["status"].should be_false
+ end
+ end
+
+ context 'user does not exist' do
+ it do
+ pull(OpenStruct.new(id: 0), project)
+
+ response.status.should == 200
+ JSON.parse(response.body)["status"].should be_false
+ end
+ end
end
def pull(key, project)
@@ -159,7 +178,8 @@ describe API::API, api: true do
api("/internal/allowed"),
key_id: key.id,
project: project.path_with_namespace,
- action: 'git-upload-pack'
+ action: 'git-upload-pack',
+ secret_token: secret_token
)
end
@@ -169,7 +189,8 @@ describe API::API, api: true do
changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master',
key_id: key.id,
project: project.path_with_namespace,
- action: 'git-receive-pack'
+ action: 'git-receive-pack',
+ secret_token: secret_token
)
end
@@ -179,7 +200,8 @@ describe API::API, api: true do
ref: 'master',
key_id: key.id,
project: project.path_with_namespace,
- action: 'git-upload-archive'
+ action: 'git-upload-archive',
+ secret_token: secret_token
)
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index aa1437c71aa..2c4b68c10b6 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -203,15 +203,12 @@ describe API::API, api: true do
json_response['message']['name'].should == [
'can\'t be blank',
'is too short (minimum is 0 characters)',
- 'can contain only letters, digits, \'_\', \'-\' and \'.\' and '\
- 'space. It must start with letter, digit or \'_\'.'
+ Gitlab::Regex.project_regex_message
]
json_response['message']['path'].should == [
'can\'t be blank',
'is too short (minimum is 0 characters)',
- 'can contain only letters, digits, \'_\', \'-\' and \'.\'. It must '\
- 'start with letter, digit or \'_\', optionally preceeded by \'.\'. '\
- 'It must not end in \'.git\'.'
+ Gitlab::Regex.send(:default_regex_message)
]
end
@@ -339,6 +336,7 @@ describe API::API, api: true do
json_event['action_name'].should == 'joined'
json_event['project_id'].to_i.should == project.id
+ json_event['author_username'].should == user.username
end
it "should return a 404 error if not found" do
@@ -632,6 +630,11 @@ describe API::API, api: true do
describe "DELETE /projects/:id" do
context "when authenticated as user" do
it "should remove project" do
+ expect(GitlabShellWorker).to(
+ receive(:perform_async).with(:remove_repository,
+ /#{project.path_with_namespace}/)
+ ).twice
+
delete api("/projects/#{project.id}", user)
response.status.should == 200
end
diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb
index 6e54839b677..beae71c02d9 100644
--- a/spec/requests/api/repositories_spec.rb
+++ b/spec/requests/api/repositories_spec.rb
@@ -34,21 +34,23 @@ describe API::API, api: true do
end
end
- # TODO: fix this test for CI
- #context 'annotated tag' do
- #it 'should create a new annotated tag' do
- #post api("/projects/#{project.id}/repository/tags", user),
- #tag_name: 'v7.1.0',
- #ref: 'master',
- #message: 'tag message'
-
- #response.status.should == 201
- #json_response['name'].should == 'v7.1.0'
- # The message is not part of the JSON response.
- # Additional changes to the gitlab_git gem may be required.
- # json_response['message'].should == 'tag message'
- #end
- #end
+ context 'annotated tag' do
+ it 'should create a new annotated tag' do
+ # Identity must be set in .gitconfig to create annotated tag.
+ repo_path = project.repository.path_to_repo
+ system(*%W(git --git-dir=#{repo_path} config user.name #{user.name}))
+ system(*%W(git --git-dir=#{repo_path} config user.email #{user.email}))
+
+ post api("/projects/#{project.id}/repository/tags", user),
+ tag_name: 'v7.1.0',
+ ref: 'master',
+ message: 'Release 7.1.0'
+
+ response.status.should == 201
+ json_response['name'].should == 'v7.1.0'
+ json_response['message'].should == 'Release 7.1.0'
+ end
+ end
it 'should deny for user without push access' do
post api("/projects/#{project.id}/repository/tags", user2),
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index f883c9e028a..d8282d0696b 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -27,4 +27,30 @@ describe API::API, api: true do
project.gitlab_ci_service.should be_nil
end
end
+
+ describe 'PUT /projects/:id/services/hipchat' do
+ it 'should update hipchat settings' do
+ put api("/projects/#{project.id}/services/hipchat", user),
+ token: 'secret-token', room: 'test'
+
+ response.status.should == 200
+ project.hipchat_service.should_not be_nil
+ end
+
+ it 'should return if required fields missing' do
+ put api("/projects/#{project.id}/services/gitlab-ci", user),
+ token: 'secret-token', active: true
+
+ response.status.should == 400
+ end
+ end
+
+ describe 'DELETE /projects/:id/services/hipchat' do
+ it 'should delete hipchat settings' do
+ delete api("/projects/#{project.id}/services/hipchat", user)
+
+ response.status.should == 200
+ project.hipchat_service.should be_nil
+ end
+ end
end
diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb
index 013f425d6ce..57b2e6cbd6a 100644
--- a/spec/requests/api/session_spec.rb
+++ b/spec/requests/api/session_spec.rb
@@ -19,6 +19,32 @@ describe API::API, api: true do
end
end
+ context 'when email has case-typo and password is valid' do
+ it 'should return private token' do
+ post api('/session'), email: user.email.upcase, password: '12345678'
+ expect(response.status).to eq 201
+
+ expect(json_response['email']).to eq user.email
+ expect(json_response['private_token']).to eq user.private_token
+ expect(json_response['is_admin']).to eq user.is_admin?
+ expect(json_response['can_create_project']).to eq user.can_create_project?
+ expect(json_response['can_create_group']).to eq user.can_create_group?
+ end
+ end
+
+ context 'when login has case-typo and password is valid' do
+ it 'should return private token' do
+ post api('/session'), login: user.username.upcase, password: '12345678'
+ expect(response.status).to eq 201
+
+ expect(json_response['email']).to eq user.email
+ expect(json_response['private_token']).to eq user.private_token
+ expect(json_response['is_admin']).to eq user.is_admin?
+ expect(json_response['can_create_project']).to eq user.can_create_project?
+ expect(json_response['can_create_group']).to eq user.can_create_group?
+ end
+ end
+
context "when invalid password" do
it "should return authentication error" do
post api("/session"), email: user.email, password: '123'
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index bc1598273be..1ecc79ea7ef 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -33,7 +33,7 @@ describe API::API, api: true do
response.status.should == 200
json_response.should be_an Array
json_response.first.keys.should include 'email'
- json_response.first.keys.should include 'extern_uid'
+ json_response.first.keys.should include 'identities'
json_response.first.keys.should include 'can_create_project'
end
end
@@ -140,9 +140,7 @@ describe API::API, api: true do
json_response['message']['projects_limit'].
should == ['must be greater than or equal to 0']
json_response['message']['username'].
- should == ['can contain only letters, digits, '\
- '\'_\', \'-\' and \'.\'. It must start with letter, digit or '\
- '\'_\', optionally preceeded by \'.\'. It must not end in \'.git\'.']
+ should == [Gitlab::Regex.send(:default_regex_message)]
end
it "shouldn't available for non admin users" do
@@ -284,9 +282,7 @@ describe API::API, api: true do
json_response['message']['projects_limit'].
should == ['must be greater than or equal to 0']
json_response['message']['username'].
- should == ['can contain only letters, digits, '\
- '\'_\', \'-\' and \'.\'. It must start with letter, digit or '\
- '\'_\', optionally preceeded by \'.\'. It must not end in \'.git\'.']
+ should == [Gitlab::Regex.send(:default_regex_message)]
end
context "with existing user" do
@@ -433,6 +429,7 @@ describe API::API, api: true do
json_response['is_admin'].should == user.is_admin?
json_response['can_create_project'].should == user.can_create_project?
json_response['can_create_group'].should == user.can_create_group?
+ json_response['projects_limit'].should == user.projects_limit
end
it "should return 401 error if user is unauthenticated" do
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index f1f5ac96a62..e4652d1f836 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -55,7 +55,6 @@ end
# projects POST /projects(.:format) projects#create
# new_project GET /projects/new(.:format) projects#new
-# fork_project POST /:id/fork(.:format) projects#fork
# files_project GET /:id/files(.:format) projects#files
# edit_project GET /:id/edit(.:format) projects#edit
# project GET /:id(.:format) projects#show
@@ -71,10 +70,6 @@ describe ProjectsController, "routing" do
get("/projects/new").should route_to('projects#new')
end
- it "to #fork" do
- post("/gitlab/gitlabhq/fork").should route_to('projects#fork', id: 'gitlab/gitlabhq')
- end
-
it "to #edit" do
get("/gitlab/gitlabhq/edit").should route_to('projects#edit', id: 'gitlab/gitlabhq')
end
@@ -464,3 +459,13 @@ describe Projects::GraphsController, "routing" do
get("/gitlab/gitlabhq/graphs/master").should route_to('projects/graphs#show', project_id: 'gitlab/gitlabhq', id: 'master')
end
end
+
+describe Projects::ForksController, "routing" do
+ it "to #new" do
+ get("/gitlab/gitlabhq/fork/new").should route_to("projects/forks#new", project_id: 'gitlab/gitlabhq')
+ end
+
+ it "to #create" do
+ post("/gitlab/gitlabhq/fork").should route_to("projects/forks#create", project_id: 'gitlab/gitlabhq')
+ end
+end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 4ef053a767f..19b442573f4 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -8,7 +8,7 @@ describe GitPushService do
let (:service) { GitPushService.new }
before do
- @blankrev = '0000000000000000000000000000000000000000'
+ @blankrev = Gitlab::Git::BLANK_SHA
@oldrev = sample_commit.parent_id
@newrev = sample_commit.id
@ref = 'refs/heads/master'
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
new file mode 100644
index 00000000000..9f294152053
--- /dev/null
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -0,0 +1,98 @@
+require 'spec_helper'
+
+describe MergeRequests::RefreshService do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:service) { MergeRequests::RefreshService }
+
+ describe :execute do
+ before do
+ @user = create(:user)
+ group = create(:group)
+ group.add_owner(@user)
+
+ @project = create(:project, namespace: group)
+ @fork_project = Projects::ForkService.new(@project, @user).execute
+ @merge_request = create(:merge_request, source_project: @project,
+ source_branch: 'master',
+ target_branch: 'feature',
+ target_project: @project)
+
+ @fork_merge_request = create(:merge_request, source_project: @fork_project,
+ source_branch: 'master',
+ target_branch: 'feature',
+ target_project: @project)
+
+ @commits = @merge_request.commits
+
+ @oldrev = @commits.last.id
+ @newrev = @commits.first.id
+ end
+
+ context 'push to origin repo source branch' do
+ before do
+ service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
+ reload_mrs
+ end
+
+ it { @merge_request.notes.should_not be_empty }
+ it { @merge_request.should be_open }
+ it { @fork_merge_request.should be_open }
+ it { @fork_merge_request.notes.should be_empty }
+ end
+
+ context 'push to origin repo target branch' do
+ before do
+ service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ reload_mrs
+ end
+
+ it { @merge_request.notes.should be_empty }
+ it { @merge_request.should be_merged }
+ it { @fork_merge_request.should be_merged }
+ it { @fork_merge_request.notes.should be_empty }
+ end
+
+ context 'push to fork repo source branch' do
+ before do
+ service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/master')
+ reload_mrs
+ end
+
+ it { @merge_request.notes.should be_empty }
+ it { @merge_request.should be_open }
+ it { @fork_merge_request.notes.should_not be_empty }
+ it { @fork_merge_request.should be_open }
+ end
+
+ context 'push to fork repo target branch' do
+ before do
+ service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ reload_mrs
+ end
+
+ it { @merge_request.notes.should be_empty }
+ it { @merge_request.should be_open }
+ it { @fork_merge_request.notes.should be_empty }
+ it { @fork_merge_request.should be_open }
+ end
+
+ context 'push to origin repo target branch after fork project was removed' do
+ before do
+ @fork_project.destroy
+ service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature')
+ reload_mrs
+ end
+
+ it { @merge_request.notes.should be_empty }
+ it { @merge_request.should be_merged }
+ it { @fork_merge_request.should be_open }
+ it { @fork_merge_request.notes.should be_empty }
+ end
+
+ def reload_mrs
+ @merge_request.reload
+ @fork_merge_request.reload
+ end
+ end
+end
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index 0edc3a8e807..5c80345c2b3 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -42,10 +42,54 @@ describe Projects::ForkService do
end
end
- def fork_project(from_project, user, fork_success = true)
- context = Projects::ForkService.new(from_project, user)
- shell = double("gitlab_shell")
- shell.stub(fork_repository: fork_success)
+ describe :fork_to_namespace do
+ before do
+ @group_owner = create(:user)
+ @developer = create(:user)
+ @project = create(:project, creator_id: @group_owner.id,
+ star_count: 777,
+ description: 'Wow, such a cool project!')
+ @group = create(:group)
+ @group.add_user(@group_owner, GroupMember::OWNER)
+ @group.add_user(@developer, GroupMember::DEVELOPER)
+ @opts = { namespace: @group }
+ end
+
+ context 'fork project for group' do
+ it 'group owner successfully forks project into the group' do
+ to_project = fork_project(@project, @group_owner, true, @opts)
+ to_project.owner.should == @group
+ to_project.namespace.should == @group
+ to_project.name.should == @project.name
+ to_project.path.should == @project.path
+ to_project.description.should == @project.description
+ to_project.star_count.should be_zero
+ end
+ end
+
+ context 'fork project for group when user not owner' do
+ it 'group developer should fail to fork project into the group' do
+ to_project = fork_project(@project, @developer, true, @opts)
+ to_project.errors[:namespace].should == ['insufficient access rights']
+ end
+ end
+
+ context 'project already exists in group' do
+ it 'should fail due to validation, not transaction failure' do
+ existing_project = create(:project, name: @project.name,
+ namespace: @group)
+ to_project = fork_project(@project, @group_owner, true, @opts)
+ existing_project.persisted?.should be_true
+ to_project.errors[:base].should == ['Invalid fork destination']
+ to_project.errors[:name].should == ['has already been taken']
+ to_project.errors[:path].should == ['has already been taken']
+ end
+ end
+ end
+
+ def fork_project(from_project, user, fork_success = true, params = {})
+ context = Projects::ForkService.new(from_project, user, params)
+ shell = double('gitlab_shell').stub(fork_repository: fork_success)
context.stub(gitlab_shell: shell)
context.execute
end
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 2508dfc4565..79d0526ff89 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -3,15 +3,12 @@ require 'spec_helper'
describe Projects::TransferService do
let(:user) { create(:user) }
let(:group) { create(:group) }
- let(:group2) { create(:group) }
let(:project) { create(:project, namespace: user.namespace) }
context 'namespace -> namespace' do
before do
group.add_owner(user)
- @service = Projects::TransferService.new(project, user, namespace_id: group.id)
- @service.gitlab_shell.stub(mv_repository: true)
- @result = @service.execute
+ @result = transfer_project(project, user, namespace_id: group.id)
end
it { @result.should be_true }
@@ -20,24 +17,25 @@ describe Projects::TransferService do
context 'namespace -> no namespace' do
before do
- group.add_owner(user)
- @service = Projects::TransferService.new(project, user, namespace_id: nil)
- @service.gitlab_shell.stub(mv_repository: true)
- @result = @service.execute
+ @result = transfer_project(project, user, namespace_id: nil)
end
+ it { @result.should_not be_nil } # { result.should be_false } passes on nil
it { @result.should be_false }
it { project.namespace.should == user.namespace }
end
context 'namespace -> not allowed namespace' do
before do
- @service = Projects::TransferService.new(project, user, namespace_id: group2.id)
- @service.gitlab_shell.stub(mv_repository: true)
- @result = @service.execute
+ @result = transfer_project(project, user, namespace_id: group.id)
end
+ it { @result.should_not be_nil } # { result.should be_false } passes on nil
it { @result.should be_false }
it { project.namespace.should == user.namespace }
end
+
+ def transfer_project(project, user, params)
+ Projects::TransferService.new(project, user, params).execute
+ end
end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 5f55871dc4a..e6db410fb1c 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -3,6 +3,16 @@ require 'rspec/mocks'
module TestEnv
extend self
+ # When developing the seed repository, comment out the branch you will modify.
+ BRANCH_SHA = {
+ 'feature' => '0b4bc9a',
+ 'feature_conflict' => 'bb5206f',
+ 'fix' => '12d65c8',
+ 'improve/awesome' => '5937ac0',
+ 'markdown' => '0ed8c6c',
+ 'master' => '5937ac0'
+ }
+
# Test environment
#
# See gitlab.yml.example test section for paths
@@ -18,13 +28,13 @@ module TestEnv
if File.directory?(tmp_test_path)
Dir.entries(tmp_test_path).each do |entry|
- unless ['.', '..', 'gitlab-shell'].include?(entry)
+ unless ['.', '..', 'gitlab-shell', factory_repo_name].include?(entry)
FileUtils.rm_r(File.join(tmp_test_path, entry))
end
end
end
- FileUtils.mkdir_p(tmp_test_path)
+ FileUtils.mkdir_p(repos_path)
# Setup GitLab shell for test instance
setup_gitlab_shell
@@ -49,13 +59,32 @@ module TestEnv
clone_url = "https://gitlab.com/gitlab-org/#{factory_repo_name}.git"
unless File.directory?(factory_repo_path)
- git_cmd = %W(git clone --bare #{clone_url} #{factory_repo_path})
- system(*git_cmd)
+ system(*%W(git clone #{clone_url} #{factory_repo_path}))
+ end
+
+ Dir.chdir(factory_repo_path) do
+ BRANCH_SHA.each do |branch, sha|
+ # Try to reset without fetching to avoid using the network.
+ reset = %W(git update-ref refs/heads/#{branch} #{sha})
+ unless system(*reset)
+ if system(*%w(git fetch origin))
+ unless system(*reset)
+ raise 'The fetched test seed '\
+ 'does not contain the required revision.'
+ end
+ else
+ raise 'Could not fetch test seed repository.'
+ end
+ end
+ end
end
+
+ # We must copy bare repositories because we will push to them.
+ system(*%W(git clone --bare #{factory_repo_path} #{factory_repo_path_bare}))
end
def copy_repo(project)
- base_repo_path = File.expand_path(factory_repo_path)
+ base_repo_path = File.expand_path(factory_repo_path_bare)
target_repo_path = File.expand_path(repos_path + "/#{project.namespace.path}/#{project.path}.git")
FileUtils.mkdir_p(target_repo_path)
FileUtils.cp_r("#{base_repo_path}/.", target_repo_path)
@@ -69,7 +98,11 @@ module TestEnv
private
def factory_repo_path
- @factory_repo_path ||= repos_path + "/root/#{factory_repo_name}.git"
+ @factory_repo_path ||= Rails.root.join('tmp', 'tests', factory_repo_name)
+ end
+
+ def factory_repo_path_bare
+ factory_repo_path.to_s + '_bare'
end
def factory_repo_name
diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
new file mode 100644
index 00000000000..45aaf0fc90b
--- /dev/null
+++ b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
@@ -0,0 +1,27 @@
+require 'spec_helper'
+require 'rake'
+
+describe 'gitlab:mail_google_schema_whitelisting rake task' do
+ before :all do
+ Rake.application.rake_require "tasks/gitlab/task_helpers"
+ Rake.application.rake_require "tasks/gitlab/mail_google_schema_whitelisting"
+ # empty task as env is already loaded
+ Rake::Task.define_task :environment
+ end
+
+ describe 'call' do
+ before do
+ # avoid writing task output to spec progress
+ $stdout.stub :write
+ end
+
+ let :run_rake_task do
+ Rake::Task["gitlab:mail_google_schema_whitelisting"].reenable
+ Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting"
+ end
+
+ it 'should run the task without errors' do
+ expect { run_rake_task }.to_not raise_error
+ end
+ end
+end
diff --git a/vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js b/vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js
new file mode 100644
index 00000000000..ee374a07fab
--- /dev/null
+++ b/vendor/assets/javascripts/pwstrength-bootstrap-1.2.2.js
@@ -0,0 +1,659 @@
+/*!
+ * jQuery Password Strength plugin for Twitter Bootstrap
+ *
+ * Copyright (c) 2008-2013 Tane Piper
+ * Copyright (c) 2013 Alejandro Blanco
+ * Dual licensed under the MIT and GPL licenses.
+ */
+
+(function (jQuery) {
+// Source: src/rules.js
+
+ var rulesEngine = {};
+
+ try {
+ if (!jQuery && module && module.exports) {
+ var jQuery = require("jquery"),
+ jsdom = require("jsdom").jsdom;
+ jQuery = jQuery(jsdom().parentWindow);
+ }
+ } catch (ignore) {}
+
+ (function ($, rulesEngine) {
+ "use strict";
+ var validation = {};
+
+ rulesEngine.forbiddenSequences = [
+ "0123456789", "abcdefghijklmnopqrstuvwxyz", "qwertyuiop", "asdfghjkl",
+ "zxcvbnm", "!@#$%^&*()_+"
+ ];
+
+ validation.wordNotEmail = function (options, word, score) {
+ if (word.match(/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i)) {
+ return score;
+ }
+ return 0;
+ };
+
+ validation.wordLength = function (options, word, score) {
+ var wordlen = word.length,
+ lenScore = Math.pow(wordlen, options.rules.raisePower);
+ if (wordlen < options.common.minChar) {
+ lenScore = (lenScore + score);
+ }
+ return lenScore;
+ };
+
+ validation.wordSimilarToUsername = function (options, word, score) {
+ var username = $(options.common.usernameField).val();
+ if (username && word.toLowerCase().match(username.toLowerCase())) {
+ return score;
+ }
+ return 0;
+ };
+
+ validation.wordTwoCharacterClasses = function (options, word, score) {
+ if (word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) ||
+ (word.match(/([a-zA-Z])/) && word.match(/([0-9])/)) ||
+ (word.match(/(.[!,@,#,$,%,\^,&,*,?,_,~])/) && word.match(/[a-zA-Z0-9_]/))) {
+ return score;
+ }
+ return 0;
+ };
+
+ validation.wordRepetitions = function (options, word, score) {
+ if (word.match(/(.)\1\1/)) { return score; }
+ return 0;
+ };
+
+ validation.wordSequences = function (options, word, score) {
+ var found = false,
+ j;
+ if (word.length > 2) {
+ $.each(rulesEngine.forbiddenSequences, function (idx, seq) {
+ var sequences = [seq, seq.split('').reverse().join('')];
+ $.each(sequences, function (idx, sequence) {
+ for (j = 0; j < (word.length - 2); j += 1) { // iterate the word trough a sliding window of size 3:
+ if (sequence.indexOf(word.toLowerCase().substring(j, j + 3)) > -1) {
+ found = true;
+ }
+ }
+ });
+ });
+ if (found) { return score; }
+ }
+ return 0;
+ };
+
+ validation.wordLowercase = function (options, word, score) {
+ return word.match(/[a-z]/) && score;
+ };
+
+ validation.wordUppercase = function (options, word, score) {
+ return word.match(/[A-Z]/) && score;
+ };
+
+ validation.wordOneNumber = function (options, word, score) {
+ return word.match(/\d+/) && score;
+ };
+
+ validation.wordThreeNumbers = function (options, word, score) {
+ return word.match(/(.*[0-9].*[0-9].*[0-9])/) && score;
+ };
+
+ validation.wordOneSpecialChar = function (options, word, score) {
+ return word.match(/.[!,@,#,$,%,\^,&,*,?,_,~]/) && score;
+ };
+
+ validation.wordTwoSpecialChar = function (options, word, score) {
+ return word.match(/(.*[!,@,#,$,%,\^,&,*,?,_,~].*[!,@,#,$,%,\^,&,*,?,_,~])/) && score;
+ };
+
+ validation.wordUpperLowerCombo = function (options, word, score) {
+ return word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) && score;
+ };
+
+ validation.wordLetterNumberCombo = function (options, word, score) {
+ return word.match(/([a-zA-Z])/) && word.match(/([0-9])/) && score;
+ };
+
+ validation.wordLetterNumberCharCombo = function (options, word, score) {
+ return word.match(/([a-zA-Z0-9].*[!,@,#,$,%,\^,&,*,?,_,~])|([!,@,#,$,%,\^,&,*,?,_,~].*[a-zA-Z0-9])/) && score;
+ };
+
+ rulesEngine.validation = validation;
+
+ rulesEngine.executeRules = function (options, word) {
+ var totalScore = 0;
+
+ $.each(options.rules.activated, function (rule, active) {
+ if (active) {
+ var score = options.rules.scores[rule],
+ funct = rulesEngine.validation[rule],
+ result,
+ errorMessage;
+
+ if (!$.isFunction(funct)) {
+ funct = options.rules.extra[rule];
+ }
+
+ if ($.isFunction(funct)) {
+ result = funct(options, word, score);
+ if (result) {
+ totalScore += result;
+ }
+ if (result < 0 || (!$.isNumeric(result) && !result)) {
+ errorMessage = options.ui.spanError(options, rule);
+ if (errorMessage.length > 0) {
+ options.instances.errors.push(errorMessage);
+ }
+ }
+ }
+ }
+ });
+
+ return totalScore;
+ };
+ }(jQuery, rulesEngine));
+
+ try {
+ if (module && module.exports) {
+ module.exports = rulesEngine;
+ }
+ } catch (ignore) {}
+
+// Source: src/options.js
+
+
+
+
+ var defaultOptions = {};
+
+ defaultOptions.common = {};
+ defaultOptions.common.minChar = 6;
+ defaultOptions.common.usernameField = "#username";
+ defaultOptions.common.userInputs = [
+ // Selectors for input fields with user input
+ ];
+ defaultOptions.common.onLoad = undefined;
+ defaultOptions.common.onKeyUp = undefined;
+ defaultOptions.common.zxcvbn = false;
+ defaultOptions.common.debug = false;
+
+ defaultOptions.rules = {};
+ defaultOptions.rules.extra = {};
+ defaultOptions.rules.scores = {
+ wordNotEmail: -100,
+ wordLength: -50,
+ wordSimilarToUsername: -100,
+ wordSequences: -50,
+ wordTwoCharacterClasses: 2,
+ wordRepetitions: -25,
+ wordLowercase: 1,
+ wordUppercase: 3,
+ wordOneNumber: 3,
+ wordThreeNumbers: 5,
+ wordOneSpecialChar: 3,
+ wordTwoSpecialChar: 5,
+ wordUpperLowerCombo: 2,
+ wordLetterNumberCombo: 2,
+ wordLetterNumberCharCombo: 2
+ };
+ defaultOptions.rules.activated = {
+ wordNotEmail: true,
+ wordLength: true,
+ wordSimilarToUsername: true,
+ wordSequences: true,
+ wordTwoCharacterClasses: false,
+ wordRepetitions: false,
+ wordLowercase: true,
+ wordUppercase: true,
+ wordOneNumber: true,
+ wordThreeNumbers: true,
+ wordOneSpecialChar: true,
+ wordTwoSpecialChar: true,
+ wordUpperLowerCombo: true,
+ wordLetterNumberCombo: true,
+ wordLetterNumberCharCombo: true
+ };
+ defaultOptions.rules.raisePower = 1.4;
+
+ defaultOptions.ui = {};
+ defaultOptions.ui.bootstrap2 = false;
+ defaultOptions.ui.showProgressBar = true;
+ defaultOptions.ui.showPopover = false;
+ defaultOptions.ui.showStatus = false;
+ defaultOptions.ui.spanError = function (options, key) {
+ "use strict";
+ var text = options.ui.errorMessages[key];
+ if (!text) { return ''; }
+ return '<span style="color: #d52929">' + text + '</span>';
+ };
+ defaultOptions.ui.errorMessages = {
+ wordLength: "Your password is too short",
+ wordNotEmail: "Do not use your email as your password",
+ wordSimilarToUsername: "Your password cannot contain your username",
+ wordTwoCharacterClasses: "Use different character classes",
+ wordRepetitions: "Too many repetitions",
+ wordSequences: "Your password contains sequences"
+ };
+ defaultOptions.ui.verdicts = ["Weak", "Normal", "Medium", "Strong", "Very Strong"];
+ defaultOptions.ui.showVerdicts = true;
+ defaultOptions.ui.showVerdictsInsideProgressBar = false;
+ defaultOptions.ui.showErrors = false;
+ defaultOptions.ui.container = undefined;
+ defaultOptions.ui.viewports = {
+ progress: undefined,
+ verdict: undefined,
+ errors: undefined
+ };
+ defaultOptions.ui.scores = [14, 26, 38, 50];
+
+// Source: src/ui.js
+
+
+
+
+ var ui = {};
+
+ (function ($, ui) {
+ "use strict";
+
+ var barClasses = ["danger", "warning", "success"],
+ statusClasses = ["error", "warning", "success"];
+
+ ui.getContainer = function (options, $el) {
+ var $container;
+
+ $container = $(options.ui.container);
+ if (!($container && $container.length === 1)) {
+ $container = $el.parent();
+ }
+ return $container;
+ };
+
+ ui.findElement = function ($container, viewport, cssSelector) {
+ if (viewport) {
+ return $container.find(viewport).find(cssSelector);
+ }
+ return $container.find(cssSelector);
+ };
+
+ ui.getUIElements = function (options, $el) {
+ var $container, result;
+
+ if (options.instances.viewports) {
+ return options.instances.viewports;
+ }
+
+ $container = ui.getContainer(options, $el);
+
+ result = {};
+ result.$progressbar = ui.findElement($container, options.ui.viewports.progress, "div.progress");
+ if (options.ui.showVerdictsInsideProgressBar) {
+ result.$verdict = result.$progressbar.find("span.password-verdict");
+ }
+
+ if (!options.ui.showPopover) {
+ if (!options.ui.showVerdictsInsideProgressBar) {
+ result.$verdict = ui.findElement($container, options.ui.viewports.verdict, "span.password-verdict");
+ }
+ result.$errors = ui.findElement($container, options.ui.viewports.errors, "ul.error-list");
+ }
+
+ options.instances.viewports = result;
+ return result;
+ };
+
+ ui.initProgressBar = function (options, $el) {
+ var $container = ui.getContainer(options, $el),
+ progressbar = "<div class='progress'><div class='";
+
+ if (!options.ui.bootstrap2) {
+ progressbar += "progress-";
+ }
+ progressbar += "bar'>";
+ if (options.ui.showVerdictsInsideProgressBar) {
+ progressbar += "<span class='password-verdict'></span>";
+ }
+ progressbar += "</div></div>";
+
+ if (options.ui.viewports.progress) {
+ $container.find(options.ui.viewports.progress).append(progressbar);
+ } else {
+ $(progressbar).insertAfter($el);
+ }
+ };
+
+ ui.initHelper = function (options, $el, html, viewport) {
+ var $container = ui.getContainer(options, $el);
+ if (viewport) {
+ $container.find(viewport).append(html);
+ } else {
+ $(html).insertAfter($el);
+ }
+ };
+
+ ui.initVerdict = function (options, $el) {
+ ui.initHelper(options, $el, "<span class='password-verdict'></span>",
+ options.ui.viewports.verdict);
+ };
+
+ ui.initErrorList = function (options, $el) {
+ ui.initHelper(options, $el, "<ul class='error-list'></ul>",
+ options.ui.viewports.errors);
+ };
+
+ ui.initPopover = function (options, $el) {
+ $el.popover("destroy");
+ $el.popover({
+ html: true,
+ placement: "top",
+ trigger: "manual",
+ content: " "
+ });
+ };
+
+ ui.initUI = function (options, $el) {
+ if (options.ui.showPopover) {
+ ui.initPopover(options, $el);
+ } else {
+ if (options.ui.showErrors) { ui.initErrorList(options, $el); }
+ if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) {
+ ui.initVerdict(options, $el);
+ }
+ }
+ if (options.ui.showProgressBar) {
+ ui.initProgressBar(options, $el);
+ }
+ };
+
+ ui.possibleProgressBarClasses = ["danger", "warning", "success"];
+
+ ui.updateProgressBar = function (options, $el, cssClass, percentage) {
+ var $progressbar = ui.getUIElements(options, $el).$progressbar,
+ $bar = $progressbar.find(".progress-bar"),
+ cssPrefix = "progress-";
+
+ if (options.ui.bootstrap2) {
+ $bar = $progressbar.find(".bar");
+ cssPrefix = "";
+ }
+
+ $.each(ui.possibleProgressBarClasses, function (idx, value) {
+ $bar.removeClass(cssPrefix + "bar-" + value);
+ });
+ $bar.addClass(cssPrefix + "bar-" + barClasses[cssClass]);
+ $bar.css("width", percentage + '%');
+ };
+
+ ui.updateVerdict = function (options, $el, text) {
+ var $verdict = ui.getUIElements(options, $el).$verdict;
+ $verdict.text(text);
+ };
+
+ ui.updateErrors = function (options, $el) {
+ var $errors = ui.getUIElements(options, $el).$errors,
+ html = "";
+ $.each(options.instances.errors, function (idx, err) {
+ html += "<li>" + err + "</li>";
+ });
+ $errors.html(html);
+ };
+
+ ui.updatePopover = function (options, $el, verdictText) {
+ var popover = $el.data("bs.popover"),
+ html = "",
+ hide = true;
+
+ if (options.ui.showVerdicts &&
+ !options.ui.showVerdictsInsideProgressBar &&
+ verdictText.length > 0) {
+ html = "<h5><span class='password-verdict'>" + verdictText +
+ "</span></h5>";
+ hide = false;
+ }
+ if (options.ui.showErrors) {
+ html += "<div><ul class='error-list' style='margin-bottom: 0; margin-left: -20px'>";
+ $.each(options.instances.errors, function (idx, err) {
+ html += "<li>" + err + "</li>";
+ hide = false;
+ });
+ html += "</ul></div>";
+ }
+
+ if (hide) {
+ $el.popover("hide");
+ return;
+ }
+
+ if (options.ui.bootstrap2) { popover = $el.data("popover"); }
+
+ if (popover.$arrow && popover.$arrow.parents("body").length > 0) {
+ $el.find("+ .popover .popover-content").html(html);
+ } else {
+ // It's hidden
+ popover.options.content = html;
+ $el.popover("show");
+ }
+ };
+
+ ui.updateFieldStatus = function (options, $el, cssClass) {
+ var targetClass = options.ui.bootstrap2 ? ".control-group" : ".form-group",
+ $container = $el.parents(targetClass).first();
+
+ $.each(statusClasses, function (idx, css) {
+ if (!options.ui.bootstrap2) { css = "has-" + css; }
+ $container.removeClass(css);
+ });
+
+ cssClass = statusClasses[cssClass];
+ if (!options.ui.bootstrap2) { cssClass = "has-" + cssClass; }
+ $container.addClass(cssClass);
+ };
+
+ ui.percentage = function (score, maximun) {
+ var result = Math.floor(100 * score / maximun);
+ result = result < 0 ? 0 : result;
+ result = result > 100 ? 100 : result;
+ return result;
+ };
+
+ ui.getVerdictAndCssClass = function (options, score) {
+ var cssClass, verdictText, level;
+
+ if (score <= 0) {
+ cssClass = 0;
+ level = -1;
+ verdictText = options.ui.verdicts[0];
+ } else if (score < options.ui.scores[0]) {
+ cssClass = 0;
+ level = 0;
+ verdictText = options.ui.verdicts[0];
+ } else if (score < options.ui.scores[1]) {
+ cssClass = 0;
+ level = 1;
+ verdictText = options.ui.verdicts[1];
+ } else if (score < options.ui.scores[2]) {
+ cssClass = 1;
+ level = 2;
+ verdictText = options.ui.verdicts[2];
+ } else if (score < options.ui.scores[3]) {
+ cssClass = 1;
+ level = 3;
+ verdictText = options.ui.verdicts[3];
+ } else {
+ cssClass = 2;
+ level = 4;
+ verdictText = options.ui.verdicts[4];
+ }
+
+ return [verdictText, cssClass, level];
+ };
+
+ ui.updateUI = function (options, $el, score) {
+ var cssClass, barPercentage, verdictText;
+
+ cssClass = ui.getVerdictAndCssClass(options, score);
+ verdictText = cssClass[0];
+ cssClass = cssClass[1];
+
+ if (options.ui.showProgressBar) {
+ barPercentage = ui.percentage(score, options.ui.scores[3]);
+ ui.updateProgressBar(options, $el, cssClass, barPercentage);
+ if (options.ui.showVerdictsInsideProgressBar) {
+ ui.updateVerdict(options, $el, verdictText);
+ }
+ }
+
+ if (options.ui.showStatus) {
+ ui.updateFieldStatus(options, $el, cssClass);
+ }
+
+ if (options.ui.showPopover) {
+ ui.updatePopover(options, $el, verdictText);
+ } else {
+ if (options.ui.showVerdicts && !options.ui.showVerdictsInsideProgressBar) {
+ ui.updateVerdict(options, $el, verdictText);
+ }
+ if (options.ui.showErrors) {
+ ui.updateErrors(options, $el);
+ }
+ }
+ };
+ }(jQuery, ui));
+
+// Source: src/methods.js
+
+
+
+
+ var methods = {};
+
+ (function ($, methods) {
+ "use strict";
+ var onKeyUp, applyToAll;
+
+ onKeyUp = function (event) {
+ var $el = $(event.target),
+ options = $el.data("pwstrength-bootstrap"),
+ word = $el.val(),
+ userInputs,
+ verdictText,
+ verdictLevel,
+ score;
+
+ if (options === undefined) { return; }
+
+ options.instances.errors = [];
+ if (options.common.zxcvbn) {
+ userInputs = [];
+ $.each(options.common.userInputs, function (idx, selector) {
+ userInputs.push($(selector).val());
+ });
+ userInputs.push($(options.common.usernameField).val());
+ score = zxcvbn(word, userInputs).entropy;
+ } else {
+ score = rulesEngine.executeRules(options, word);
+ }
+ ui.updateUI(options, $el, score);
+ verdictText = ui.getVerdictAndCssClass(options, score);
+ verdictLevel = verdictText[2];
+ verdictText = verdictText[0];
+
+ if (options.common.debug) { console.log(score + ' - ' + verdictText); }
+
+ if ($.isFunction(options.common.onKeyUp)) {
+ options.common.onKeyUp(event, {
+ score: score,
+ verdictText: verdictText,
+ verdictLevel: verdictLevel
+ });
+ }
+ };
+
+ methods.init = function (settings) {
+ this.each(function (idx, el) {
+ // Make it deep extend (first param) so it extends too the
+ // rules and other inside objects
+ var clonedDefaults = $.extend(true, {}, defaultOptions),
+ localOptions = $.extend(true, clonedDefaults, settings),
+ $el = $(el);
+
+ localOptions.instances = {};
+ $el.data("pwstrength-bootstrap", localOptions);
+ $el.on("keyup", onKeyUp);
+ $el.on("change", onKeyUp);
+ $el.on("onpaste", onKeyUp);
+
+ ui.initUI(localOptions, $el);
+ if ($.trim($el.val())) { // Not empty, calculate the strength
+ $el.trigger("keyup");
+ }
+
+ if ($.isFunction(localOptions.common.onLoad)) {
+ localOptions.common.onLoad();
+ }
+ });
+
+ return this;
+ };
+
+ methods.destroy = function () {
+ this.each(function (idx, el) {
+ var $el = $(el),
+ options = $el.data("pwstrength-bootstrap"),
+ elements = ui.getUIElements(options, $el);
+ elements.$progressbar.remove();
+ elements.$verdict.remove();
+ elements.$errors.remove();
+ $el.removeData("pwstrength-bootstrap");
+ });
+ };
+
+ methods.forceUpdate = function () {
+ this.each(function (idx, el) {
+ var event = { target: el };
+ onKeyUp(event);
+ });
+ };
+
+ methods.addRule = function (name, method, score, active) {
+ this.each(function (idx, el) {
+ var options = $(el).data("pwstrength-bootstrap");
+
+ options.rules.activated[name] = active;
+ options.rules.scores[name] = score;
+ options.rules.extra[name] = method;
+ });
+ };
+
+ applyToAll = function (rule, prop, value) {
+ this.each(function (idx, el) {
+ $(el).data("pwstrength-bootstrap").rules[prop][rule] = value;
+ });
+ };
+
+ methods.changeScore = function (rule, score) {
+ applyToAll.call(this, rule, "scores", score);
+ };
+
+ methods.ruleActive = function (rule, active) {
+ applyToAll.call(this, rule, "activated", active);
+ };
+
+ $.fn.pwstrength = function (method) {
+ var result;
+
+ if (methods[method]) {
+ result = methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+ } else if (typeof method === "object" || !method) {
+ result = methods.init.apply(this, arguments);
+ } else {
+ $.error("Method " + method + " does not exist on jQuery.pwstrength-bootstrap");
+ }
+
+ return result;
+ };
+ }(jQuery, methods));
+}(jQuery)); \ No newline at end of file