summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml39
-rw-r--r--CHANGELOG29
-rw-r--r--Gemfile75
-rw-r--r--Gemfile.lock183
-rw-r--r--Guardfile27
-rw-r--r--app/assets/images/authbuttons/google_64.pngbin3169 -> 5281 bytes
-rw-r--r--app/assets/images/authbuttons/twitter_64.pngbin3054 -> 4835 bytes
-rw-r--r--app/assets/javascripts/application.js.coffee25
-rw-r--r--app/assets/javascripts/dropzone_input.js.coffee52
-rw-r--r--app/assets/javascripts/extensions/jquery.js.coffee22
-rw-r--r--app/assets/javascripts/gfm_auto_complete.js.coffee20
-rw-r--r--app/assets/javascripts/issue.js.coffee1
-rw-r--r--app/assets/javascripts/merge_request.js.coffee85
-rw-r--r--app/assets/javascripts/notes.js.coffee10
-rw-r--r--app/assets/javascripts/profile.js.coffee4
-rw-r--r--app/assets/javascripts/shortcuts_issuable.coffee2
-rw-r--r--app/assets/javascripts/stat_graph_contributors.js.coffee1
-rw-r--r--app/assets/javascripts/zen_mode.js.coffee12
-rw-r--r--app/assets/stylesheets/base/layout.scss2
-rw-r--r--app/assets/stylesheets/base/variables.scss5
-rw-r--r--app/assets/stylesheets/generic/common.scss2
-rw-r--r--app/assets/stylesheets/generic/forms.scss8
-rw-r--r--app/assets/stylesheets/generic/header.scss247
-rw-r--r--app/assets/stylesheets/generic/lists.scss1
-rw-r--r--app/assets/stylesheets/generic/markdown_area.scss16
-rw-r--r--app/assets/stylesheets/generic/mobile.scss21
-rw-r--r--app/assets/stylesheets/generic/sidebar.scss25
-rw-r--r--app/assets/stylesheets/generic/typography.scss7
-rw-r--r--app/assets/stylesheets/generic/zen.scss15
-rw-r--r--app/assets/stylesheets/pages/dashboard.scss2
-rw-r--r--app/assets/stylesheets/pages/issues.scss6
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss20
-rw-r--r--app/assets/stylesheets/pages/note_form.scss6
-rw-r--r--app/assets/stylesheets/pages/profile.scss5
-rw-r--r--app/assets/stylesheets/pages/projects.scss27
-rw-r--r--app/assets/stylesheets/themes/gitlab-theme.scss13
-rw-r--r--app/controllers/admin/application_settings_controller.rb2
-rw-r--r--app/controllers/admin/deploy_keys_controller.rb7
-rw-r--r--app/controllers/admin/groups_controller.rb2
-rw-r--r--app/controllers/application_controller.rb2
-rw-r--r--app/controllers/groups/group_members_controller.rb6
-rw-r--r--app/controllers/groups_controller.rb2
-rw-r--r--app/controllers/oauth/applications_controller.rb8
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb5
-rw-r--r--app/controllers/projects/blob_controller.rb60
-rw-r--r--app/controllers/projects/deploy_keys_controller.rb4
-rw-r--r--app/controllers/projects/merge_requests_controller.rb17
-rw-r--r--app/controllers/projects/project_members_controller.rb8
-rw-r--r--app/controllers/projects_controller.rb29
-rw-r--r--app/controllers/sessions_controller.rb16
-rw-r--r--app/helpers/application_helper.rb4
-rw-r--r--app/helpers/application_settings_helper.rb4
-rw-r--r--app/helpers/gitlab_markdown_helper.rb46
-rw-r--r--app/helpers/projects_helper.rb12
-rw-r--r--app/helpers/tab_helper.rb2
-rw-r--r--app/models/application_setting.rb6
-rw-r--r--app/models/commit.rb6
-rw-r--r--app/models/concerns/mentionable.rb8
-rw-r--r--app/models/concerns/taskable.rb1
-rw-r--r--app/models/group.rb4
-rw-r--r--app/models/merge_request.rb1
-rw-r--r--app/models/namespace.rb15
-rw-r--r--app/models/project_services/gitlab_ci_service.rb14
-rw-r--r--app/models/repository.rb53
-rw-r--r--app/models/user.rb8
-rw-r--r--app/services/delete_user_service.rb6
-rw-r--r--app/services/destroy_group_service.rb17
-rw-r--r--app/services/files/base_service.rb80
-rw-r--r--app/services/files/create_service.rb44
-rw-r--r--app/services/files/delete_service.rb33
-rw-r--r--app/services/files/update_service.rb36
-rw-r--r--app/services/git_push_service.rb3
-rw-r--r--app/services/issuable_base_service.rb6
-rw-r--r--app/services/merge_requests/update_service.rb14
-rw-r--r--app/services/projects/destroy_service.rb67
-rw-r--r--app/services/projects/participants_service.rb4
-rw-r--r--app/services/system_note_service.rb19
-rw-r--r--app/views/admin/application_settings/_form.html.haml14
-rw-r--r--app/views/admin/broadcast_messages/index.html.haml4
-rw-r--r--app/views/admin/deploy_keys/index.html.haml3
-rw-r--r--app/views/admin/deploy_keys/show.html.haml35
-rw-r--r--app/views/admin/groups/index.html.haml2
-rw-r--r--app/views/dashboard/groups/index.html.haml13
-rw-r--r--app/views/devise/sessions/_new_ldap.html.haml5
-rw-r--r--app/views/events/event/_push.html.haml4
-rw-r--r--app/views/explore/groups/index.html.haml2
-rw-r--r--app/views/explore/projects/_filter.html.haml2
-rw-r--r--app/views/groups/group_members/_group_member.html.haml3
-rw-r--r--app/views/groups/group_members/index.html.haml2
-rw-r--r--app/views/groups/show.html.haml2
-rw-r--r--app/views/layouts/_head.html.haml2
-rw-r--r--app/views/layouts/_head_panel.html.haml42
-rw-r--r--app/views/layouts/_page.html.haml10
-rw-r--r--app/views/layouts/_public_head_panel.html.haml22
-rw-r--r--app/views/layouts/application.html.haml9
-rw-r--r--app/views/layouts/devise.html.haml2
-rw-r--r--app/views/layouts/errors.html.haml2
-rw-r--r--app/views/layouts/header/_default.html.haml46
-rw-r--r--app/views/layouts/header/_empty.html.haml (renamed from app/views/layouts/_empty_head_panel.html.haml)4
-rw-r--r--app/views/layouts/header/_public.html.haml14
-rw-r--r--app/views/layouts/nav/_project.html.haml7
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml15
-rw-r--r--app/views/layouts/notify.html.haml2
-rw-r--r--app/views/layouts/project.html.haml8
-rw-r--r--app/views/notify/repository_push_email.html.haml2
-rw-r--r--app/views/profiles/accounts/show.html.haml23
-rw-r--r--app/views/profiles/applications.html.haml60
-rw-r--r--app/views/profiles/keys/_key.html.haml3
-rw-r--r--app/views/profiles/keys/_key_details.html.haml2
-rw-r--r--app/views/profiles/show.html.haml7
-rw-r--r--app/views/projects/_aside.html.haml14
-rw-r--r--app/views/projects/_home_panel.html.haml2
-rw-r--r--app/views/projects/_issuable_form.html.haml28
-rw-r--r--app/views/projects/_md_preview.html.haml37
-rw-r--r--app/views/projects/blob/_editor.html.haml4
-rw-r--r--app/views/projects/blob/new.html.haml11
-rw-r--r--app/views/projects/deploy_keys/_deploy_key.html.haml16
-rw-r--r--app/views/projects/deploy_keys/show.html.haml14
-rw-r--r--app/views/projects/diffs/_file.html.haml5
-rw-r--r--app/views/projects/edit.html.haml42
-rw-r--r--app/views/projects/issues/_form.html.haml2
-rw-r--r--app/views/projects/issues/_issue.html.haml2
-rw-r--r--app/views/projects/labels/index.html.haml5
-rw-r--r--app/views/projects/merge_requests/_form.html.haml2
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml2
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml8
-rw-r--r--app/views/projects/merge_requests/_show.html.haml23
-rw-r--r--app/views/projects/merge_requests/index.html.haml7
-rw-r--r--app/views/projects/merge_requests/show/_mr_accept.html.haml6
-rw-r--r--app/views/projects/merge_requests/show/_state_widget.html.haml4
-rw-r--r--app/views/projects/milestones/_form.html.haml2
-rw-r--r--app/views/projects/notes/_form.html.haml7
-rw-r--r--app/views/projects/project_members/_project_member.html.haml5
-rw-r--r--app/views/projects/project_members/index.html.haml2
-rw-r--r--app/views/projects/update.js.haml2
-rw-r--r--app/views/projects/wikis/_form.html.haml3
-rw-r--r--app/views/search/_form.html.haml2
-rw-r--r--app/views/shared/_file_highlight.html.haml3
-rw-r--r--app/views/shared/_issuable_search_form.html.haml2
-rw-r--r--app/views/shared/_project.html.haml3
-rw-r--r--app/views/users/show.html.haml2
-rwxr-xr-xbin/guard16
-rw-r--r--config/gitlab.yml.example19
-rw-r--r--config/initializers/1_settings.rb6
-rw-r--r--config/initializers/6_rack_profiler.rb3
-rw-r--r--config/initializers/7_omniauth.rb2
-rw-r--r--config/routes.rb6
-rw-r--r--db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb5
-rw-r--r--db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb5
-rw-r--r--db/schema.rb4
-rw-r--r--doc/README.md69
-rw-r--r--doc/api/README.md1
-rw-r--r--doc/api/merge_requests.md4
-rw-r--r--doc/api/namespaces.md44
-rw-r--r--doc/install/installation.md5
-rw-r--r--doc/integration/bitbucket.md54
-rw-r--r--doc/integration/ldap.md7
-rw-r--r--doc/integration/omniauth.md1
-rw-r--r--doc/integration/saml.md77
-rw-r--r--doc/operations/README.md1
-rw-r--r--doc/operations/unicorn.md86
-rw-r--r--doc/raketasks/maintenance.md3
-rw-r--r--doc/release/monthly.md7
-rw-r--r--doc/workflow/2fa.pngbin0 -> 23415 bytes
-rw-r--r--doc/workflow/2fa_auth.pngbin0 -> 15569 bytes
-rw-r--r--doc/workflow/README.md33
-rw-r--r--doc/workflow/import_projects_from_github.md13
-rw-r--r--doc/workflow/importing/README.md6
-rw-r--r--doc/workflow/importing/bitbucket_importer/bitbucket_import_grant_access.pngbin0 -> 30083 bytes
-rw-r--r--doc/workflow/importing/bitbucket_importer/bitbucket_import_new_project.pngbin0 -> 16502 bytes
-rw-r--r--doc/workflow/importing/bitbucket_importer/bitbucket_import_select_bitbucket.pngbin0 -> 46606 bytes
-rw-r--r--doc/workflow/importing/bitbucket_importer/bitbucket_import_select_project.pngbin0 -> 16121 bytes
-rw-r--r--doc/workflow/importing/github_importer/importer.png (renamed from doc/workflow/github_importer/importer.png)bin39335 -> 39335 bytes
-rw-r--r--doc/workflow/importing/github_importer/new_project_page.png (renamed from doc/workflow/github_importer/new_project_page.png)bin46276 -> 46276 bytes
-rw-r--r--doc/workflow/importing/gitlab_importer/importer.png (renamed from doc/workflow/gitlab_importer/importer.png)bin40778 -> 40778 bytes
-rw-r--r--doc/workflow/importing/gitlab_importer/new_project_page.png (renamed from doc/workflow/gitlab_importer/new_project_page.png)bin72663 -> 72663 bytes
-rw-r--r--doc/workflow/importing/import_projects_from_bitbucket.md26
-rw-r--r--doc/workflow/importing/import_projects_from_github.md18
-rw-r--r--doc/workflow/importing/import_projects_from_gitlab_com.md (renamed from doc/workflow/import_projects_from_gitlab_com.md)0
-rw-r--r--doc/workflow/importing/migrating_from_svn.md (renamed from doc/workflow/migrating_from_svn.md)0
-rw-r--r--doc/workflow/two_factor_authentication.md67
-rw-r--r--doc/workflow/wip_merge_requests.md13
-rw-r--r--doc/workflow/wip_merge_requests/blocked_accept_button.pngbin0 -> 65231 bytes
-rw-r--r--doc/workflow/wip_merge_requests/mark_as_wip.pngbin0 -> 41549 bytes
-rw-r--r--doc/workflow/wip_merge_requests/unmark_as_wip.pngbin0 -> 32151 bytes
-rw-r--r--doc_styleguide.md1
-rw-r--r--docker/README.md10
-rw-r--r--features/admin/deploy_keys.feature5
-rw-r--r--features/dashboard/group.feature3
-rw-r--r--features/project/active_tab.feature12
-rw-r--r--features/project/merge_requests.feature8
-rw-r--r--features/steps/admin/deploy_keys.rb11
-rw-r--r--features/steps/dashboard/dashboard.rb4
-rw-r--r--features/steps/dashboard/group.rb4
-rw-r--r--features/steps/profile/profile.rb26
-rw-r--r--features/steps/project/commits/commits.rb3
-rw-r--r--features/steps/project/merge_requests.rb14
-rw-r--r--features/steps/shared/paths.rb4
-rw-r--r--features/steps/shared/project_tab.rb4
-rw-r--r--features/support/env.rb1
-rw-r--r--lib/api/commits.rb2
-rw-r--r--lib/api/files.rb50
-rw-r--r--lib/api/groups.rb2
-rw-r--r--lib/api/merge_requests.rb8
-rw-r--r--lib/api/namespaces.rb11
-rw-r--r--lib/backup/manager.rb3
-rw-r--r--lib/gitlab/backend/shell.rb14
-rw-r--r--lib/gitlab/gitorious_import.rb5
-rw-r--r--lib/gitlab/gitorious_import/client.rb2
-rw-r--r--lib/gitlab/gitorious_import/repository.rb2
-rw-r--r--lib/gitlab/markdown.rb3
-rw-r--r--lib/gitlab/markdown/reference_filter.rb14
-rw-r--r--lib/gitlab/markdown/sanitization_filter.rb59
-rw-r--r--lib/gitlab/o_auth/user.rb63
-rw-r--r--lib/gitlab/reference_extractor.rb54
-rw-r--r--lib/gitlab/satellite/files/delete_file_action.rb50
-rw-r--r--lib/gitlab/satellite/files/edit_file_action.rb68
-rw-r--r--lib/gitlab/satellite/files/file_action.rb25
-rw-r--r--lib/gitlab/satellite/files/new_file_action.rb67
-rw-r--r--lib/gitlab/upgrader.rb11
-rw-r--r--lib/redcarpet/render/gitlab_html.rb2
-rwxr-xr-xlib/support/init.d/gitlab3
-rwxr-xr-xlib/support/init.d/gitlab.default.example5
-rw-r--r--lib/tasks/gitlab/check.rake86
-rw-r--r--lib/tasks/gitlab/shell.rake3
-rw-r--r--lib/tasks/gitlab/task_helpers.rake4
-rw-r--r--lib/tasks/jasmine.rake2
-rw-r--r--spec/features/groups_spec.rb36
-rw-r--r--spec/features/markdown_spec.rb2
-rw-r--r--spec/features/projects_spec.rb55
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb6
-rw-r--r--spec/javascripts/extensions/array_spec.js.coffee12
-rw-r--r--spec/javascripts/extensions/jquery_spec.js.coffee34
-rw-r--r--spec/javascripts/fixtures/issuable.html.haml2
-rw-r--r--spec/javascripts/fixtures/issue_note.html.haml12
-rw-r--r--spec/javascripts/fixtures/issues_show.html.haml13
-rw-r--r--spec/javascripts/fixtures/merge_requests_show.html.haml13
-rw-r--r--spec/javascripts/fixtures/zen_mode.html.haml9
-rw-r--r--spec/javascripts/issue_spec.js.coffee30
-rw-r--r--spec/javascripts/merge_request_spec.js.coffee31
-rw-r--r--spec/javascripts/notes_spec.js.coffee19
-rw-r--r--spec/javascripts/shortcuts_issuable_spec.js.coffee7
-rw-r--r--spec/javascripts/spec_helper.coffee46
-rw-r--r--spec/javascripts/support/jasmine.yml15
-rw-r--r--spec/javascripts/support/jasmine_helper.rb15
-rw-r--r--spec/javascripts/zen_mode_spec.js.coffee52
-rw-r--r--spec/lib/gitlab/markdown/autolink_filter_spec.rb6
-rw-r--r--spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb2
-rw-r--r--spec/lib/gitlab/markdown/commit_reference_filter_spec.rb2
-rw-r--r--spec/lib/gitlab/markdown/emoji_filter_spec.rb4
-rw-r--r--spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb2
-rw-r--r--spec/lib/gitlab/markdown/external_link_filter_spec.rb4
-rw-r--r--spec/lib/gitlab/markdown/issue_reference_filter_spec.rb2
-rw-r--r--spec/lib/gitlab/markdown/label_reference_filter_spec.rb2
-rw-r--r--spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb2
-rw-r--r--spec/lib/gitlab/markdown/sanitization_filter_spec.rb33
-rw-r--r--spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb2
-rw-r--r--spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb4
-rw-r--r--spec/lib/gitlab/markdown/task_list_filter_spec.rb4
-rw-r--r--spec/lib/gitlab/markdown/user_reference_filter_spec.rb2
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb166
-rw-r--r--spec/lib/gitlab/reference_extractor_spec.rb24
-rw-r--r--spec/lib/gitlab/upgrader_spec.rb15
-rw-r--r--spec/lib/repository_cache_spec.rb1
-rw-r--r--spec/models/concerns/issuable_spec.rb2
-rw-r--r--spec/models/concerns/mentionable_spec.rb21
-rw-r--r--spec/models/deploy_keys_project_spec.rb8
-rw-r--r--spec/models/key_spec.rb4
-rw-r--r--spec/models/project_services/gitlab_ci_service_spec.rb15
-rw-r--r--spec/models/project_spec.rb8
-rw-r--r--spec/models/snippet_spec.rb4
-rw-r--r--spec/models/user_spec.rb4
-rw-r--r--spec/requests/api/commits_spec.rb3
-rw-r--r--spec/requests/api/files_spec.rb51
-rw-r--r--spec/requests/api/merge_requests_spec.rb4
-rw-r--r--spec/requests/api/namespaces_spec.rb29
-rw-r--r--spec/requests/api/projects_spec.rb9
-rw-r--r--spec/routing/project_routing_spec.rb34
-rw-r--r--spec/services/destroy_group_service_spec.rb44
-rw-r--r--spec/services/merge_requests/update_service_spec.rb11
-rw-r--r--spec/services/projects/destroy_service_spec.rb34
-rw-r--r--spec/services/system_note_service_spec.rb14
-rw-r--r--spec/spec_helper.rb13
-rw-r--r--spec/support/api_helpers.rb2
-rw-r--r--spec/support/coverage.rb8
-rw-r--r--spec/support/filter_spec_helper.rb (renamed from spec/support/reference_filter_spec_helper.rb)75
-rw-r--r--spec/support/matchers.rb2
-rw-r--r--spec/support/mentionable_shared_examples.rb21
-rw-r--r--spec/support/webmock.rb4
-rw-r--r--spec/teaspoon_env.rb178
-rwxr-xr-xvendor/assets/javascripts/jasmine-fixture.js433
291 files changed, 3164 insertions, 2222 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
new file mode 100644
index 00000000000..1411a9194b5
--- /dev/null
+++ b/.gitlab-ci.yml
@@ -0,0 +1,39 @@
+before_script:
+ - export PATH=$HOME/bin:/usr/local/bin:/usr/bin:/bin
+ - ruby -v
+ - which ruby
+ - gem install bundler
+ - which bundle
+ - echo $PATH
+ - cp config/database.yml.mysql config/database.yml
+ - cp config/gitlab.yml.example config/gitlab.yml
+ - ! 'sed "s/username\:.*$/username\: runner/" -i config/database.yml'
+ - ! 'sed "s/password\:.*$/password\: ''password''/" -i config/database.yml'
+ - sed "s/gitlabhq_test/gitlabhq_test_$((RANDOM/5000))/" -i config/database.yml
+ - touch log/application.log
+ - touch log/test.log
+ - bundle install --without postgres production --jobs $(nproc)
+ - bundle exec rake db:create RAILS_ENV=test
+jobs:
+- script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec
+ name: Rspec
+ runner: ruby,mysql
+- script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach
+ name: Spinach
+ runner: ruby,mysql
+- script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake jasmine:ci
+ name: Jasmine
+ runner: ruby,mysql
+- script:
+ - bundle exec rubocop
+ name: Rubocop
+ runner: ruby,mysql
+- script:
+ - bundle exec rake brakeman
+ name: Brakeman
+ runner: ruby,mysql
+deploy_jobs: []
+skip_refs: ''
diff --git a/CHANGELOG b/CHANGELOG
index 8fbf85df338..9d558b15ab9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,6 +1,17 @@
Please view this file on the master branch, on stable branches it's out of date.
v 7.12.0 (unreleased)
+ - Update oauth button logos for Twitter and Google to recommended assets
+ - Update browser gem to version 0.8.0 for IE11 support (Stan Hu)
+ - Fix timeout when rendering file with thousands of lines.
+ - Add "Remember me" checkbox to LDAP signin form.
+ - Don't notify users mentioned in code blocks or blockquotes.
+ - Omit link to generate labels if user does not have access to create them (Stan Hu)
+ - Show warning when a comment will add 10 or more people to the discussion.
+ - Disable changing of the source branch in merge request update API (Stan Hu)
+ - Shorten merge request WIP text.
+ - Add option to disallow users from registering any application to use GitLab as an OAuth provider
+ - Support editing target branch of merge request (Stan Hu)
- Refactor permission checks with issues and merge requests project settings (Stan Hu)
- Fix Markdown preview not working in Edit Milestone page (Stan Hu)
- Fix Zen Mode not closing with ESC key (Stan Hu)
@@ -8,11 +19,13 @@ v 7.12.0 (unreleased)
- Add file attachment support in Milestone description (Stan Hu)
- Fix milestone "Browse Issues" button.
- Set milestone on new issue when creating issue from index with milestone filter active.
+ - Make namespace API available to all users (Stan Hu)
- Add web hook support for note events (Stan Hu)
- Disable "New Issue" and "New Merge Request" buttons when features are disabled in project settings (Stan Hu)
- Remove Rack Attack monkey patches and bump to version 4.3.0 (Stan Hu)
- Fix clone URL losing selection after a single click in Safari and Chrome (Stan Hu)
- Fix git blame syntax highlighting when different commits break up lines (Stan Hu)
+ - Add "Resend confirmation e-mail" link in profile settings (Stan Hu)
- Allow to configure location of the `.gitlab_shell_secret` file. (Jakub Jirutka)
- Disabled expansion of top/bottom blobs for new file diffs
- Update Asciidoctor gem to version 1.5.2. (Jakub Jirutka)
@@ -29,6 +42,17 @@ v 7.12.0 (unreleased)
- Clarify navigation labels for Project Settings and Group Settings.
- Move user avatar and logout button to sidebar
- You can not remove user if he/she is an only owner of group
+ - User should be able to leave group. If not - show him proper message
+ - User has ability to leave project
+ - Add SAML support as an omniauth provider
+ - Allow to configure a URL to show after sign out
+ - Add an option to automatically sign-in with an Omniauth provider
+ - Better performance for web editor (switched from satellites to rugged)
+ - GitLab CI service sends .gitlab-ci.yaml in each push call
+ - When remove project - move repository and schedule it removal
+ - Improve group removing logic
+ - Trigger create-hooks on backup restore task
+ - Add option to automatically link omniauth and LDAP identities
v 7.11.4
- Fix missing bullets when creating lists
@@ -36,6 +60,7 @@ v 7.11.4
v 7.11.3
- no changes
+ - Fix upgrader script (Martins Polakovs)
v 7.11.2
- no changes
@@ -46,6 +71,9 @@ v 7.11.1
v 7.11.0
- Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger)
- Get editing comments to work in Chrome 43 again.
+ - Allow special character in users bio. I.e.: I <3 GitLab
+
+v 7.11.0
- Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu)
- Don't show duplicate deploy keys
- Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger)
@@ -117,6 +145,7 @@ v 7.10.4
- Fix DB error when trying to tag a repository (Stan Hu)
- Fix Error 500 when searching Wiki pages (Stan Hu)
- Unescape branch names in compare commit (Stan Hu)
+ - Order commit comments chronologically in API.
v 7.10.2
- Fix CI links on MR page
diff --git a/Gemfile b/Gemfile
index 26981f3e0a3..645e0b5cf7c 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,18 +1,7 @@
source "https://rubygems.org"
-def darwin_only(require_as)
- RUBY_PLATFORM.include?('darwin') && require_as
-end
-
-def linux_only(require_as)
- RUBY_PLATFORM.include?('linux') && require_as
-end
-
gem "rails", "~> 4.1.0"
-# Make links from text
-gem 'rails_autolink', '~> 1.1'
-
# Default values for AR models
gem "default_value_for", "~> 3.0.0"
@@ -31,6 +20,7 @@ gem 'omniauth-shibboleth'
gem 'omniauth-kerberos', group: :kerberos
gem 'omniauth-gitlab'
gem 'omniauth-bitbucket'
+gem 'omniauth-saml'
gem 'doorkeeper', '2.1.3'
gem "rack-oauth2", "~> 1.0.5"
@@ -40,22 +30,30 @@ gem 'rqrcode-rails3'
gem 'attr_encrypted', '1.3.4'
# Browser detection
-gem "browser"
+gem "browser", '~> 0.8.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 7.1.13'
+gem "gitlab_git", '~> 7.2.3'
# Ruby/Rack Git Smart-HTTP Server Handler
+# GitLab fork with a lot of changes (improved thread-safety, better memory usage etc)
+# For full list of changes see https://github.com/SaitoWu/grack/compare/master...gitlabhq:master
gem 'gitlab-grack', '~> 2.0.2', require: 'grack'
# LDAP Auth
+# GitLab fork with several improvements to original library. For full list of changes
+# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
# Git Wiki
gem 'gollum-lib', '~> 4.0.2'
# Language detection
+# GitLab fork of linguist does not require pygments/python dependency.
+# New version of original gem also dropped pygments support but it has strict
+# dependency to unstable rugged version. We have internal issue for replacing
+# fork with original gem when we meet on same rugged version - https://dev.gitlab.org/gitlab/gitlabhq/issues/2052.
gem "gitlab-linguist", "~> 3.0.1", require: "linguist"
# API
@@ -83,7 +81,7 @@ gem "carrierwave"
gem 'dropzonejs-rails'
# for aws storage
-gem "fog", "~> 1.14"
+gem "fog", "~> 1.25.0"
gem "unf"
# Authorization
@@ -186,23 +184,23 @@ gem 'charlock_holmes'
gem "sass-rails", '~> 4.0.2'
gem "coffee-rails"
gem "uglifier"
-gem 'turbolinks'
+gem 'turbolinks', '~> 2.5.0'
gem 'jquery-turbolinks'
-gem 'select2-rails'
+gem 'addressable'
+gem 'bootstrap-sass', '~> 3.0'
+gem 'font-awesome-rails', '~> 4.2'
+gem 'gitlab_emoji', '~> 0.1'
+gem 'gon', '~> 5.0.0'
gem 'jquery-atwho-rails', '~> 1.0.0'
-gem "jquery-rails"
-gem "jquery-ui-rails"
-gem "jquery-scrollto-rails"
-gem "raphael-rails", "~> 2.1.2"
-gem 'bootstrap-sass', '~> 3.0'
-gem "font-awesome-rails", '~> 4.2'
-gem "gitlab_emoji", "~> 0.1"
-gem "gon", '~> 5.0.0'
+gem 'jquery-rails', '3.1.2'
+gem 'jquery-scrollto-rails'
+gem 'jquery-ui-rails'
gem 'nprogress-rails'
+gem 'raphael-rails', '~> 2.1.2'
gem 'request_store'
-gem "virtus"
-gem 'addressable'
+gem 'select2-rails'
+gem 'virtus'
group :development do
gem 'brakeman', require: false
@@ -241,32 +239,25 @@ group :development, :test do
# Generate Fake data
gem 'ffaker', '~> 2.0.0'
- # Guard
- gem 'guard-rspec'
- gem 'guard-spinach'
-
- # Notification
- gem 'rb-fsevent', require: darwin_only('rb-fsevent')
- gem 'growl', require: darwin_only('growl')
- gem 'rb-inotify', require: linux_only('rb-inotify')
-
# PhantomJS driver for Capybara
gem 'poltergeist', '~> 1.5.1'
- gem 'jasmine-rails'
+ gem 'teaspoon', '~> 1.0.0'
+ gem 'teaspoon-jasmine'
- gem "spring", '~> 1.3.1'
- gem "spring-commands-rspec", '1.0.4'
- gem "spring-commands-spinach", '1.0.0'
+ gem 'spring', '~> 1.3.1'
+ gem 'spring-commands-rspec', '~> 1.0.0'
+ gem 'spring-commands-spinach', '~> 1.0.0'
+ gem 'spring-commands-teaspoon', '~> 0.0.2'
gem "byebug"
end
group :test do
- gem "simplecov", require: false
- gem "shoulda-matchers", "~> 2.7.0"
+ gem 'simplecov', require: false
+ gem 'shoulda-matchers', '~> 2.8.0', require: false
gem 'email_spec'
- gem "webmock"
+ gem 'webmock', '~> 1.21.0'
gem 'test_after_commit'
end
diff --git a/Gemfile.lock b/Gemfile.lock
index 7dbc3b4ffa9..1de29ad8f8a 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -1,6 +1,7 @@
GEM
remote: https://rubygems.org/
specs:
+ CFPropertyList (2.3.1)
RedCloth (4.2.9)
ace-rails-ap (2.0.1)
actionmailer (4.1.9)
@@ -35,7 +36,7 @@ GEM
tzinfo (~> 1.1)
acts-as-taggable-on (3.5.0)
activerecord (>= 3.2, < 5)
- addressable (2.3.5)
+ addressable (2.3.8)
annotate (2.6.0)
activerecord (>= 2.3.0)
rake (>= 0.8.7)
@@ -75,7 +76,7 @@ GEM
ruby_parser (~> 3.5.0)
sass (~> 3.0)
terminal-table (~> 1.4)
- browser (0.7.2)
+ browser (0.8.0)
builder (3.2.2)
byebug (3.2.0)
columnize (~> 0.8)
@@ -101,13 +102,13 @@ GEM
coderay (1.1.0)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
- coffee-rails (4.0.1)
+ coffee-rails (4.1.0)
coffee-script (>= 2.2.0)
railties (>= 4.0.0, < 5.0)
- coffee-script (2.2.0)
+ coffee-script (2.4.1)
coffee-script-source
execjs
- coffee-script-source (1.6.3)
+ coffee-script-source (1.9.1.1)
colored (1.2)
colorize (0.5.8)
columnize (0.9.0)
@@ -118,8 +119,8 @@ GEM
simplecov (>= 0.7)
term-ansicolor
thor
- crack (0.4.1)
- safe_yaml (~> 0.9.0)
+ crack (0.4.2)
+ safe_yaml (~> 1.0.0)
creole (0.3.8)
d3_rails (3.5.5)
railties (>= 3.1.0)
@@ -163,7 +164,7 @@ GEM
erubis (2.7.0)
escape_utils (0.2.4)
eventmachine (1.0.4)
- excon (0.32.1)
+ excon (0.45.3)
execjs (2.5.2)
expression_parser (0.9.0)
factory_girl (4.3.0)
@@ -178,29 +179,69 @@ GEM
fastercsv (1.5.5)
ffaker (2.0.0)
ffi (1.9.8)
- fog (1.21.0)
- fog-brightbox
- fog-core (~> 1.21, >= 1.21.1)
+ fission (0.5.0)
+ CFPropertyList (~> 2.2)
+ fog (1.25.0)
+ fog-brightbox (~> 0.4)
+ fog-core (~> 1.25)
fog-json
+ fog-profitbricks
+ fog-radosgw (>= 0.0.2)
+ fog-sakuracloud (>= 0.0.4)
+ fog-softlayer
+ fog-terremark
+ fog-vmfusion
+ fog-voxel
+ fog-xml (~> 0.1.1)
+ ipaddress (~> 0.5)
nokogiri (~> 1.5, >= 1.5.11)
- fog-brightbox (0.0.1)
- fog-core
+ opennebula
+ fog-brightbox (0.7.1)
+ fog-core (~> 1.22)
fog-json
- fog-core (1.21.1)
+ inflecto (~> 0.0.2)
+ fog-core (1.30.0)
builder
- excon (~> 0.32)
- formatador (~> 0.2.0)
+ excon (~> 0.45)
+ formatador (~> 0.2)
mime-types
net-scp (~> 1.1)
net-ssh (>= 2.1.3)
- fog-json (1.0.0)
- multi_json (~> 1.0)
+ fog-json (1.0.2)
+ fog-core (~> 1.0)
+ multi_json (~> 1.10)
+ fog-profitbricks (0.0.3)
+ fog-core
+ fog-xml
+ nokogiri
+ fog-radosgw (0.0.4)
+ fog-core (>= 1.21.0)
+ fog-json
+ fog-xml (>= 0.0.1)
+ fog-sakuracloud (1.0.1)
+ fog-core
+ fog-json
+ fog-softlayer (0.4.6)
+ fog-core
+ fog-json
+ fog-terremark (0.1.0)
+ fog-core
+ fog-xml
+ fog-vmfusion (0.1.0)
+ fission
+ fog-core
+ fog-voxel (0.1.0)
+ fog-core
+ fog-xml
+ fog-xml (0.1.2)
+ fog-core
+ nokogiri (~> 1.5, >= 1.5.11)
font-awesome-rails (4.2.0.0)
railties (>= 3.2, < 5.0)
foreman (0.63.0)
dotenv (>= 0.7)
thor (>= 0.13.6)
- formatador (0.2.4)
+ formatador (0.2.5)
gemnasium-gitlab-service (0.2.6)
rugged (~> 0.21)
gemojione (2.0.0)
@@ -225,7 +266,7 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.1.0)
gemojione (~> 2.0)
- gitlab_git (7.1.13)
+ gitlab_git (7.2.3)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
@@ -261,19 +302,6 @@ GEM
grape-entity (0.4.2)
activesupport
multi_json (>= 1.3.2)
- growl (1.0.3)
- guard (2.2.4)
- formatador (>= 0.2.4)
- listen (~> 2.1)
- lumberjack (~> 1.0)
- pry (>= 0.9.12)
- thor (>= 0.18.1)
- guard-rspec (4.2.0)
- guard (>= 2.1.1)
- rspec (>= 2.14, < 4.0)
- guard-spinach (0.0.2)
- guard (>= 1.1)
- spinach
haml (4.0.5)
tilt
haml-rails (0.5.3)
@@ -300,14 +328,10 @@ GEM
i18n (0.7.0)
ice_cube (0.11.1)
ice_nine (0.10.0)
- jasmine-core (2.2.0)
- jasmine-rails (0.10.8)
- jasmine-core (>= 1.3, < 3.0)
- phantomjs (>= 1.9)
- railties (>= 3.2.0)
- sprockets-rails
+ inflecto (0.0.2)
+ ipaddress (0.8.0)
jquery-atwho-rails (1.0.1)
- jquery-rails (3.1.0)
+ jquery-rails (3.1.2)
railties (>= 3.0, < 5.0)
thor (>= 0.14, < 2.0)
jquery-scrollto-rails (1.4.3)
@@ -332,7 +356,8 @@ GEM
celluloid (~> 0.16.0)
rb-fsevent (>= 0.9.3)
rb-inotify (>= 0.9)
- lumberjack (1.0.4)
+ macaddr (1.7.1)
+ systemu (~> 2.6.2)
mail (2.6.3)
mime-types (>= 1.16, < 3)
method_source (0.8.2)
@@ -346,9 +371,9 @@ GEM
multipart-post (1.2.0)
mysql2 (0.3.16)
net-ldap (0.11)
- net-scp (1.1.2)
+ net-scp (1.2.1)
net-ssh (>= 2.6.5)
- net-ssh (2.8.0)
+ net-ssh (2.9.2)
newrelic_rpm (3.9.4.245)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
@@ -389,18 +414,24 @@ GEM
omniauth-oauth2 (1.1.1)
oauth2 (~> 0.8.0)
omniauth (~> 1.0)
+ omniauth-saml (1.3.1)
+ omniauth (~> 1.1)
+ ruby-saml (~> 0.8.1)
omniauth-shibboleth (1.1.1)
omniauth (>= 1.0.0)
omniauth-twitter (1.0.1)
multi_json (~> 1.3)
omniauth-oauth (~> 1.0)
+ opennebula (4.12.1)
+ json
+ nokogiri
+ rbvmomi
org-ruby (0.9.12)
rubypants (~> 0.2)
orm_adapter (0.5.0)
parser (2.2.0.2)
ast (>= 1.1, < 3.0)
pg (0.15.1)
- phantomjs (1.9.8.0)
poltergeist (1.5.1)
capybara (~> 2.1)
cliver (~> 0.3.1)
@@ -418,7 +449,7 @@ GEM
quiet_assets (1.0.2)
railties (>= 3.1, < 5.0)
racc (1.4.10)
- rack (1.5.2)
+ rack (1.5.3)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (4.3.0)
@@ -450,8 +481,6 @@ GEM
sprockets-rails (~> 2.0)
rails-observers (0.1.2)
activemodel (~> 4.0)
- rails_autolink (1.1.6)
- rails (> 3.1)
railties (4.1.9)
actionpack (= 4.1.9)
activesupport (= 4.1.9)
@@ -464,6 +493,10 @@ GEM
rb-fsevent (0.9.4)
rb-inotify (0.9.5)
ffi (>= 0.5.0)
+ rbvmomi (1.8.2)
+ builder
+ nokogiri (>= 1.4.1)
+ trollop
rdoc (3.12.2)
json (~> 1.4)
redcarpet (3.2.3)
@@ -497,10 +530,6 @@ GEM
rqrcode (0.4.2)
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
- rspec (2.99.0)
- rspec-core (~> 2.99.0)
- rspec-expectations (~> 2.99.0)
- rspec-mocks (~> 2.99.0)
rspec-collection_matchers (1.1.2)
rspec-expectations (>= 2.99.0.beta1)
rspec-core (2.99.2)
@@ -523,6 +552,9 @@ GEM
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.4)
ruby-progressbar (1.7.1)
+ ruby-saml (0.8.2)
+ nokogiri (>= 1.5.0)
+ uuid (~> 2.3)
ruby2ruby (2.1.3)
ruby_parser (~> 3.1)
sexp_processor (~> 4.0)
@@ -532,7 +564,7 @@ GEM
rubypants (0.2.0)
rugged (0.22.2)
rugments (1.0.0.beta7)
- safe_yaml (0.9.7)
+ safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
sass (3.2.19)
@@ -554,7 +586,7 @@ GEM
thor (~> 0.14)
settingslogic (2.0.9)
sexp_processor (4.4.5)
- shoulda-matchers (2.7.0)
+ shoulda-matchers (2.8.0)
activesupport (>= 3.0.0)
sidekiq (3.3.0)
celluloid (>= 0.16.0)
@@ -594,6 +626,8 @@ GEM
spring (>= 0.9.1)
spring-commands-spinach (1.0.0)
spring (>= 0.9.1)
+ spring-commands-teaspoon (0.0.2)
+ spring (>= 0.9.1)
sprockets (2.11.0)
hike (~> 1.2)
multi_json (~> 1.0)
@@ -606,8 +640,13 @@ GEM
stamp (0.5.0)
state_machine (1.2.0)
stringex (2.5.2)
+ systemu (2.6.5)
task_list (1.0.2)
html-pipeline
+ teaspoon (1.0.2)
+ railties (>= 3.2.5, < 5)
+ teaspoon-jasmine (2.2.0)
+ teaspoon (>= 1.0.0)
temple (0.6.7)
term-ansicolor (1.2.2)
tins (~> 0.8)
@@ -633,7 +672,8 @@ GEM
multi_json (~> 1.7)
twitter-stream (~> 0.1)
tins (0.13.1)
- turbolinks (2.0.0)
+ trollop (2.1.2)
+ turbolinks (2.5.3)
coffee-rails
twitter-stream (0.1.16)
eventmachine (>= 0.12.8)
@@ -654,6 +694,8 @@ GEM
raindrops (~> 0.7)
unicorn-worker-killer (0.4.2)
unicorn (~> 4)
+ uuid (2.3.7)
+ macaddr (~> 1.0)
version_sorter (2.0.0)
virtus (1.0.1)
axiom-types (~> 0.0.5)
@@ -662,8 +704,8 @@ GEM
equalizer (~> 0.0.7)
warden (1.2.3)
rack (>= 1.0)
- webmock (1.16.0)
- addressable (>= 2.2.7)
+ webmock (1.21.0)
+ addressable (>= 2.3.6)
crack (>= 0.3.2)
websocket-driver (0.3.3)
wikicloth (0.8.1)
@@ -690,7 +732,7 @@ DEPENDENCIES
binding_of_caller
bootstrap-sass (~> 3.0)
brakeman
- browser
+ browser (~> 0.8.0)
byebug
cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.2.1)
@@ -714,7 +756,7 @@ DEPENDENCIES
enumerize
factory_girl_rails
ffaker (~> 2.0.0)
- fog (~> 1.14)
+ fog (~> 1.25.0)
font-awesome-rails (~> 4.2)
foreman
gemnasium-gitlab-service (~> 0.2)
@@ -723,23 +765,19 @@ DEPENDENCIES
gitlab-grack (~> 2.0.2)
gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.1)
- gitlab_git (~> 7.1.13)
+ gitlab_git (~> 7.2.3)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.2.1)
gollum-lib (~> 4.0.2)
gon (~> 5.0.0)
grape (~> 0.6.1)
grape-entity (~> 0.4.2)
- growl
- guard-rspec
- guard-spinach
haml-rails
hipchat (~> 1.5.0)
html-pipeline (~> 1.11.0)
httparty
- jasmine-rails
jquery-atwho-rails (~> 1.0.0)
- jquery-rails
+ jquery-rails (= 3.1.2)
jquery-scrollto-rails
jquery-turbolinks
jquery-ui-rails
@@ -757,6 +795,7 @@ DEPENDENCIES
omniauth-gitlab
omniauth-google-oauth2
omniauth-kerberos
+ omniauth-saml
omniauth-shibboleth
omniauth-twitter
org-ruby (= 0.9.12)
@@ -769,10 +808,7 @@ DEPENDENCIES
rack-mini-profiler
rack-oauth2 (~> 1.0.5)
rails (~> 4.1.0)
- rails_autolink (~> 1.1)
raphael-rails (~> 2.1.2)
- rb-fsevent
- rb-inotify
rdoc (~> 3.6)
redcarpet (~> 3.2.3)
redis-rails
@@ -788,7 +824,7 @@ DEPENDENCIES
seed-fu
select2-rails
settingslogic
- shoulda-matchers (~> 2.7.0)
+ shoulda-matchers (~> 2.8.0)
sidekiq (~> 3.3)
sidetiq (= 0.6.3)
simplecov
@@ -798,15 +834,18 @@ DEPENDENCIES
slim
spinach-rails
spring (~> 1.3.1)
- spring-commands-rspec (= 1.0.4)
- spring-commands-spinach (= 1.0.0)
+ spring-commands-rspec (~> 1.0.0)
+ spring-commands-spinach (~> 1.0.0)
+ spring-commands-teaspoon (~> 0.0.2)
stamp
state_machine
task_list (= 1.0.2)
+ teaspoon (~> 1.0.0)
+ teaspoon-jasmine
test_after_commit
thin
tinder (~> 1.9.2)
- turbolinks
+ turbolinks (~> 2.5.0)
uglifier
underscore-rails (~> 1.4.4)
unf
@@ -814,5 +853,5 @@ DEPENDENCIES
unicorn-worker-killer
version_sorter
virtus
- webmock
+ webmock (~> 1.21.0)
wikicloth (= 0.8.1)
diff --git a/Guardfile b/Guardfile
deleted file mode 100644
index 68ac3232b09..00000000000
--- a/Guardfile
+++ /dev/null
@@ -1,27 +0,0 @@
-# A sample Guardfile
-# More info at https://github.com/guard/guard#readme
-
-guard 'rspec', cmd: "spring rspec", all_on_start: false, all_after_pass: false do
- watch(%r{^spec/.+_spec\.rb$})
- watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
- watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" }
- watch('spec/spec_helper.rb') { "spec" }
-
- # Rails example
- watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
- watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
- watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
- watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
- watch('config/routes.rb') { "spec/routing" }
- watch('app/controllers/application_controller.rb') { "spec/controllers" }
-
- # Capybara request specs
- watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
-end
-
-guard 'spinach', command_prefix: 'spring' do
- watch(%r|^features/(.*)\.feature|)
- watch(%r|^features/steps/(.*)([^/]+)\.rb|) do |m|
- "features/#{m[1]}#{m[2]}.feature"
- end
-end
diff --git a/app/assets/images/authbuttons/google_64.png b/app/assets/images/authbuttons/google_64.png
index 94a0e089c6e..fb64f8bee68 100644
--- a/app/assets/images/authbuttons/google_64.png
+++ b/app/assets/images/authbuttons/google_64.png
Binary files differ
diff --git a/app/assets/images/authbuttons/twitter_64.png b/app/assets/images/authbuttons/twitter_64.png
index 5c9f14cb077..e3bd9169a34 100644
--- a/app/assets/images/authbuttons/twitter_64.png
+++ b/app/assets/images/authbuttons/twitter_64.png
Binary files differ
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index ea2a4b97101..6a3f7386d5b 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -49,8 +49,6 @@ window.slugify = (text) ->
window.ajaxGet = (url) ->
$.ajax({type: "GET", url: url, dataType: "script"})
-window.showAndHide = (selector) ->
-
window.split = (val) ->
return val.split( /,\s*/ )
@@ -92,15 +90,7 @@ window.disableButtonIfAnyEmptyField = (form, form_selector, button_selector) ->
window.sanitize = (str) ->
return str.replace(/<(?:.|\n)*?>/gm, '')
-window.linkify = (str) ->
- exp = /(\b(https?|ftp|file):\/\/[-A-Z0-9+&@#\/%?=~_|!:,.;]*[-A-Z0-9+&@#\/%=~_|])/ig
- return str.replace(exp,"<a href='$1'>$1</a>")
-
-window.simpleFormat = (str) ->
- linkify(sanitize(str).replace(/\n/g, '<br />'))
-
window.unbindEvents = ->
- $(document).unbind('scroll')
$(document).off('scroll')
window.shiftWindow = ->
@@ -177,6 +167,10 @@ $ ->
$(@).next('table').show()
$(@).remove()
+ $('.navbar-toggle').on 'click', ->
+ $('.header-content .title').toggle()
+ $('.header-content .navbar-collapse').toggle()
+
# Show/hide comments on diff
$("body").on "click", ".js-toggle-diff-comments", (e) ->
$(@).toggleClass('active')
@@ -192,14 +186,3 @@ $ ->
new ConfirmDangerModal(form, text)
new Aside()
-
-(($) ->
- # Disable an element and add the 'disabled' Bootstrap class
- $.fn.extend disable: ->
- $(@).attr('disabled', 'disabled').addClass('disabled')
-
- # Enable an element and remove the 'disabled' Bootstrap class
- $.fn.extend enable: ->
- $(@).removeAttr('disabled').removeClass('disabled')
-
-)(jQuery)
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index fca2a290e2d..a7476146010 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -10,12 +10,17 @@ class @DropzoneInput
iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>"
btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
project_uploads_path = window.project_uploads_path or null
+ markdown_preview_path = window.markdown_preview_path or null
max_file_size = gon.max_file_size or 10
form_textarea = $(form).find("textarea.markdown-area")
form_textarea.wrap "<div class=\"div-dropzone\"></div>"
- form_textarea.bind 'paste', (event) =>
+ form_textarea.on 'paste', (event) =>
handlePaste(event)
+ form_textarea.on "input", ->
+ hideReferencedUsers()
+ form_textarea.on "blur", ->
+ renderMarkdown()
form_dropzone = $(form).find('.div-dropzone')
form_dropzone.parent().addClass "div-dropzone-wrapper"
@@ -45,16 +50,7 @@ class @DropzoneInput
form.find(".md-write-holder").hide()
form.find(".md-preview-holder").show()
- preview = form.find(".js-md-preview")
- mdText = form.find(".markdown-area").val()
- if mdText.trim().length is 0
- preview.text "Nothing to preview."
- else
- preview.text "Loading..."
- $.post($(this).data("url"),
- md_text: mdText
- ).success (previewData) ->
- preview.html previewData
+ renderMarkdown()
# Write button
$(document).off "click", ".js-md-write-button"
@@ -133,6 +129,40 @@ class @DropzoneInput
child = $(dropzone[0]).children("textarea")
+ hideReferencedUsers = ->
+ referencedUsers = form.find(".referenced-users")
+ referencedUsers.hide()
+
+ renderReferencedUsers = (users) ->
+ referencedUsers = form.find(".referenced-users")
+
+ if referencedUsers.length
+ if users.length >= 10
+ referencedUsers.show()
+ referencedUsers.find(".js-referenced-users-count").text users.length
+ else
+ referencedUsers.hide()
+
+ renderMarkdown = ->
+ preview = form.find(".js-md-preview")
+ mdText = form.find(".markdown-area").val()
+ if mdText.trim().length is 0
+ preview.text "Nothing to preview."
+ hideReferencedUsers()
+ else
+ preview.text "Loading..."
+ $.ajax(
+ type: "POST",
+ url: markdown_preview_path,
+ data: {
+ text: mdText
+ },
+ dataType: "json"
+ ).success (data) ->
+ preview.html data.body
+
+ renderReferencedUsers data.references.users
+
formatLink = (link) ->
text = "[#{link.alt}](#{link.url})"
text = "!#{text}" if link.is_image
diff --git a/app/assets/javascripts/extensions/jquery.js.coffee b/app/assets/javascripts/extensions/jquery.js.coffee
index 40fb6cb9fc3..0a9db8eb5ef 100644
--- a/app/assets/javascripts/extensions/jquery.js.coffee
+++ b/app/assets/javascripts/extensions/jquery.js.coffee
@@ -1,13 +1,11 @@
-$.fn.showAndHide = ->
- $(@).show().
- delay(3000).
- fadeOut()
-
-$.fn.enableButton = ->
- $(@).removeAttr('disabled').
- removeClass('disabled')
-
-$.fn.disableButton = ->
- $(@).attr('disabled', 'disabled').
- addClass('disabled')
+# Disable an element and add the 'disabled' Bootstrap class
+$.fn.extend disable: ->
+ $(@)
+ .attr('disabled', 'disabled')
+ .addClass('disabled')
+# Enable an element and remove the 'disabled' Bootstrap class
+$.fn.extend enable: ->
+ $(@)
+ .removeAttr('disabled')
+ .removeClass('disabled')
diff --git a/app/assets/javascripts/gfm_auto_complete.js.coffee b/app/assets/javascripts/gfm_auto_complete.js.coffee
index 4eb3f3c03f3..7967892f856 100644
--- a/app/assets/javascripts/gfm_auto_complete.js.coffee
+++ b/app/assets/javascripts/gfm_auto_complete.js.coffee
@@ -10,7 +10,7 @@ GitLab.GfmAutoComplete =
# Team Members
Members:
- template: '<li>${username} <small>${name}</small></li>'
+ template: '<li>${username} <small>${title}</small></li>'
# Issues and MergeRequests
Issues:
@@ -34,7 +34,13 @@ GitLab.GfmAutoComplete =
searchKey: 'search'
callbacks:
beforeSave: (members) ->
- $.map members, (m) -> name: m.name, username: m.username, search: "#{m.username} #{m.name}"
+ $.map members, (m) ->
+ title = m.name
+ title += " (#{m.count})" if m.count
+
+ username: m.username
+ title: sanitize(title)
+ search: sanitize("#{m.username} #{m.name}")
input.atwho
at: '#'
@@ -44,7 +50,10 @@ GitLab.GfmAutoComplete =
insertTpl: '${atwho-at}${id}'
callbacks:
beforeSave: (issues) ->
- $.map issues, (i) -> id: i.iid, title: sanitize(i.title), search: "#{i.iid} #{i.title}"
+ $.map issues, (i) ->
+ id: i.iid
+ title: sanitize(i.title)
+ search: "#{i.iid} #{i.title}"
input.atwho
at: '!'
@@ -54,7 +63,10 @@ GitLab.GfmAutoComplete =
insertTpl: '${atwho-at}${id}'
callbacks:
beforeSave: (merges) ->
- $.map merges, (m) -> id: m.iid, title: sanitize(m.title), search: "#{m.iid} #{m.title}"
+ $.map merges, (m) ->
+ id: m.iid
+ title: sanitize(m.title)
+ search: "#{m.iid} #{m.title}"
input.one 'focus', =>
$.getJSON(@dataSource).done (data) ->
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index 86ad3d03bac..74d6b80be5e 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -1,4 +1,3 @@
-#= require jquery
#= require jquery.waitforimages
#= require task_list
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
index 3937c428e24..b8f916b5223 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -1,5 +1,4 @@
-#= require jquery
-#= require bootstrap
+#= require jquery.waitforimages
#= require task_list
class @MergeRequest
@@ -26,7 +25,7 @@ class @MergeRequest
@commits_loaded = @opts.commits_loaded or false
this.bindEvents()
- this.activateTabFromHash()
+ this.activateTabFromPath()
this.initMergeWidget()
this.$('.show-all-commits').on 'click', =>
@@ -82,19 +81,16 @@ class @MergeRequest
bindEvents: ->
this.$('.merge-request-tabs a[data-toggle="tab"]').on 'shown.bs.tab', (e) =>
$target = $(e.target)
-
- # Nothing else to be done if we're on the first tab
- return if $target.data('action') == 'notes'
-
- # Persist current tab selection via URL
- href = $target.attr('href')
- if href.substr(0,1) == '#'
- location.replace("#!#{href.substr(1)}")
+ tab_action = $target.data('action')
# Lazy-load diffs
- if $target.data('action') == 'diffs'
+ if tab_action == 'diffs'
this.loadDiff() unless @diffs_loaded
- $('.diff-header').trigger("sticky_kit:recalc")
+ $('.diff-header').trigger('sticky_kit:recalc')
+
+ # Skip tab-persisting behavior on MergeRequests#new
+ unless @opts.action == 'new'
+ @setCurrentAction(tab_action)
this.$('.accept_merge_request').on 'click', ->
$('.automerge_widget.can_be_merged').hide()
@@ -112,27 +108,54 @@ class @MergeRequest
this.$('.remove_source_branch_in_progress').hide()
this.$('.remove_source_branch_widget.failed').show()
- # Activates a tab section based on the `#!` URL hash
+ # Activate a tab based on the current URL path
#
- # If no hash value is present (i.e., on the initial page load), the first tab
- # is selected by default.
+ # If the current action is 'show' or 'new' (i.e., initial page load),
+ # activates the first tab, otherwise activates the tab corresponding to the
+ # current action (diffs, commits).
+ activateTabFromPath: ->
+ if @opts.action == 'show' || @opts.action == 'new'
+ this.$('.merge-request-tabs a[data-toggle="tab"]:first').tab('show')
+ else
+ this.$(".merge-request-tabs a[data-action='#{@opts.action}']").tab('show')
+
+ # Replaces the current Merge Request-specific action in the URL with a new one
#
- # ... unless the current controller action is `diffs`, in which case that tab
- # is selected instead. Fun, right?
+ # If the action is "notes", the URL is reset to the standard
+ # `MergeRequests#show` route.
#
- # Note: We use a `#!` instead of a standard URL hash for two reasons:
+ # Examples:
#
- # 1. Prevents the hash acting like an anchor and scrolling the page.
- # 2. Prevents mutating browser history.
- activateTabFromHash: ->
- # Correct the hash if we came here directly via the `/diffs` path
- if location.hash == '' and @opts.action == 'diffs'
- location.replace('#!diffs')
-
- if location.hash == ''
- this.$('.merge-request-tabs a[data-toggle="tab"]:first').tab('show')
- else if location.hash.substr(0,2) == '#!'
- this.$(".merge-request-tabs a[href='##{location.hash.substr(2)}']").tab("show")
+ # location.pathname # => "/namespace/project/merge_requests/1"
+ # setCurrentAction('diffs')
+ # location.pathname # => "/namespace/project/merge_requests/1/diffs"
+ #
+ # location.pathname # => "/namespace/project/merge_requests/1/diffs"
+ # setCurrentAction('notes')
+ # location.pathname # => "/namespace/project/merge_requests/1"
+ #
+ # location.pathname # => "/namespace/project/merge_requests/1/diffs"
+ # setCurrentAction('commits')
+ # location.pathname # => "/namespace/project/merge_requests/1/commits"
+ setCurrentAction: (action) ->
+ # Normalize action, just to be safe
+ action = 'notes' if action == 'show'
+
+ # Remove a trailing '/commits' or '/diffs'
+ new_state = location.pathname.replace(/\/(commits|diffs)\/?$/, '')
+
+ # Append the new action if we're on a tab other than 'notes'
+ unless action == 'notes'
+ new_state += "/#{action}"
+
+ # Ensure parameters and hash come along for the ride
+ new_state += location.search + location.hash
+
+ # Replace the current history state with the new one without breaking
+ # Turbolinks' history.
+ #
+ # See https://github.com/rails/turbolinks/issues/363
+ history.replaceState {turbolinks: true, url: new_state}, '', new_state
showState: (state) ->
$('.automerge_widget').hide()
@@ -161,7 +184,7 @@ class @MergeRequest
loadDiff: (event) ->
$.ajax
type: 'GET'
- url: this.$('.merge-request-tabs .diffs-tab a').data('source') + ".json"
+ url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + ".json"
beforeSend: =>
this.$('.mr-loading-status .loading').show()
complete: =>
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index f186fec2a0c..21656f59149 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -1,6 +1,4 @@
-#= require jquery
#= require autosave
-#= require bootstrap
#= require dropzone
#= require dropzone_input
#= require gfm_auto_complete
@@ -65,13 +63,11 @@ class @Notes
# fetch notes when tab becomes visible
$(document).on "visibilitychange", @visibilityChange
- @notes_forms = '.js-main-target-form textarea, .js-discussion-note-form textarea'
# Chrome doesn't fire keypress or keyup for Command+Enter, so we need keydown.
- $(document).on('keydown', @notes_forms, (e) ->
+ $(document).on 'keydown', '.js-note-text', (e) ->
return if e.originalEvent.repeat
if e.keyCode == 10 || ((e.metaKey || e.ctrlKey) && e.keyCode == 13)
- $(@).parents('form').submit()
- )
+ $(@).closest('form').submit()
cleanBinding: ->
$(document).off "ajax:success", ".js-main-target-form"
@@ -86,7 +82,7 @@ class @Notes
$(document).off "click", ".js-discussion-reply-button"
$(document).off "click", ".js-add-diff-note-button"
$(document).off "visibilitychange"
- $(document).off "keydown", @notes_forms
+ $(document).off "keydown", ".js-note-text"
$(document).off "keyup", ".js-note-text"
$(document).off "click", ".js-note-target-reopen"
$(document).off "click", ".js-note-target-close"
diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee
index de356fbec77..40459a9a155 100644
--- a/app/assets/javascripts/profile.js.coffee
+++ b/app/assets/javascripts/profile.js.coffee
@@ -12,11 +12,11 @@ class @Profile
$(this).find('.update-failed').hide()
$('.update-username form').on 'ajax:complete', ->
- $(this).find('.btn-save').enableButton()
+ $(this).find('.btn-save').enable()
$(this).find('.loading-gif').hide()
$('.update-notifications').on 'ajax:complete', ->
- $(this).find('.btn-save').enableButton()
+ $(this).find('.btn-save').enable()
$('.js-choose-user-avatar-button').bind "click", ->
diff --git a/app/assets/javascripts/shortcuts_issuable.coffee b/app/assets/javascripts/shortcuts_issuable.coffee
index 6b534f29218..bb532194682 100644
--- a/app/assets/javascripts/shortcuts_issuable.coffee
+++ b/app/assets/javascripts/shortcuts_issuable.coffee
@@ -1,6 +1,4 @@
-#= require jquery
#= require mousetrap
-
#= require shortcuts_navigation
class @ShortcutsIssuable extends ShortcutsNavigation
diff --git a/app/assets/javascripts/stat_graph_contributors.js.coffee b/app/assets/javascripts/stat_graph_contributors.js.coffee
index ed12bdcef22..3be14cb43dd 100644
--- a/app/assets/javascripts/stat_graph_contributors.js.coffee
+++ b/app/assets/javascripts/stat_graph_contributors.js.coffee
@@ -1,5 +1,4 @@
#= require d3
-#= require jquery
#= require stat_graph_contributors_util
class @ContributorsStatGraph
diff --git a/app/assets/javascripts/zen_mode.js.coffee b/app/assets/javascripts/zen_mode.js.coffee
index dc6a84c6c52..8a0564a9098 100644
--- a/app/assets/javascripts/zen_mode.js.coffee
+++ b/app/assets/javascripts/zen_mode.js.coffee
@@ -1,3 +1,7 @@
+#= require dropzone
+#= require mousetrap
+#= require mousetrap/pause
+
class @ZenMode
constructor: ->
@active_zen_area = null
@@ -26,7 +30,7 @@ class @ZenMode
@exitZenMode()
$(document).on 'keydown', (e) =>
- if e.keyCode is $.ui.keyCode.ESCAPE
+ if e.keyCode is 27 # Esc
@exitZenMode()
e.preventDefault()
@@ -42,7 +46,9 @@ class @ZenMode
@active_checkbox.prop('checked', false)
@active_zen_area = null
@active_checkbox = null
- window.location.hash = ''
- window.scrollTo(window.pageXOffset, @scroll_position)
+ @restoreScroll(@scroll_position)
# Enable dropzone when leaving ZEN mode
Dropzone.forElement('.div-dropzone').enable()
+
+ restoreScroll: (y) ->
+ window.scrollTo(window.pageXOffset, y)
diff --git a/app/assets/stylesheets/base/layout.scss b/app/assets/stylesheets/base/layout.scss
index 62c11b06368..690d89a5c16 100644
--- a/app/assets/stylesheets/base/layout.scss
+++ b/app/assets/stylesheets/base/layout.scss
@@ -4,7 +4,7 @@ html {
&.touch .tooltip { display: none !important; }
body {
- padding-top: 46px;
+ padding-top: $header-height;
}
}
diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss
index 596376c3970..08f153dfbc9 100644
--- a/app/assets/stylesheets/base/variables.scss
+++ b/app/assets/stylesheets/base/variables.scss
@@ -1,16 +1,19 @@
$style_color: #474D57;
-$hover: #FFF3EB;
+$hover: #FFFAF1;
$gl-text-color: #222222;
$gl-link-color: #446e9b;
$nprogress-color: #c0392b;
$gl-font-size: 14px;
$list-font-size: 15px;
+$sidebar_collapsed_width: 52px;
$sidebar_width: 230px;
$avatar_radius: 50%;
$code_font_size: 13px;
$code_line_height: 1.5;
$border-color: #E5E5E5;
$background-color: #f5f5f5;
+$header-height: 50px;
+
/*
* State colors:
diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index b69c5c4b574..1419a9cded9 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/generic/common.scss
@@ -307,7 +307,7 @@ table {
}
.btn-sign-in {
- margin-top: 5px;
+ margin-top: 7px;
text-shadow: none;
}
diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/generic/forms.scss
index 7e070b4f386..4282832e2bf 100644
--- a/app/assets/stylesheets/generic/forms.scss
+++ b/app/assets/stylesheets/generic/forms.scss
@@ -49,14 +49,6 @@ label {
width: 250px;
}
-.input-mx-250 {
- max-width: 250px;
-}
-
-.input-mn-300 {
- min-width: 300px;
-}
-
.custom-form-control {
width: 150px;
}
diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss
index fe32b024f49..5e8701830e7 100644
--- a/app/assets/stylesheets/generic/header.scss
+++ b/app/assets/stylesheets/generic/header.scss
@@ -3,76 +3,40 @@
*
*/
header {
+ &.navbar-empty {
+ background: #FFF;
+ border-bottom: 1px solid #EEE;
+
+ .center-logo {
+ margin: 8px 0;
+ text-align: center;
+ }
+ }
+
&.navbar-gitlab {
z-index: 100;
margin-bottom: 0;
- min-height: 40px;
+ min-height: $header-height;
border: none;
width: 100%;
.container {
+ background: #FFF;
width: 100% !important;
padding: 0;
- padding-right: 35px;
- background: #FFF;
- border-bottom: 1px solid #EEE;
filter: none;
- .title {
- position: relative;
- float: left;
- margin: 0;
- margin-left: 25px;
- font-size: 18px;
- line-height: 44px;
- font-weight: bold;
- color: #444;
-
- @include str-truncated(37%);
-
- a {
- color: #444;
- &:hover {
- text-decoration: underline;
- }
- }
- }
-
- .app_logo {
- border-bottom: 1px solid transparent;
- margin-bottom: -1px;
-
- a {
- padding: 5px 8px;
-
- img {
- float: left;
- }
-
- h3 {
- width: 158px;
- float: left;
- margin: 0;
- margin-left: 20px;
- font-size: 18px;
- line-height: 34px;
- font-weight: normal;
- }
- }
- }
-
.nav > li > a {
color: #888;
font-size: 14px;
- line-height: 19px;
padding: 0;
background-color: #f5f5f5;
- margin: 9px 0;
+ margin: ($header-height - 28) / 2 0;
margin-left: 10px;
border-radius: 40px;
- height: 26px;
- width: 26px;
- line-height: 26px;
+ height: 28px;
+ width: 28px;
+ line-height: 28px;
text-align: center;
&:hover, &:focus, &:active {
@@ -80,93 +44,84 @@ header {
}
}
- /** NAV block with links and profile **/
- .nav {
- float: right;
- margin-right: 0;
- }
-
.navbar-toggle {
color: #666;
margin: 0;
border-radius: 0;
+ position: absolute;
+ right: 2px;
&:hover {
background-color: #EEE;
}
}
}
-
- .turbolink-spinner {
- font-size: 20px;
- margin-right: 10px;
- }
-
- @media (max-width: $screen-xs-max) {
- border-width: 0;
- font-size: 18px;
-
- .title {
- @include str-truncated(70%);
- }
-
- .navbar-collapse {
- margin-top: 47px;
- }
-
- .navbar-nav {
- margin: 5px 0;
-
- .visible-xs, .visable-sm {
- display: table-cell !important;
- }
- }
-
- li {
- display: table-cell;
- width: 1%;
-
- a {
- text-align: center;
- font-size: 18px !important;
- }
- }
- }
}
- /**
- *
- * Logo holder
- *
- */
- .app_logo {
+ .header-logo {
+ border-bottom: 1px solid transparent;
float: left;
- margin-right: 9px;
+ height: $header-height;
+ width: $sidebar_width;
a {
float: left;
- height: 46px;
+ height: $header-height;
width: 100%;
+ padding: ($header-height - 36 ) / 2 8px;
+
+ h3 {
+ width: 158px;
+ float: left;
+ margin: 0;
+ margin-left: 14px;
+ font-size: 18px;
+ line-height: $header-height - 14;
+ font-weight: normal;
+ }
img {
width: 36px;
height: 36px;
+ float: left;
}
}
+
&:hover {
background-color: #EEE;
}
}
- /**
- *
- * Search box
- *
- */
+ .header-content {
+ border-bottom: 1px solid #EEE;
+ padding-right: 35px;
+ height: $header-height;
+
+ .title {
+ position: relative;
+ float: left;
+ margin: 0;
+ margin-left: 35px;
+ font-size: 18px;
+ line-height: $header-height;
+ font-weight: bold;
+ color: #444;
+
+ @include str-truncated(37%);
+
+ a {
+ color: #444;
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+ }
+ }
+
.search {
margin-right: 10px;
margin-left: 10px;
- margin-top: 8px;
+ margin-top: ($header-height - 28) / 2;
form {
margin: 0;
@@ -174,6 +129,7 @@ header {
}
.search-input {
+ width: 220px;
background-image: image-url("icon-search.png");
background-repeat: no-repeat;
background-position: 10px;
@@ -183,45 +139,80 @@ header {
font-size: 13px;
background-color: #f5f5f5;
border-color: #f5f5f5;
+
+ &:focus {
+ @include box-shadow(none);
+ outline: none;
+ border-color: #DDD;
+ background-color: #FFF;
+ }
}
}
}
-.search .search-input {
- width: 300px;
-}
+@mixin collapsed-header {
+ .header-logo {
+ width: $sidebar_collapsed_width;
-@media (max-width: 1200px) {
- .search .search-input {
- width: 200px;
+ h3 {
+ display: none;
+ }
}
-}
-@media (max-width: $screen-xs-max) {
- #nprogress .spinner {
- right: 35px !important;
+ .header-content {
+ .title {
+ margin-left: 30px;
+ }
}
}
@media (max-width: $screen-md-max) {
- .header-collapsed, .header-expanded {
- width: 52px;
+ header .container .title {
+ max-width: 43%;
+ }
- h3 {
- display: none;
- }
+ .header-collapsed, .header-expanded {
+ @include collapsed-header;
}
}
@media(min-width: $screen-md-max) {
.header-collapsed {
- width: 52px;
-
- h3 {
- display: none;
- }
+ @include collapsed-header;
}
.header-expanded {
}
}
+
+@media (max-width: $screen-xs-max) {
+ header .container {
+ font-size: 18px;
+
+ .title {
+ max-width: 70%;
+ }
+
+ .navbar-nav {
+ margin: 0px;
+ float: none !important;
+
+ .visible-xs, .visable-sm {
+ display: table-cell !important;
+ }
+ }
+
+ .navbar-collapse {
+ padding-left: 5px;
+
+ li {
+ display: table-cell;
+ width: 1%;
+
+ a {
+ margin-left: 8px !important;
+ }
+ }
+ }
+ }
+}
diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/generic/lists.scss
index 08bf6e943d2..c502d953c75 100644
--- a/app/assets/stylesheets/generic/lists.scss
+++ b/app/assets/stylesheets/generic/lists.scss
@@ -39,7 +39,6 @@
&:hover {
background: $hover;
- border-bottom: 1px solid darken($hover, 10%);
}
&:last-child {
diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss
index eb39b6bb7e9..f94677d1925 100644
--- a/app/assets/stylesheets/generic/markdown_area.scss
+++ b/app/assets/stylesheets/generic/markdown_area.scss
@@ -52,6 +52,22 @@
transition: opacity 200ms ease-in-out;
}
+.md-area {
+ position: relative;
+}
+
+.md-header ul {
+ float: left;
+}
+
+.referenced-users {
+ padding: 10px 0;
+ color: #999;
+ margin-left: 10px;
+ margin-top: 1px;
+ margin-right: 130px;
+}
+
.md-preview-holder {
background: #FFF;
border: 1px solid #ddd;
diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss
index b7f6fac5223..f04c8eef904 100644
--- a/app/assets/stylesheets/generic/mobile.scss
+++ b/app/assets/stylesheets/generic/mobile.scss
@@ -19,6 +19,10 @@
}
}
+ .referenced-users {
+ margin-right: 0;
+ }
+
.issues-filters,
.dash-projects-filters,
.check-all-holder {
@@ -57,9 +61,24 @@
}
.container .title {
- margin-left: 6px !important;
+ margin-left: 15px !important;
max-width: 70% !important;
}
+
+ .issue-info, .merge-request-info {
+ display: none;
+ }
+
+ .issue-details {
+ .creator,
+ .page-title .btn-close {
+ display: none;
+ }
+ }
+
+ %ul.notes .note-role, .note-actions {
+ display: none;
+ }
}
@media (max-width: $screen-sm-max) {
diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss
index a80b5850803..65e06e14c73 100644
--- a/app/assets/stylesheets/generic/sidebar.scss
+++ b/app/assets/stylesheets/generic/sidebar.scss
@@ -1,6 +1,4 @@
.page-with-sidebar {
- background: $background-color;
-
.sidebar-wrapper {
position: fixed;
top: 0;
@@ -88,7 +86,7 @@
.nav-sidebar {
margin-top: 29px;
position: fixed;
- top: 45px;
+ top: $header-height;
width: $sidebar_width;
}
}
@@ -102,13 +100,13 @@
padding-left: 50px;
.sidebar-wrapper {
- width: 52px;
+ width: $sidebar_collapsed_width;
.nav-sidebar {
margin-top: 29px;
position: fixed;
- top: 45px;
- width: 52px;
+ top: $header-height;
+ width: $sidebar_collapsed_width;
li a {
padding-left: 18px;
@@ -125,28 +123,20 @@
.collapse-nav a {
left: 0px;
- width: 52px;
+ width: $sidebar_collapsed_width;
}
.sidebar-user {
.username {
display: none;
}
-
- .avatar {
- margin-bottom: 10px;
- }
-
- .logout-holder {
- text-align: center;
- }
}
}
}
.collapse-nav a {
position: fixed;
- top: 46px;
+ top: $header-height;
left: 198px;
font-size: 13px;
background: transparent;
@@ -190,9 +180,8 @@
bottom: 0;
width: 100%;
padding: 10px;
- color: #fff;
- .avatar {
+ .username {
margin-top: 5px;
}
}
diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss
index e5590897947..66767cb13cb 100644
--- a/app/assets/stylesheets/generic/typography.scss
+++ b/app/assets/stylesheets/generic/typography.scss
@@ -23,6 +23,13 @@ pre {
font-family: $monospace_font;
}
+code {
+ &.key-fingerprint {
+ background: $body-bg;
+ color: $text-color;
+ }
+}
+
/**
* Wiki typography
*
diff --git a/app/assets/stylesheets/generic/zen.scss b/app/assets/stylesheets/generic/zen.scss
index 26afc21a6ab..7ab01187a02 100644
--- a/app/assets/stylesheets/generic/zen.scss
+++ b/app/assets/stylesheets/generic/zen.scss
@@ -1,15 +1,14 @@
.zennable {
- position: relative;
-
- input {
+ .zen-toggle-comment {
display: none;
}
.zen-enter-link {
color: #888;
position: absolute;
- top: -26px;
+ top: 0px;
right: 4px;
+ line-height: 40px;
}
.zen-leave-link {
@@ -26,10 +25,12 @@
}
}
+ // Hide the Enter link when we're in Zen mode
input:checked ~ .zen-backdrop .zen-enter-link {
display: none;
}
+ // Show the Leave link when we're in Zen mode
input:checked ~ .zen-backdrop .zen-leave-link {
display: block;
position: absolute;
@@ -62,6 +63,9 @@
}
}
+ // Make the placeholder text in the standard textarea the same color as the
+ // background, effectively hiding it
+
.zen-backdrop textarea::-webkit-input-placeholder {
color: white;
}
@@ -78,6 +82,9 @@
color: white;
}
+ // Make the color of the placeholder text in the Zenned-out textarea darker,
+ // so it becomes visible
+
input:checked ~ .zen-backdrop textarea::-webkit-input-placeholder {
color: #999;
}
diff --git a/app/assets/stylesheets/pages/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss
index af9c83e5dc8..09e8d57a100 100644
--- a/app/assets/stylesheets/pages/dashboard.scss
+++ b/app/assets/stylesheets/pages/dashboard.scss
@@ -29,7 +29,7 @@
line-height: 24px;
.str-truncated {
- max-width: 72%;
+ max-width: 76%;
}
a {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index 3572f33e91f..ed938f86b35 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -145,3 +145,9 @@ h2.issue-title {
.issue-form .select2-container {
width: 250px !important;
}
+
+.issues-holder {
+ .issue-info {
+ margin-left: 20px;
+ }
+}
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 3165396a94d..f5ac7bd8805 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -123,38 +123,31 @@
.mr-state-widget {
font-size: 13px;
- background: #F9F9F9;
+ background: #FAFAFA;
margin-bottom: 20px;
color: #666;
- border: 1px solid #EEE;
- @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
+ border: 1px solid #e5e5e5;
+ @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05));
+ @include border-radius(3px);
.ci_widget {
padding: 10px 15px;
font-size: 15px;
- border-bottom: 1px solid #BBB;
- color: #777;
- background-color: $background-color;
+ border-bottom: 1px solid #EEE;
&.ci-success {
color: $gl-success;
- border-color: $gl-success;
- background-color: #F1FAF1;
}
&.ci-pending,
&.ci-running {
color: $gl-warning;
- border-color: $gl-warning;
- background-color: #FAF5F1;
}
&.ci-failed,
&.ci-canceled,
&.ci-error {
color: $gl-danger;
- border-color: $gl-danger;
- background-color: #FAF1F1;
}
}
@@ -162,7 +155,8 @@
padding: 10px 15px;
h4 {
- font-weight: normal;
+ font-weight: bold;
+ margin: 5px 0;
}
p:last-child {
diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss
index a0522030785..203f9374cee 100644
--- a/app/assets/stylesheets/pages/note_form.scss
+++ b/app/assets/stylesheets/pages/note_form.scss
@@ -39,11 +39,8 @@
.new_note, .edit_note {
.buttons {
- float: left;
margin-top: 8px;
- }
- .clearfix {
- margin-bottom: 0;
+ margin-bottom: 3px;
}
.note-preview-holder {
@@ -82,7 +79,6 @@
.note-form-actions {
background: #F9F9F9;
- height: 45px;
.note-form-option {
margin-top: 8px;
diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss
index 5b528b38d36..5a5fbc468a3 100644
--- a/app/assets/stylesheets/pages/profile.scss
+++ b/app/assets/stylesheets/pages/profile.scss
@@ -84,8 +84,9 @@
}
.btn {
- line-height: 36px;
- height: 56px;
+ line-height: 40px;
+ height: 42px;
+ padding: 0px 12px;
img {
width: 32px;
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index ee8c746d2df..e19b2eafa43 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -15,18 +15,16 @@
}
.project-home-panel {
- margin-bottom: 20px;
+ margin-top: 10px;
+ margin-bottom: 15px;
position: relative;
padding-left: 65px;
- border-bottom: 1px solid #DDD;
- padding-bottom: 10px;
- padding-top: 5px;
min-height: 50px;
.project-identicon-holder {
position: absolute;
left: 0;
- top: -10px;
+ top: -14px;
.avatar {
width: 50px;
@@ -48,14 +46,16 @@
}
.project-home-desc {
+ color: $gray;
+ float: left;
font-size: 16px;
line-height: 1.3;
margin-right: 250px;
- }
- .project-home-desc {
- float: left;
- color: $gray;
+ // Render Markdown-generated HTML inline for this block
+ p {
+ display: inline;
+ }
}
}
@@ -210,8 +210,13 @@ ul.nav.nav-projects-tabs {
}
.panel {
+ @include border-radius(3px);
+
.panel-heading, .panel-footer {
- background-color: #fcfcfc;
+ font-weight: normal;
+ background-color: transparent;
+ color: #666;
+ border-color: #EEE;
}
.actions {
@@ -225,7 +230,7 @@ ul.nav.nav-projects-tabs {
}
.nav {
- margin-bottom: 10px;
+ margin-bottom: 15px;
}
}
diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss
index 9b8e3d8e291..10fcaf18fa9 100644
--- a/app/assets/stylesheets/themes/gitlab-theme.scss
+++ b/app/assets/stylesheets/themes/gitlab-theme.scss
@@ -1,8 +1,9 @@
@mixin gitlab-theme($color-light, $color, $color-darker, $color-dark) {
header {
&.navbar-gitlab {
- .app_logo {
+ .header-logo {
background-color: $color-darker;
+ border-color: $color-darker;
a {
color: $color-light;
@@ -19,8 +20,6 @@
}
.page-with-sidebar {
- background: $color-darker;
-
.collapse-nav a {
color: #FFF;
background: $color;
@@ -31,8 +30,12 @@
border-right: 1px solid $color-darker;
.sidebar-user {
- a {
- color: $color-light;
+ color: $color-light;
+
+ &:hover {
+ background-color: $color-dark;
+ color: #FFF;
+ text-decoration: none;
}
}
}
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 4c35622fff1..a01e2a907d7 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -38,11 +38,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:twitter_sharing_enabled,
:sign_in_text,
:home_page_url,
+ :after_sign_out_path,
:max_attachment_size,
:default_project_visibility,
:default_snippet_visibility,
:restricted_signup_domains_raw,
:version_check_enabled,
+ :user_oauth_applications,
restricted_visibility_levels: [],
)
end
diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb
index c301e61d1c7..285e8495342 100644
--- a/app/controllers/admin/deploy_keys_controller.rb
+++ b/app/controllers/admin/deploy_keys_controller.rb
@@ -1,13 +1,8 @@
class Admin::DeployKeysController < Admin::ApplicationController
before_action :deploy_keys, only: [:index]
- before_action :deploy_key, only: [:show, :destroy]
+ before_action :deploy_key, only: [:destroy]
def index
-
- end
-
- def show
-
end
def new
diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb
index 2dfae13ac5c..4d3e48f7f81 100644
--- a/app/controllers/admin/groups_controller.rb
+++ b/app/controllers/admin/groups_controller.rb
@@ -47,7 +47,7 @@ class Admin::GroupsController < Admin::ApplicationController
end
def destroy
- @group.destroy
+ DestroyGroupService.new(@group, current_user).execute
redirect_to admin_groups_path, notice: 'Group was successfully deleted.'
end
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index e5da94b2327..62d46a5482e 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -89,7 +89,7 @@ class ApplicationController < ActionController::Base
end
def after_sign_out_path_for(resource)
- new_user_session_path
+ current_application_settings.after_sign_out_path || new_user_session_path
end
def abilities
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index a11c554a2af..040255f08e6 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -66,7 +66,11 @@ class Groups::GroupMembersController < Groups::ApplicationController
@group_member.destroy
redirect_to(dashboard_groups_path, notice: "You left #{group.name} group.")
else
- return render_403
+ if @group.last_owner?(current_user)
+ redirect_to(dashboard_groups_path, alert: "You can not leave #{group.name} group because you're the last owner. Transfer or delete the group.")
+ else
+ return render_403
+ end
end
end
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 34f0b257db3..2e381822e42 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -82,7 +82,7 @@ class GroupsController < Groups::ApplicationController
end
def destroy
- @group.destroy
+ DestroyGroupService.new(@group, current_user).execute
redirect_to root_path, notice: 'Group was removed.'
end
diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb
index 507b8290a2b..fc31118124b 100644
--- a/app/controllers/oauth/applications_controller.rb
+++ b/app/controllers/oauth/applications_controller.rb
@@ -1,6 +1,8 @@
class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
+ include Gitlab::CurrentSettings
include PageLayoutHelper
+ before_action :verify_user_oauth_applications_enabled
before_action :authenticate_user!
layout 'profile'
@@ -32,6 +34,12 @@ class Oauth::ApplicationsController < Doorkeeper::ApplicationsController
private
+ def verify_user_oauth_applications_enabled
+ return if current_application_settings.user_oauth_applications?
+
+ redirect_to applications_profile_url
+ end
+
def set_application
@application = current_user.oauth_applications.find(params[:id])
end
diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb
index dcd949a71de..765adaf2128 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -1,4 +1,7 @@
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
+
+ protect_from_forgery except: [:kerberos, :saml]
+
Gitlab.config.omniauth.providers.each do |provider|
define_method provider['name'] do
handle_omniauth
@@ -21,7 +24,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
@user = Gitlab::LDAP::User.new(oauth)
@user.save if @user.changed? # will also save new users
gl_user = @user.gl_user
- gl_user.remember_me = true if @user.persisted?
+ gl_user.remember_me = params[:remember_me] if @user.persisted?
# Do additional LDAP checks for the user filter and EE features
if @user.allowed?
diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb
index b762518d377..100d3d3b317 100644
--- a/app/controllers/projects/blob_controller.rb
+++ b/app/controllers/projects/blob_controller.rb
@@ -13,27 +13,20 @@ class Projects::BlobController < Projects::ApplicationController
before_action :commit, except: [:new, :create]
before_action :blob, except: [:new, :create]
before_action :from_merge_request, only: [:edit, :update]
- before_action :after_edit_path, only: [:edit, :update]
before_action :require_branch_head, only: [:edit, :update]
+ before_action :editor_variables, except: [:show, :preview, :diff]
+ before_action :after_edit_path, only: [:edit, :update]
def new
commit unless @repository.empty?
end
def create
- file_path = File.join(@path, File.basename(params[:file_name]))
- result = Files::CreateService.new(
- @project,
- current_user,
- params.merge(new_branch: sanitized_new_branch_name),
- @ref,
- file_path
- ).execute
+ result = Files::CreateService.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
- ref = sanitized_new_branch_name.presence || @ref
- redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(ref, file_path))
+ redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @file_path))
else
flash[:alert] = result[:message]
render :new
@@ -48,22 +41,10 @@ class Projects::BlobController < Projects::ApplicationController
end
def update
- result = Files::UpdateService.
- new(
- @project,
- current_user,
- params.merge(new_branch: sanitized_new_branch_name),
- @ref,
- @path
- ).execute
+ result = Files::UpdateService.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
-
- if from_merge_request
- from_merge_request.reload_code
- end
-
redirect_to after_edit_path
else
flash[:alert] = result[:message]
@@ -80,12 +61,11 @@ class Projects::BlobController < Projects::ApplicationController
end
def destroy
- result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute
+ result = Files::DeleteService.new(@project, current_user, @commit_params).execute
if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed"
- redirect_to namespace_project_tree_path(@project.namespace, @project,
- @ref)
+ redirect_to namespace_project_tree_path(@project.namespace, @project, @target_branch)
else
flash[:alert] = result[:message]
render :show
@@ -135,7 +115,6 @@ class Projects::BlobController < Projects::ApplicationController
@id = params[:id]
@ref, @path = extract_ref(@id)
-
rescue InvalidPathError
not_found!
end
@@ -145,8 +124,8 @@ class Projects::BlobController < Projects::ApplicationController
if from_merge_request
diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) +
"#file-path-#{hexdigest(@path)}"
- elsif sanitized_new_branch_name.present?
- namespace_project_blob_path(@project.namespace, @project, File.join(sanitized_new_branch_name, @path))
+ elsif @target_branch.present?
+ namespace_project_blob_path(@project.namespace, @project, File.join(@target_branch, @path))
else
namespace_project_blob_path(@project.namespace, @project, @id)
end
@@ -160,4 +139,25 @@ class Projects::BlobController < Projects::ApplicationController
def sanitized_new_branch_name
@new_branch ||= sanitize(strip_tags(params[:new_branch]))
end
+
+ def editor_variables
+ @current_branch = @ref
+ @target_branch = (sanitized_new_branch_name || @ref)
+
+ @file_path =
+ if action_name.to_s == 'create'
+ File.join(@path, File.basename(params[:file_name]))
+ else
+ @path
+ end
+
+ @commit_params = {
+ file_path: @file_path,
+ current_branch: @current_branch,
+ target_branch: @target_branch,
+ commit_message: params[:commit_message],
+ file_content: params[:content],
+ file_content_encoding: params[:encoding]
+ }
+ end
end
diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb
index 8c1bbf76917..40e2b37912b 100644
--- a/app/controllers/projects/deploy_keys_controller.rb
+++ b/app/controllers/projects/deploy_keys_controller.rb
@@ -18,10 +18,6 @@ class Projects::DeployKeysController < Projects::ApplicationController
@available_public_keys -= @available_project_keys
end
- def show
- @key = @project.deploy_keys.find(params[:id])
- end
-
def new
@key = @project.deploy_keys.new
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index c7467e9b2f5..71d3051ab88 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -2,10 +2,13 @@ require 'gitlab/satellite/satellite'
class Projects::MergeRequestsController < Projects::ApplicationController
before_action :module_enabled
- before_action :merge_request, only: [:edit, :update, :show, :diffs, :automerge, :automerge_check, :ci_status, :toggle_subscription]
- before_action :closes_issues, only: [:edit, :update, :show, :diffs]
- before_action :validates_merge_request, only: [:show, :diffs]
- before_action :define_show_vars, only: [:show, :diffs]
+ before_action :merge_request, only: [
+ :edit, :update, :show, :diffs, :commits, :automerge, :automerge_check,
+ :ci_status, :toggle_subscription
+ ]
+ before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits]
+ before_action :validates_merge_request, only: [:show, :diffs, :commits]
+ before_action :define_show_vars, only: [:show, :diffs, :commits]
# Allow read any merge_request
before_action :authorize_read_merge_request!
@@ -27,7 +30,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_requests = @merge_requests.full_search(terms)
end
end
-
+
@merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE)
respond_to do |format|
@@ -67,6 +70,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
end
+ def commits
+ render 'show'
+ end
+
def new
params[:merge_request] ||= ActionController::Parameters.new(source_project: @project)
@merge_request = MergeRequests::BuildService.new(project, current_user, merge_request_params).execute
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index d7fbc979067..b82b6f45d59 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -2,8 +2,6 @@ class Projects::ProjectMembersController < Projects::ApplicationController
# Authorize
before_action :authorize_admin_project!, except: :leave
- layout "project_settings"
-
def index
@project_members = @project.project_members
@project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project)
@@ -73,10 +71,14 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def leave
+ if @project.namespace == current_user.namespace
+ return redirect_to(:back, alert: 'You can not leave your own project. Transfer or delete the project.')
+ end
+
@project.project_members.find_by(user_id: current_user).destroy
respond_to do |format|
- format.html { redirect_to :back }
+ format.html { redirect_to dashboard_path }
format.js { render nothing: true }
end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index dc430351551..be5968cd7b0 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -97,18 +97,15 @@ class ProjectsController < ApplicationController
return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).execute
+ flash[:alert] = 'Project deleted.'
- respond_to do |format|
- format.html do
- flash[:alert] = 'Project deleted.'
-
- if request.referer.include?('/admin')
- redirect_to admin_namespaces_projects_path
- else
- redirect_to dashboard_path
- end
- end
+ if request.referer.include?('/admin')
+ redirect_to admin_namespaces_projects_path
+ else
+ redirect_to dashboard_path
end
+ rescue Projects::DestroyService::DestroyError => ex
+ redirect_to edit_project_path(@project), alert: ex.message
end
def autocomplete_sources
@@ -154,7 +151,17 @@ class ProjectsController < ApplicationController
end
def markdown_preview
- render text: view_context.markdown(params[:md_text])
+ text = params[:text]
+
+ ext = Gitlab::ReferenceExtractor.new(@project, current_user)
+ ext.analyze(text)
+
+ render json: {
+ body: view_context.markdown(text),
+ references: {
+ users: ext.users.map(&:username)
+ }
+ }
end
private
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index b89b4c27350..4d976fe6630 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -2,6 +2,7 @@ class SessionsController < Devise::SessionsController
include AuthenticatesWithTwoFactor
prepend_before_action :authenticate_with_two_factor, only: [:create]
+ before_action :auto_sign_in_with_provider, only: [:new]
def new
redirect_path =
@@ -75,6 +76,21 @@ class SessionsController < Devise::SessionsController
end
end
+ def auto_sign_in_with_provider
+ provider = Gitlab.config.omniauth.auto_sign_in_with_provider
+ return unless provider.present?
+
+ # Auto sign in with an Omniauth provider only if the standard "you need to sign-in" alert is
+ # registered or no alert at all. In case of another alert (such as a blocked user), it is safer
+ # to do nothing to prevent redirection loops with certain Omniauth providers.
+ return unless flash[:alert].blank? || flash[:alert] == I18n.t('devise.failure.unauthenticated')
+
+ # Prevent alert from popping up on the first page shown after authentication.
+ flash[:alert] = nil
+
+ redirect_to omniauth_authorize_path(:user, provider.to_sym)
+ end
+
def valid_otp_attempt?(user)
user.valid_otp?(user_params[:otp_attempt]) ||
user.invalidate_otp_backup_code!(user_params[:otp_attempt])
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 89dcdf57798..a539ec49f7a 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -279,10 +279,6 @@ module ApplicationHelper
html_options
end
- def escaped_autolink(text)
- auto_link ERB::Util.html_escape(text), link: :urls
- end
-
def promo_host
'about.gitlab.com'
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 241d6075c9f..63c3ff5674d 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -19,6 +19,10 @@ module ApplicationSettingsHelper
current_application_settings.sign_in_text
end
+ def user_oauth_applications?
+ current_application_settings.user_oauth_applications
+ end
+
# Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect.
def restricted_level_checkboxes(help_block_id)
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index d89f7b4a28d..2777944fc9d 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -1,3 +1,5 @@
+require 'nokogiri'
+
module GitlabMarkdownHelper
include Gitlab::Markdown
@@ -21,36 +23,44 @@ module GitlabMarkdownHelper
gfm_body = gfm(escaped_body, {}, html_options)
- gfm_body.gsub!(%r{<a.*?>.*?</a>}m) do |match|
- "</a>#{match}#{link_to("", url, html_options)[0..-5]}" # "</a>".length +1
+ fragment = Nokogiri::XML::DocumentFragment.parse(gfm_body)
+ if fragment.children.size == 1 && fragment.children[0].name == 'a'
+ # Fragment has only one node, and it's a link generated by `gfm`.
+ # Replace it with our requested link.
+ text = fragment.children[0].text
+ fragment.children[0].replace(link_to(text, url, html_options))
+ else
+ # Traverse the fragment's first generation of children looking for pure
+ # text, wrapping anything found in the requested link
+ fragment.children.each do |node|
+ next unless node.text?
+ node.replace(link_to(node.text, url, html_options))
+ end
end
- link_to(gfm_body.html_safe, url, html_options)
+ fragment.to_html.html_safe
end
+ MARKDOWN_OPTIONS = {
+ no_intra_emphasis: true,
+ tables: true,
+ fenced_code_blocks: true,
+ strikethrough: true,
+ lax_spacing: true,
+ space_after_headers: true,
+ superscript: true,
+ footnotes: true
+ }.freeze
+
def markdown(text, options={})
unless @markdown && options == @options
@options = options
- options.merge!(
- # Handled further down the line by Gitlab::Markdown::SanitizationFilter
- escape_html: false
- )
-
# see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch
rend = Redcarpet::Render::GitlabHTML.new(self, user_color_scheme_class, options)
# see https://github.com/vmg/redcarpet#and-its-like-really-simple-to-use
- @markdown = Redcarpet::Markdown.new(rend,
- no_intra_emphasis: true,
- tables: true,
- fenced_code_blocks: true,
- strikethrough: true,
- lax_spacing: true,
- space_after_headers: true,
- superscript: true,
- footnotes: true
- )
+ @markdown = Redcarpet::Markdown.new(rend, MARKDOWN_OPTIONS)
end
@markdown.render(text).html_safe
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index f8df39d236a..94ce6646634 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -294,4 +294,16 @@ module ProjectsHelper
nil
end
end
+
+ def user_max_access_in_project(user, project)
+ level = project.team.max_member_access(user)
+
+ if level
+ Gitlab::Access.options_with_owner.key(level)
+ end
+ end
+
+ def leave_project_message(project)
+ "Are you sure you want to leave \"#{project.name}\" project?"
+ end
end
diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb
index a1d263d9d3a..77727337f07 100644
--- a/app/helpers/tab_helper.rb
+++ b/app/helpers/tab_helper.rb
@@ -89,7 +89,7 @@ module TabHelper
def project_tab_class
return "active" if current_page?(controller: "/projects", action: :edit, id: @project)
- if ['services', 'hooks', 'deploy_keys', 'project_members', 'protected_branches'].include? controller.controller_name
+ if ['services', 'hooks', 'deploy_keys', 'protected_branches'].include? controller.controller_name
"active"
end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index d5123249c53..80463ee8841 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -18,6 +18,8 @@
# default_project_visibility :integer
# default_snippet_visibility :integer
# restricted_signup_domains :text
+# user_oauth_applications :bool default(TRUE)
+# after_sign_out_path :string(255)
#
class ApplicationSetting < ActiveRecord::Base
@@ -30,6 +32,10 @@ class ApplicationSetting < ActiveRecord::Base
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" },
if: :home_page_url_column_exist
+ validates :after_sign_out_path,
+ allow_blank: true,
+ format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }
+
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
diff --git a/app/models/commit.rb b/app/models/commit.rb
index f02fe240540..9d721661629 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -172,10 +172,8 @@ class Commit
@raw.send(m, *args, &block)
end
- def respond_to?(method)
- return true if @raw.respond_to?(method)
-
- super
+ def respond_to_missing?(method, include_private = false)
+ @raw.respond_to?(method, include_private) || super
end
# Truncate sha to 8 characters
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 6f9f54d08cc..10c39cb1ece 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -67,7 +67,13 @@ module Mentionable
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
def create_cross_references!(p = project, a = author, without = [])
- refs = references(p) - without
+ refs = references(p)
+
+ # We're using this method instead of Array diffing because that requires
+ # both of the object's `hash` values to be the same, which may not be the
+ # case for otherwise identical Commit objects.
+ refs.reject! { |ref| without.include?(ref) }
+
refs.each do |ref|
Note.create_cross_reference_note(ref, local_reference, a)
end
diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb
index 33b4814d7ec..660e58b876d 100644
--- a/app/models/concerns/taskable.rb
+++ b/app/models/concerns/taskable.rb
@@ -1,4 +1,5 @@
require 'task_list'
+require 'task_list/filter'
# Contains functionality for objects that can have task lists in their
# descriptions. Task list items can be added with Markdown like "* [x] Fix
diff --git a/app/models/group.rb b/app/models/group.rb
index b4e908c5602..051c672cb33 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -101,10 +101,14 @@ class Group < Namespace
end
def post_create_hook
+ Gitlab::AppLogger.info("Group \"#{name}\" was created")
+
system_hook_service.execute_hooks_for(self, :create)
end
def post_destroy_hook
+ Gitlab::AppLogger.info("Group \"#{name}\" was removed")
+
system_hook_service.execute_hooks_for(self, :destroy)
end
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 5690c375b96..f1f9f23b12c 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -197,7 +197,6 @@ class MergeRequest < ActiveRecord::Base
def update_merge_request_diff
if source_branch_changed? || target_branch_changed?
reload_code
- mark_as_unchecked
end
end
diff --git a/app/models/namespace.rb b/app/models/namespace.rb
index 211dfa76b81..03d2ab165ea 100644
--- a/app/models/namespace.rb
+++ b/app/models/namespace.rb
@@ -72,7 +72,7 @@ class Namespace < ActiveRecord::Base
path.gsub!(/[^a-zA-Z0-9_\-\.]/, "")
# Users with the great usernames of "." or ".." would end up with a blank username.
- # Work around that by setting their username to "blank", followed by a counter.
+ # Work around that by setting their username to "blank", followed by a counter.
path = "blank" if path.blank?
counter = 0
@@ -99,7 +99,18 @@ class Namespace < ActiveRecord::Base
end
def rm_dir
- gitlab_shell.rm_namespace(path)
+ # Move namespace directory into trash.
+ # We will remove it later async
+ new_path = "#{path}+#{id}+deleted"
+
+ if gitlab_shell.mv_namespace(path, new_path)
+ message = "Namespace directory \"#{path}\" moved to \"#{new_path}\""
+ Gitlab::AppLogger.info message
+
+ # Remove namespace directroy async with delay so
+ # GitLab has time to remove all projects first
+ GitlabShellWorker.perform_in(5.minutes, :rm_namespace, new_path)
+ end
end
def move_dir
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index 949a4d7111b..a9354754686 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -40,6 +40,12 @@ class GitlabCiService < CiService
def execute(data)
return unless supported_events.include?(data[:object_kind])
+ ci_yaml_file = ci_yaml_file(data)
+
+ if ci_yaml_file
+ data.merge!(ci_yaml_file: ci_yaml_file)
+ end
+
service_hook.execute(data)
end
@@ -123,6 +129,14 @@ class GitlabCiService < CiService
private
+ def ci_yaml_file(data)
+ ref = data[:checkout_sha]
+ repo = project.repository
+ commit = repo.commit(ref)
+ blob = Gitlab::Git::Blob.find(repo, commit.id, ".gitlab-ci.yml")
+ blob && blob.data
+ end
+
def fork_registration_path
project_url.sub(/projects\/\d*/, "#{API_PREFIX}/forks")
end
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 1b8c74028d9..2c6347222aa 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -163,10 +163,8 @@ class Repository
end
end
- def respond_to?(method)
- return true if raw_repository.respond_to?(method)
-
- super
+ def respond_to_missing?(method, include_private = false)
+ raw_repository.respond_to?(method, include_private) || super
end
def blob_at(sha, path)
@@ -370,8 +368,55 @@ class Repository
@root_ref ||= raw_repository.root_ref
end
+ def commit_file(user, path, content, message, ref)
+ path[0] = '' if path[0] == '/'
+
+ committer = user_to_comitter(user)
+ options = {}
+ options[:committer] = committer
+ options[:author] = committer
+ options[:commit] = {
+ message: message,
+ branch: ref
+ }
+
+ options[:file] = {
+ content: content,
+ path: path
+ }
+
+ Gitlab::Git::Blob.commit(raw_repository, options)
+ end
+
+ def remove_file(user, path, message, ref)
+ path[0] = '' if path[0] == '/'
+
+ committer = user_to_comitter(user)
+ options = {}
+ options[:committer] = committer
+ options[:author] = committer
+ options[:commit] = {
+ message: message,
+ branch: ref
+ }
+
+ options[:file] = {
+ path: path
+ }
+
+ Gitlab::Git::Blob.remove(raw_repository, options)
+ end
+
private
+ def user_to_comitter(user)
+ {
+ email: user.email,
+ name: user.name,
+ time: Time.now
+ }
+ end
+
def cache
@cache ||= RepositoryCache.new(path_with_namespace)
end
diff --git a/app/models/user.rb b/app/models/user.rb
index c1bb51e86fc..596dc7ea33a 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -483,7 +483,7 @@ class User < ActiveRecord::Base
end
def sanitize_attrs
- %w(name username skype linkedin twitter bio).each do |attr|
+ %w(name username skype linkedin twitter).each do |attr|
value = self.send(attr)
self.send("#{attr}=", Sanitize.clean(value)) if value.present?
end
@@ -655,6 +655,12 @@ class User < ActiveRecord::Base
end
end
+ def namespaces
+ namespace_ids = groups.pluck(:id)
+ namespace_ids.push(namespace.id)
+ Namespace.where(id: namespace_ids)
+ end
+
def oauth_authorized_tokens
Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil)
end
diff --git a/app/services/delete_user_service.rb b/app/services/delete_user_service.rb
index d259b4efca6..9017a63af3b 100644
--- a/app/services/delete_user_service.rb
+++ b/app/services/delete_user_service.rb
@@ -4,6 +4,12 @@ class DeleteUserService
user.errors[:base] << 'You must transfer ownership or delete groups before you can remove user'
user
else
+ user.personal_projects.each do |project|
+ # Skip repository removal because we remove directory with namespace
+ # that contain all this repositories
+ ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
+ end
+
user.destroy
end
end
diff --git a/app/services/destroy_group_service.rb b/app/services/destroy_group_service.rb
new file mode 100644
index 00000000000..d929a676293
--- /dev/null
+++ b/app/services/destroy_group_service.rb
@@ -0,0 +1,17 @@
+class DestroyGroupService
+ attr_accessor :group, :current_user
+
+ def initialize(group, user)
+ @group, @current_user = group, user
+ end
+
+ def execute
+ @group.projects.each do |project|
+ # Skip repository removal because we remove directory with namespace
+ # that contain all this repositories
+ ::Projects::DestroyService.new(project, current_user, skip_repo: true).execute
+ end
+
+ @group.destroy
+ end
+end
diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb
index bd245100955..f587ee266da 100644
--- a/app/services/files/base_service.rb
+++ b/app/services/files/base_service.rb
@@ -1,11 +1,34 @@
module Files
class BaseService < ::BaseService
- attr_reader :ref, :path
+ class ValidationError < StandardError; end
- def initialize(project, user, params, ref, path = nil)
- @project, @current_user, @params = project, user, params.dup
- @ref = ref
- @path = path
+ def execute
+ @current_branch = params[:current_branch]
+ @target_branch = params[:target_branch]
+ @commit_message = params[:commit_message]
+ @file_path = params[:file_path]
+ @file_content = if params[:file_content_encoding] == 'base64'
+ Base64.decode64(params[:file_content])
+ else
+ params[:file_content]
+ end
+
+ # Validate parameters
+ validate
+
+ # Create new branch if it different from current_branch
+ if @target_branch != @current_branch
+ create_target_branch
+ end
+
+ if sha = commit
+ after_commit(sha, @target_branch)
+ success
+ else
+ error("Something went wrong. Your changes were not committed")
+ end
+ rescue ValidationError => ex
+ error(ex.message)
end
private
@@ -13,5 +36,52 @@ module Files
def repository
project.repository
end
+
+ def after_commit(sha, branch)
+ commit = repository.commit(sha)
+ full_ref = 'refs/heads/' + branch
+ old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA
+ GitPushService.new.execute(project, current_user, old_sha, sha, full_ref)
+ end
+
+ def current_branch
+ @current_branch ||= params[:current_branch]
+ end
+
+ def target_branch
+ @target_branch ||= params[:target_branch]
+ end
+
+ def raise_error(message)
+ raise ValidationError.new(message)
+ end
+
+ def validate
+ allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(@target_branch)
+
+ unless allowed
+ raise_error("You are not allowed to push into this branch")
+ end
+
+ unless project.empty_repo?
+ unless repository.branch_names.include?(@current_branch)
+ raise_error("You can only create files if you are on top of a branch")
+ end
+
+ if @current_branch != @target_branch
+ if repository.branch_names.include?(@target_branch)
+ raise_error("Branch with such name already exists. You need to switch to this branch in order to make changes")
+ end
+ end
+ end
+ end
+
+ def create_target_branch
+ result = CreateBranchService.new(project, current_user).execute(@target_branch, @current_branch)
+
+ unless result[:status] == :success
+ raise_error("Something went wrong when we tried to create #{@target_branch} for you")
+ end
+ end
end
end
diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb
index 23833aa78ec..91d715b2d63 100644
--- a/app/services/files/create_service.rb
+++ b/app/services/files/create_service.rb
@@ -1,52 +1,30 @@
require_relative "base_service"
module Files
- class CreateService < BaseService
- def execute
- allowed = Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
+ class CreateService < Files::BaseService
+ def commit
+ repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
+ end
- unless allowed
- return error("You are not allowed to create file in this branch")
- end
+ def validate
+ super
- file_name = File.basename(path)
- file_path = path
+ file_name = File.basename(@file_path)
unless file_name =~ Gitlab::Regex.file_name_regex
- return error(
+ raise_error(
'Your changes could not be committed, because the file name ' +
Gitlab::Regex.file_name_regex_message
)
end
- if project.empty_repo?
- # everything is ok because repo does not have a commits yet
- else
- unless repository.branch_names.include?(ref)
- return error("You can only create files if you are on top of a branch")
- end
-
- blob = repository.blob_at_branch(ref, file_path)
+ unless project.empty_repo?
+ blob = repository.blob_at_branch(@current_branch, @file_path)
if blob
- return error("Your changes could not be committed, because file with such name exists")
+ raise_error("Your changes could not be committed, because file with such name exists")
end
end
-
-
- new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path)
- created_successfully = new_file_action.commit!(
- params[:content],
- params[:commit_message],
- params[:encoding],
- params[:new_branch]
- )
-
- if created_successfully
- success
- else
- error("Your changes could not be committed, because the file has been changed")
- end
end
end
end
diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb
index 1497a0f883b..27c881c3430 100644
--- a/app/services/files/delete_service.rb
+++ b/app/services/files/delete_service.rb
@@ -1,36 +1,9 @@
require_relative "base_service"
module Files
- class DeleteService < BaseService
- def execute
- allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
-
- unless allowed
- return error("You are not allowed to push into this branch")
- end
-
- unless repository.branch_names.include?(ref)
- return error("You can only create files if you are on top of a branch")
- end
-
- blob = repository.blob_at_branch(ref, path)
-
- unless blob
- return error("You can only edit text files")
- end
-
- delete_file_action = Gitlab::Satellite::DeleteFileAction.new(current_user, project, ref, path)
-
- deleted_successfully = delete_file_action.commit!(
- nil,
- params[:commit_message]
- )
-
- if deleted_successfully
- success
- else
- error("Your changes could not be committed, because the file has been changed")
- end
+ class DeleteService < Files::BaseService
+ def commit
+ repository.remove_file(current_user, @file_path, @commit_message, @target_branch)
end
end
end
diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb
index 0724d3ae634..a20903c6f02 100644
--- a/app/services/files/update_service.rb
+++ b/app/services/files/update_service.rb
@@ -1,39 +1,9 @@
require_relative "base_service"
module Files
- class UpdateService < BaseService
- def execute
- allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref)
-
- unless allowed
- return error("You are not allowed to push into this branch")
- end
-
- unless repository.branch_names.include?(ref)
- return error("You can only create files if you are on top of a branch")
- end
-
- blob = repository.blob_at_branch(ref, path)
-
- unless blob
- return error("You can only edit text files")
- end
-
- edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path)
- edit_file_action.commit!(
- params[:content],
- params[:commit_message],
- params[:encoding],
- params[:new_branch]
- )
-
- success
- rescue Gitlab::Satellite::CheckoutFailed => ex
- error("Your changes could not be committed because ref '#{ref}' could not be checked out", 400)
- rescue Gitlab::Satellite::CommitFailed => ex
- error("Your changes could not be committed. Maybe there was nothing to commit?", 409)
- rescue Gitlab::Satellite::PushFailed => ex
- error("Your changes could not be committed. Maybe the file was changed by another process?", 409)
+ class UpdateService < Files::BaseService
+ def commit
+ repository.commit_file(current_user, @file_path, @file_content, @commit_message, @target_branch)
end
end
end
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index bdf36af02fd..cde65349d5c 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -127,7 +127,8 @@ class GitPushService
end
def is_default_branch?(ref)
- Gitlab::Git.branch_ref?(ref) && Gitlab::Git.ref_name(ref) == project.default_branch
+ Gitlab::Git.branch_ref?(ref) &&
+ (Gitlab::Git.ref_name(ref) == project.default_branch || project.default_branch.nil?)
end
def commit_user(commit)
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index c5769a5ad27..1d99223cfe6 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -20,4 +20,10 @@ class IssuableBaseService < BaseService
SystemNoteService.change_title(
issuable, issuable.project, current_user, old_title)
end
+
+ def create_branch_change_note(issuable, branch_type, old_branch, new_branch)
+ SystemNoteService.change_branch(
+ issuable, issuable.project, current_user, branch_type,
+ old_branch, new_branch)
+ end
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 34fd59d6927..4f6c6cba9a9 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -5,10 +5,11 @@ require_relative 'close_service'
module MergeRequests
class UpdateService < MergeRequests::BaseService
def execute(merge_request)
- # We dont allow change of source/target projects
+ # We don't allow change of source/target projects and source branch
# after merge request was created
params.except!(:source_project_id)
params.except!(:target_project_id)
+ params.except!(:source_branch)
state = params[:state_event]
@@ -41,6 +42,12 @@ module MergeRequests
)
end
+ if merge_request.previous_changes.include?('target_branch')
+ create_branch_change_note(merge_request, 'target',
+ merge_request.previous_changes['target_branch'].first,
+ merge_request.target_branch)
+ end
+
if merge_request.previous_changes.include?('milestone_id')
create_milestone_note(merge_request)
end
@@ -54,6 +61,11 @@ module MergeRequests
create_title_change_note(merge_request, merge_request.previous_changes['title'].first)
end
+ if merge_request.previous_changes.include?('target_branch') ||
+ merge_request.previous_changes.include?('source_branch')
+ merge_request.mark_as_unchecked
+ end
+
merge_request.notice_added_references(merge_request.project, current_user)
execute_hooks(merge_request, 'update')
end
diff --git a/app/services/projects/destroy_service.rb b/app/services/projects/destroy_service.rb
index 7e1d753b021..403f419ec50 100644
--- a/app/services/projects/destroy_service.rb
+++ b/app/services/projects/destroy_service.rb
@@ -1,28 +1,69 @@
module Projects
class DestroyService < BaseService
+ include Gitlab::ShellAdapter
+
+ class DestroyError < StandardError; end
+
+ DELETED_FLAG = '+deleted'
+
def execute
return false unless can?(current_user, :remove_project, project)
project.team.truncate
project.repository.expire_cache unless project.empty_repo?
- if project.destroy
- GitlabShellWorker.perform_async(
- :remove_repository,
- project.path_with_namespace
- )
+ repo_path = project.path_with_namespace
+ wiki_path = repo_path + '.wiki'
- GitlabShellWorker.perform_async(
- :remove_repository,
- project.path_with_namespace + ".wiki"
- )
+ Project.transaction do
+ project.destroy!
- project.satellite.destroy
+ unless remove_repository(repo_path)
+ raise_error('Failed to remove project repository. Please try again or contact administrator')
+ end
- log_info("Project \"#{project.name}\" was removed")
- system_hook_service.execute_hooks_for(project, :destroy)
- true
+ unless remove_repository(wiki_path)
+ raise_error('Failed to remove wiki repository. Please try again or contact administrator')
+ end
end
+
+ project.satellite.destroy
+ log_info("Project \"#{project.name}\" was removed")
+ system_hook_service.execute_hooks_for(project, :destroy)
+ true
+ end
+
+ private
+
+ def remove_repository(path)
+ # Skip repository removal. We use this flag when remove user or group
+ return true if params[:skip_repo] == true
+
+ # There is a possibility project does not have repository or wiki
+ return true unless gitlab_shell.exists?(path + '.git')
+
+ new_path = removal_path(path)
+
+ if gitlab_shell.mv_repository(path, new_path)
+ log_info("Repository \"#{path}\" moved to \"#{new_path}\"")
+ GitlabShellWorker.perform_in(5.minutes, :remove_repository, new_path)
+ else
+ false
+ end
+ end
+
+ def raise_error(message)
+ raise DestroyError.new(message)
+ end
+
+ # Build a path for removing repositories
+ # We use `+` because its not allowed by GitLab so user can not create
+ # project with name cookies+119+deleted and capture someone stalled repository
+ #
+ # gitlab/cookies.git -> gitlab/cookies+119+deleted.git
+ #
+ def removal_path(path)
+ "#{path}+#{project.id}#{DELETED_FLAG}"
end
end
end
diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb
index b91590a1a90..0004a399f47 100644
--- a/app/services/projects/participants_service.rb
+++ b/app/services/projects/participants_service.rb
@@ -38,13 +38,13 @@ module Projects
def groups
current_user.authorized_groups.sort_by(&:path).map do |group|
count = group.users.count
- { username: group.path, name: "#{group.name} (#{count})" }
+ { username: group.path, name: group.name, count: count }
end
end
def all_members
count = project.team.members.flatten.count
- [{ username: "all", name: "All Project and Group Members (#{count})" }]
+ [{ username: "all", name: "All Project and Group Members", count: count }]
end
end
end
diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb
index 1527ae0486d..b6801a92330 100644
--- a/app/services/system_note_service.rb
+++ b/app/services/system_note_service.rb
@@ -149,6 +149,25 @@ class SystemNoteService
create_note(noteable: noteable, project: project, author: author, note: body)
end
+ # Called when a branch in Noteable is changed
+ #
+ # noteable - Noteable object
+ # project - Project owning noteable
+ # author - User performing the change
+ # branch_type - 'source' or 'target'
+ # old_branch - old branch name
+ # new_branch - new branch nmae
+ #
+ # Example Note text:
+ #
+ # "Target branch changed from `Old` to `New`"
+ #
+ # Returns the created Note object
+ def self.change_branch(noteable, project, author, branch_type, old_branch, new_branch)
+ body = "#{branch_type} branch changed from `#{old_branch}` to `#{new_branch}`".capitalize
+ create_note(noteable: noteable, project: project, author: author, note: body)
+ end
+
# Called when a Mentionable references a Noteable
#
# noteable - Noteable object being referenced
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 4ceae814805..188a08940ab 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -30,7 +30,7 @@
.checkbox
= f.label :twitter_sharing_enabled do
= f.check_box :twitter_sharing_enabled, :'aria-describedby' => 'twitter_help_block'
- %strong Twitter enabled
+ Twitter enabled
%span.help-block#twitter_help_block Show users a button to share their newly created public or internal projects on twitter
.form-group
.col-sm-offset-2.col-sm-10
@@ -70,6 +70,11 @@
= f.text_field :home_page_url, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'home_help_block'
%span.help-block#home_help_block We will redirect non-logged in users to this page
.form-group
+ = f.label :after_sign_out_path, class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.text_field :after_sign_out_path, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'after_sign_out_path_help_block'
+ %span.help-block#after_sign_out_path_help_block We will redirect users to this page after they sign out
+ .form-group
= f.label :sign_in_text, class: 'control-label col-sm-2'
.col-sm-10
= f.text_area :sign_in_text, class: 'form-control', rows: 4
@@ -83,6 +88,13 @@
.col-sm-10
= f.text_area :restricted_signup_domains_raw, placeholder: 'domain.com', class: 'form-control'
.help-block Only users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
+ .form_group
+ = f.label :user_oauth_applications, 'User OAuth applications', class: 'control-label col-sm-2'
+ .col-sm-10
+ .checkbox
+ = f.label :user_oauth_applications do
+ = f.check_box :user_oauth_applications
+ Allow users to register any application to use GitLab as an OAuth provider
.form-actions
= f.submit 'Save', class: 'btn btn-primary'
diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml
index 267c9a52921..17dffebd360 100644
--- a/app/views/admin/broadcast_messages/index.html.haml
+++ b/app/views/admin/broadcast_messages/index.html.haml
@@ -22,11 +22,11 @@
.form-group.js-toggle-colors-container.hide
= f.label :color, "Background Color", class: 'control-label'
.col-sm-10
- = f.color_field :color, value: "#AA33EE", class: "form-control"
+ = f.color_field :color, value: "#eb9532", class: "form-control"
.form-group.js-toggle-colors-container.hide
= f.label :font, "Font Color", class: 'control-label'
.col-sm-10
- = f.color_field :font, value: "#224466", class: "form-control"
+ = f.color_field :font, value: "#FFFFFF", class: "form-control"
.form-group
= f.label :starts_at, class: 'control-label'
.col-sm-10.datetime-controls
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 367d25cd6a1..6405a69fad3 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -19,8 +19,7 @@
= link_to admin_deploy_key_path(deploy_key) do
%strong= deploy_key.title
%td
- %span
- (#{deploy_key.fingerprint})
+ %code.key-fingerprint= deploy_key.fingerprint
%td
%span.cgray
added #{time_ago_with_tooltip(deploy_key.created_at)}
diff --git a/app/views/admin/deploy_keys/show.html.haml b/app/views/admin/deploy_keys/show.html.haml
deleted file mode 100644
index ea361ca4bdb..00000000000
--- a/app/views/admin/deploy_keys/show.html.haml
+++ /dev/null
@@ -1,35 +0,0 @@
-- page_title @deploy_key.title, "Deploy Keys"
-.row
- .col-md-4
- .panel.panel-default
- .panel-heading
- Deploy Key
- %ul.well-list
- %li
- %span.light Title:
- %strong= @deploy_key.title
- %li
- %span.light Created on:
- %strong= @deploy_key.created_at.stamp("Aug 21, 2011")
-
- .panel.panel-default
- .panel-heading Projects (#{@deploy_key.deploy_keys_projects.count})
- - if @deploy_key.deploy_keys_projects.any?
- %ul.well-list
- - @deploy_key.projects.each do |project|
- %li
- %span
- %strong
- = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project]
- .pull-right
- = link_to disable_namespace_project_deploy_key_path(project.namespace, project, @deploy_key), data: { confirm: "Are you sure?" }, method: :put, class: "btn-xs btn btn-remove", title: 'Remove deploy key from project' do
- %i.fa.fa-times.fa-inverse
-
- .col-md-8
- %p
- %span.light Fingerprint:
- %strong= @deploy_key.fingerprint
- %pre.well-pre
- = @deploy_key.key
- .pull-right
- = link_to 'Remove', admin_deploy_key_path(@deploy_key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key"
diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml
index e00b23ad99f..5ce7cdf2f8d 100644
--- a/app/views/admin/groups/index.html.haml
+++ b/app/views/admin/groups/index.html.haml
@@ -11,7 +11,7 @@
= form_tag admin_groups_path, method: :get, class: 'form-inline' do
= hidden_field_tag :sort, @sort
.form-group
- = text_field_tag :name, params[:name], class: "form-control input-mn-300"
+ = text_field_tag :name, params[:name], class: "form-control"
= button_tag "Search", class: "btn submit btn-primary"
.pull-right
diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml
index 5ecd53cff84..0a354373b9b 100644
--- a/app/views/dashboard/groups/index.html.haml
+++ b/app/views/dashboard/groups/index.html.haml
@@ -2,7 +2,7 @@
%h3.page-title
Group Membership
- if current_user.can_create_group?
- %span.pull-right
+ %span.pull-right.hidden-xs
= link_to new_group_path, class: "btn btn-new" do
%i.fa.fa-plus
New Group
@@ -17,18 +17,17 @@
- @group_members.each do |group_member|
- group = group_member.group
%li
- .pull-right
+ .pull-right.hidden-xs
- if can?(current_user, :admin_group, group)
= link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do
%i.fa.fa-cogs
Settings
- - if can?(current_user, :destroy_group_member, group_member)
- = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Remove user from group' do
- %i.fa.fa-sign-out
- Leave
+ = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do
+ %i.fa.fa-sign-out
+ Leave
- = image_tag group_icon(group), class: "avatar s40 avatar-tile"
+ = image_tag group_icon(group), class: "avatar s40 avatar-tile hidden-xs"
= link_to group, class: 'group-name' do
%strong= group.name
diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml
index 812e22373a7..6ec741e4882 100644
--- a/app/views/devise/sessions/_new_ldap.html.haml
+++ b/app/views/devise/sessions/_new_ldap.html.haml
@@ -1,4 +1,9 @@
= form_tag(user_omniauth_callback_path(server['provider_name']), id: 'new_ldap_user' ) do
= text_field_tag :username, nil, {class: "form-control top", placeholder: "#{server['label']} Login", autofocus: "autofocus"}
= password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"}
+ - if devise_mapping.rememberable?
+ .remember-me.checkbox
+ %label{for: "remember_me"}
+ = check_box_tag :remember_me, '1', false, id: 'remember_me'
+ %span Remember me
= button_tag "#{server['label']} Sign in", class: "btn-save btn"
diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml
index 1da702be384..34a7c00dc43 100644
--- a/app/views/events/event/_push.html.haml
+++ b/app/views/events/event/_push.html.haml
@@ -4,8 +4,8 @@
- if event.rm_ref?
%strong= event.ref_name
- else
- = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do
- %strong= event.ref_name
+ %strong
+ = link_to event.ref_name, namespace_project_commits_path(event.project.namespace, event.project, event.ref_name)
at
= link_to_project event.project
diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml
index c05d45e0100..f3f0b778539 100644
--- a/app/views/explore/groups/index.html.haml
+++ b/app/views/explore/groups/index.html.haml
@@ -4,7 +4,7 @@
= form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f|
= hidden_field_tag :sort, @sort
.form-group
- = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "groups_search"
+ = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "groups_search"
.form-group
= button_tag 'Search', class: "btn btn-primary wide"
diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml
index b3963a9d901..82622a58ed2 100644
--- a/app/views/explore/projects/_filter.html.haml
+++ b/app/views/explore/projects/_filter.html.haml
@@ -1,7 +1,7 @@
.pull-left
= form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f|
.form-group
- = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search"
+ = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search"
.form-group
= button_tag 'Search', class: "btn btn-primary wide"
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index 56b1948a474..ec39a755f0f 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -40,7 +40,8 @@
&nbsp;
- if current_user == user
= link_to leave_group_group_members_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
- %i.fa.fa-minus.fa-inverse
+ = icon("sign-out")
+ Leave
- else
= link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-minus.fa-inverse
diff --git a/app/views/groups/group_members/index.html.haml b/app/views/groups/group_members/index.html.haml
index 903ca877218..a70d1ff0697 100644
--- a/app/views/groups/group_members/index.html.haml
+++ b/app/views/groups/group_members/index.html.haml
@@ -14,7 +14,7 @@
.clearfix.js-toggle-container
= form_tag group_group_members_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' }
+ = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' }
= button_tag 'Search', class: 'btn'
- if current_user && current_user.can?(:admin_group, @group)
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index 1678311141e..0687840af39 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -11,7 +11,7 @@
@#{@group.path}
- if @group.description.present?
.description
- = escaped_autolink(@group.description)
+ = markdown(@group.description, pipeline: :description)
%hr
= render 'shared/show_aside'
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index b1a57d9824e..dbc68c39bf1 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -15,7 +15,7 @@
%meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'}
%meta{name: 'theme-color', content: '#474D57'}
- = yield(:meta_tags)
+ = yield :meta_tags
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
= render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id')
diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml
deleted file mode 100644
index 979755db652..00000000000
--- a/app/views/layouts/_head_panel.html.haml
+++ /dev/null
@@ -1,42 +0,0 @@
-%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
- .container
- %div.app_logo
- = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
- = brand_header_logo
- %h3 GitLab
- %h1.title
- = title
-
- %button.navbar-toggle{type: 'button', data: {target: '.navbar-collapse', toggle: 'collapse'}}
- %span.sr-only Toggle navigation
- = icon('bars')
-
- .navbar-collapse.collapse
- %ul.nav.navbar-nav
- %li.hidden-sm.hidden-xs
- = render 'layouts/search'
- %li.visible-sm.visible-xs
- = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('search')
- %li
- = link_to help_path, title: 'Help', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('question-circle')
- %li
- = link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('globe')
- %li
- = link_to user_snippets_path(current_user), title: 'Your snippets', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('clipboard')
- - if current_user.is_admin?
- %li
- = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('wrench')
- - if current_user.can_create_project?
- %li
- = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('plus')
- %li
- = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do
- = icon('cog')
-
-= render 'shared/outdated_browser'
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index c1283734d25..f17f6fdd91c 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -8,19 +8,13 @@
.collapse-nav
= render partial: 'layouts/collapse_button'
- if current_user
- .sidebar-user
- = link_to current_user, class: 'profile-pic', id: 'profile-pic', data: {toggle: 'tooltip', placement: 'top'} do
- = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s32'
+ = link_to current_user, class: 'sidebar-user' do
+ = image_tag avatar_icon(current_user.email, 60), alt: 'User activity', class: 'avatar avatar s32'
.username
= current_user.username
- .logout-holder
- = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'top'} do
- = icon('sign-out')
.content-wrapper
.container-fluid
.content
= render "layouts/flash"
.clearfix
= yield
-
-= yield :embedded_scripts
diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml
deleted file mode 100644
index 8a297566d6c..00000000000
--- a/app/views/layouts/_public_head_panel.html.haml
+++ /dev/null
@@ -1,22 +0,0 @@
-%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
- .container
- %div.app_logo
- = link_to explore_root_path, class: "home" do
- = brand_header_logo
- %h3 GitLab
- %h1.title= title
-
- %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"}
- %span.sr-only Toggle navigation
- %i.fa.fa-bars
-
- - unless current_controller?('sessions')
- .pull-right.hidden-xs
- = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new append-right-10'
-
- .navbar-collapse.collapse
- %ul.nav.navbar-nav
- %li.visible-xs
- = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes')
-
-= render 'shared/outdated_browser'
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
index a97feeb1ecd..173033f7eab 100644
--- a/app/views/layouts/application.html.haml
+++ b/app/views/layouts/application.html.haml
@@ -2,9 +2,14 @@
%html{ lang: "en"}
= render "layouts/head"
%body{class: "#{app_theme}", :'data-page' => body_data_page}
+ / Ideally this would be inside the head, but turbolinks only evaluates page-specific JS in the body.
+ = yield :scripts_body_top
+
- if current_user
- = render "layouts/head_panel", title: header_title
+ = render "layouts/header/default", title: header_title
- else
- = render "layouts/public_head_panel", title: header_title
+ = render "layouts/header/public", title: header_title
= render 'layouts/page', sidebar: sidebar
+
+ = yield :scripts_body
diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml
index 5a59c9fd59a..d406f5764a7 100644
--- a/app/views/layouts/devise.html.haml
+++ b/app/views/layouts/devise.html.haml
@@ -2,7 +2,7 @@
%html{ lang: "en"}
= render "layouts/head"
%body.ui_mars.login-page.application
- = render "layouts/empty_head_panel"
+ = render "layouts/header/empty"
= render "layouts/broadcast"
.container.navless-container
.content
diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml
index aa0f3f0a819..2e3a2b16eb7 100644
--- a/app/views/layouts/errors.html.haml
+++ b/app/views/layouts/errors.html.haml
@@ -2,7 +2,7 @@
%html{ lang: "en"}
= render "layouts/head"
%body{class: "#{app_theme} application"}
- = render "layouts/head_panel", title: "" if current_user
+ = render "layouts/header/empty"
.container.navless-container
= render "layouts/flash"
.error-page
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
new file mode 100644
index 00000000000..8b4510d6516
--- /dev/null
+++ b/app/views/layouts/header/_default.html.haml
@@ -0,0 +1,46 @@
+%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
+ .container
+ .header-logo
+ = link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = brand_header_logo
+ %h3 GitLab
+ .header-content
+ %h1.title
+ = title
+
+ %button.navbar-toggle
+ %span.sr-only Toggle navigation
+ = icon('bars')
+
+ .navbar-collapse.collapse
+ %ul.nav.navbar-nav.pull-right
+ %li.hidden-sm.hidden-xs
+ = render 'layouts/search'
+ %li.visible-sm.visible-xs
+ = link_to search_path, title: 'Search', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('search')
+ %li.hidden-xs
+ = link_to help_path, title: 'Help', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('question-circle fw')
+ %li
+ = link_to explore_root_path, title: 'Explore', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('globe fw')
+ %li
+ = link_to user_snippets_path(current_user), title: 'Your snippets', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('clipboard fw')
+ - if current_user.is_admin?
+ %li
+ = link_to admin_root_path, title: 'Admin area', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('wrench fw')
+ - if current_user.can_create_project?
+ %li.hidden-xs
+ = link_to new_project_path, title: 'New project', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('plus fw')
+ %li
+ = link_to profile_path, title: 'Profile settings', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('cog fw')
+ %li
+ = link_to destroy_user_session_path, class: 'logout', method: :delete, title: 'Sign out', data: {toggle: 'tooltip', placement: 'bottom'} do
+ = icon('sign-out')
+
+= render 'shared/outdated_browser'
diff --git a/app/views/layouts/_empty_head_panel.html.haml b/app/views/layouts/header/_empty.html.haml
index 358caa3868b..a52a3c8f0ef 100644
--- a/app/views/layouts/_empty_head_panel.html.haml
+++ b/app/views/layouts/header/_empty.html.haml
@@ -1,4 +1,4 @@
-%header.navbar.navbar-fixed-top.navbar-gitlab
+%header.navbar.navbar-fixed-top.navbar-empty
.container
- %h4.center
+ .center-logo
= image_tag 'logo-white.png', width: 32, height: 32
diff --git a/app/views/layouts/header/_public.html.haml b/app/views/layouts/header/_public.html.haml
new file mode 100644
index 00000000000..6a031722aaa
--- /dev/null
+++ b/app/views/layouts/header/_public.html.haml
@@ -0,0 +1,14 @@
+%header.navbar.navbar-fixed-top.navbar-gitlab{ class: nav_header_class }
+ .container
+ .header-logo
+ = link_to explore_root_path, class: "home" do
+ = brand_header_logo
+ %h3 GitLab
+ .header-content
+ %h1.title= title
+
+ - unless current_controller?('sessions')
+ .pull-right
+ = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-success btn-sm'
+
+= render 'shared/outdated_browser'
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 172f5197b24..cbcf560d0af 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -56,6 +56,13 @@
Merge Requests
%span.count.merge_counter= @project.merge_requests.opened.count
+ - if project_nav_tab? :settings
+ = nav_link(controller: [:project_members, :teams]) do
+ = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab', data: {placement: 'right'} do
+ = icon('users fw')
+ %span
+ Members
+
- if project_nav_tab? :labels
= nav_link(controller: :labels) do
= link_to namespace_project_labels_path(@project.namespace, @project), title: 'Labels', data: {placement: 'right'} do
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 7dd14449def..633c6ae6bfb 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -10,32 +10,27 @@
%ul.project-settings-nav.sidebar-subnav
= nav_link(path: 'projects#edit') do
= link_to edit_project_path(@project), title: 'Project', class: 'stat-tab tab', data: {placement: 'right'} do
- = icon('pencil-square-o')
+ = icon('pencil-square-o fw')
%span
Project Settings
- = nav_link(controller: [:project_members, :teams]) do
- = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: 'team-tab tab', data: {placement: 'right'} do
- = icon('users')
- %span
- Members
= nav_link(controller: :deploy_keys) do
= link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys', data: {placement: 'right'} do
- = icon('key')
+ = icon('key fw')
%span
Deploy Keys
= nav_link(controller: :hooks) do
= link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks', data: {placement: 'right'} do
- = icon('link')
+ = icon('link fw')
%span
Web Hooks
= nav_link(controller: :services) do
= link_to namespace_project_services_path(@project.namespace, @project), title: 'Services', data: {placement: 'right'} do
- = icon('cogs')
+ = icon('cogs fw')
%span
Services
= nav_link(controller: :protected_branches) do
= link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches', data: {placement: 'right'} do
- = icon('lock')
+ = icon('lock fw')
%span
Protected branches
diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml
index 00c7cedce40..ee1b57278b6 100644
--- a/app/views/layouts/notify.html.haml
+++ b/app/views/layouts/notify.html.haml
@@ -27,7 +27,7 @@
}
.file-stats .deleted-file {
color: #B00;
- }}
+ }
%body
%div.content
= yield
diff --git a/app/views/layouts/project.html.haml b/app/views/layouts/project.html.haml
index 4aeb9d397d2..44afa33dfe5 100644
--- a/app/views/layouts/project.html.haml
+++ b/app/views/layouts/project.html.haml
@@ -2,7 +2,13 @@
- header_title project_title(@project)
- sidebar "project" unless sidebar
-- content_for :embedded_scripts do
+- content_for :scripts_body_top do
+ - if current_user
+ :javascript
+ window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
+ window.markdown_preview_path = "#{markdown_preview_namespace_project_path(@project.namespace, @project)}";
+
+- content_for :scripts_body do
= render "layouts/init_auto_complete" if current_user
= render template: "layouts/application"
diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml
index a374a662333..12f83aae04b 100644
--- a/app/views/notify/repository_push_email.html.haml
+++ b/app/views/notify/repository_push_email.html.haml
@@ -35,7 +35,7 @@
= diff.new_path
- elsif diff.new_file
%span.new-file
- &plus;
+ &#43;
= diff.new_path
- else
= diff.new_path
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index c30a3f5d79a..a26d4e0c757 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -26,20 +26,23 @@
- if current_user.private_token
= text_field_tag "token", current_user.private_token, class: "form-control"
%div
- = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token"
+ = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-default btn-build-token"
- else
%span You don`t have one yet. Click generate to fix it.
- = f.submit 'Generate', class: "btn success btn-build-token"
+ = f.submit 'Generate', class: "btn btn-default btn-build-token"
- unless current_user.ldap_user?
- - if current_user.otp_required_for_login
- .panel.panel-success
- .panel-heading
- Two-factor Authentication enabled
- .panel-body
+ .panel.panel-default
+ .panel-heading
+ Two-factor Authentication
+ .panel-body
+ - if current_user.otp_required_for_login
.pull-right
= link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm',
data: { confirm: 'Are you sure?' }
+ %p.text-success
+ %strong
+ Two-factor Authentication is enabled
%p
If you lose your recovery codes you can
%strong
@@ -47,11 +50,7 @@
= link_to 'generate new ones', codes_profile_two_factor_auth_path, method: :post, data: { confirm: 'Are you sure?' }
invalidating all previous codes.
- - else
- .panel.panel-default
- .panel-heading
- Two-factor Authentication
- .panel-body
+ - else
%p
Increase your account's security by enabling two-factor authentication (2FA).
%p
diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml
index c145a9b7f6d..2c4f0804f0b 100644
--- a/app/views/profiles/applications.html.haml
+++ b/app/views/profiles/applications.html.haml
@@ -2,37 +2,43 @@
%h3.page-title
= page_title
%p.light
- OAuth2 protocol settings below.
+ - if user_oauth_applications?
+ Manage applications that can use GitLab as an OAuth provider,
+ and applications that you've authorized to use your account.
+ - else
+ Manage applications that you've authorized to use your account.
%hr
-.oauth-applications
- %h3
- Your applications
- .pull-right
- = link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
- - if @applications.any?
- %table.table.table-striped
- %thead
- %tr
- %th Name
- %th Callback URL
- %th Clients
- %th
- %th
- %tbody
- - @applications.each do |application|
- %tr{:id => "application_#{application.id}"}
- %td= link_to application.name, oauth_application_path(application)
- %td
- - application.redirect_uri.split.each do |uri|
- %div= uri
- %td= application.access_tokens.count
- %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm'
- %td= render 'doorkeeper/applications/delete_form', application: application
+- if user_oauth_applications?
+ .oauth-applications
+ %h3
+ Your applications
+ .pull-right
+ = link_to 'New Application', new_oauth_application_path, class: 'btn btn-success'
+ - if @applications.any?
+ %table.table.table-striped
+ %thead
+ %tr
+ %th Name
+ %th Callback URL
+ %th Clients
+ %th
+ %th
+ %tbody
+ - @applications.each do |application|
+ %tr{:id => "application_#{application.id}"}
+ %td= link_to application.name, oauth_application_path(application)
+ %td
+ - application.redirect_uri.split.each do |uri|
+ %div= uri
+ %td= application.access_tokens.count
+ %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm'
+ %td= render 'doorkeeper/applications/delete_form', application: application
.oauth-authorized-applications.prepend-top-20
- %h3
- Authorized applications
+ - if user_oauth_applications?
+ %h3
+ Authorized applications
- if @authorized_tokens.any?
%table.table.table-striped
diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml
index fe5770f45c3..9bbccbc45ea 100644
--- a/app/views/profiles/keys/_key.html.haml
+++ b/app/views/profiles/keys/_key.html.haml
@@ -3,8 +3,7 @@
= link_to path_to_key(key, is_admin) do
%strong= key.title
%td
- %span
- (#{key.fingerprint})
+ %code.key-fingerprint= key.fingerprint
%td
%span.cgray
added #{time_ago_with_tooltip(key.created_at)}
diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml
index 8bac22a2e1a..e0ae4d9720f 100644
--- a/app/views/profiles/keys/_key_details.html.haml
+++ b/app/views/profiles/keys/_key_details.html.haml
@@ -15,7 +15,7 @@
.col-md-8
%p
%span.light Fingerprint:
- %strong= @key.fingerprint
+ %code.key-fingerprint= @key.fingerprint
%pre.well-pre
= @key.key
.pull-right
diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml
index 62fac46df27..6534afb0e89 100644
--- a/app/views/profiles/show.html.haml
+++ b/app/views/profiles/show.html.haml
@@ -37,8 +37,11 @@
= f.text_field :email, class: "form-control", required: true
- if @user.unconfirmed_email.present?
%span.help-block
- Please click the link in the confirmation email before continuing, it was sent to
- %strong #{@user.unconfirmed_email}
+ Please click the link in the confirmation email before continuing. It was sent to
+ = succeed "." do
+ %strong #{@user.unconfirmed_email}
+ %p
+ = link_to "Resend confirmation e-mail", user_confirmation_path(user: { email: @user.unconfirmed_email }), method: :post
- else
%span.help-block We also use email for avatar detection if no avatar is uploaded.
diff --git a/app/views/projects/_aside.html.haml b/app/views/projects/_aside.html.haml
index 000a40b466d..c9c17110d2b 100644
--- a/app/views/projects/_aside.html.haml
+++ b/app/views/projects/_aside.html.haml
@@ -56,7 +56,7 @@
- unless @project.empty_repo?
.panel.panel-default
.panel-heading
- = icon("archive fw")
+ = icon("folder-o fw")
Repository
.panel-body
%ul.nav.nav-pills
@@ -94,3 +94,15 @@
= icon("exclamation-triangle fw")
Archived project!
%p Repository is read-only
+
+ - if current_user
+ - access = user_max_access_in_project(current_user, @project)
+ - if access
+ .light-well.light.prepend-top-20
+ %small
+ You have #{access} access to this project.
+ - if @project.project_member_by_id(current_user)
+ %br
+ = link_to leave_namespace_project_project_members_path(@project.namespace, @project),
+ data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project' do
+ Leave this project
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index f9cdda4a3ba..076afb11a9d 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -5,7 +5,7 @@
.project-home-row.project-home-row-top
.project-home-desc
- if @project.description.present?
- = escaped_autolink(@project.description)
+ = markdown(@project.description, pipeline: :description)
- if can?(current_user, :admin_project, @project)
&ndash;
= link_to 'Edit', edit_namespace_project_path
diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml
index 2292aaaa214..491e2107da4 100644
--- a/app/views/projects/_issuable_form.html.haml
+++ b/app/views/projects/_issuable_form.html.haml
@@ -15,16 +15,16 @@
- if issuable.is_a?(MergeRequest)
%p.help-block
- if issuable.work_in_progress?
- This merge request is marked a <strong>Work In Progress</strong>.
- When it's ready, remove the <code>WIP</code> prefix from the title to allow it to be accepted.
+ Remove the <code>WIP</code> prefix from the title to allow this
+ <strong>Work In Progress</strong> merge request to be accepted when it's ready.
- else
- To prevent this merge request from being accepted before it's ready,
- mark it a <strong>Work In Progress</strong> by starting the title with <code>[WIP]</code> or <code>WIP:</code>.
+ Start the title with <code>[WIP]</code> or <code>WIP:</code> to prevent a
+ <strong>Work In Progress</strong> merge request from being accepted before it's ready.
.form-group.issuable-description
= f.label :description, 'Description', class: 'control-label'
.col-sm-10
- = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do
+ = render layout: 'projects/md_preview', locals: { preview_class: "wiki", referenced_users: true } do
= render 'projects/zen', f: f, attr: :description,
classes: 'description form-control'
.col-sm-12.hint
@@ -79,6 +79,24 @@
- if can? current_user, :admin_label, issuable.project
= link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank
+- if issuable.is_a?(MergeRequest)
+ %hr
+ - unless @merge_request.persisted?
+ .form-group
+ = f.label :source_branch, class: 'control-label' do
+ %i.fa.fa-code-fork
+ Source Branch
+ .col-sm-10
+ = f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
+ %p.help-block
+ = link_to 'Change source branch', mr_change_branches_path(@merge_request)
+ .form-group
+ = f.label :target_branch, class: 'control-label' do
+ %i.fa.fa-code-fork
+ Target Branch
+ .col-sm-10
+ = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, { class: 'target_branch select2 span2' })
+
.form-actions
- if !issuable.project.empty_repo? && (guide_url = contribution_guide_url(issuable.project)) && !issuable.persisted?
%p
diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml
index b869fd6e12a..a831481cf80 100644
--- a/app/views/projects/_md_preview.html.haml
+++ b/app/views/projects/_md_preview.html.haml
@@ -1,13 +1,24 @@
-%ul.nav.nav-tabs
- %li.active
- = link_to '#md-write-holder', class: 'js-md-write-button' do
- Write
- %li
- = link_to '#md-preview-holder', class: 'js-md-preview-button',
- data: { url: markdown_preview_namespace_project_path(@project.namespace, @project) } do
- Preview
-%div
- .md-write-holder
- = yield
- .md.md-preview-holder.hide
- .js-md-preview{class: (preview_class if defined?(preview_class))}
+.md-area
+ .md-header.clearfix
+ %ul.nav.nav-tabs
+ %li.active
+ = link_to '#md-write-holder', class: 'js-md-write-button' do
+ Write
+ %li
+ = link_to '#md-preview-holder', class: 'js-md-preview-button' do
+ Preview
+
+ - if defined?(referenced_users) && referenced_users
+ %span.referenced-users.pull-left.hide
+ = icon('exclamation-triangle')
+ You are about to add
+ %strong
+ %span.js-referenced-users-count 0
+ people
+ to the discussion. Proceed with caution.
+
+ %div
+ .md-write-holder
+ = yield
+ .md.md-preview-holder.hide
+ .js-md-preview{class: (preview_class if defined?(preview_class))}
diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml
index 96f188e4aa7..9c3e1703c89 100644
--- a/app/views/projects/blob/_editor.html.haml
+++ b/app/views/projects/blob/_editor.html.haml
@@ -12,8 +12,8 @@
\/
= text_field_tag 'file_name', params[:file_name], placeholder: "File name",
required: true, class: 'form-control new-file-name'
- .pull-right
- = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
+ .pull-right
+ = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control'
.file-content.code
%pre.js-edit-mode-pane#editor
diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml
index 9b1d03b820e..f7ddf74b4fc 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -6,11 +6,12 @@
= render 'shared/commit_message_container', params: params,
placeholder: 'Add new file'
- .form-group.branch
- = label_tag 'branch', class: 'control-label' do
- Branch
- .col-sm-10
- = text_field_tag 'new_branch', @ref, class: "form-control"
+ - unless @project.empty_repo?
+ .form-group.branch
+ = label_tag 'branch', class: 'control-label' do
+ Branch
+ .col-sm-10
+ = text_field_tag 'new_branch', @ref, class: "form-control"
= hidden_field_tag 'content', '', id: 'file-content'
= render 'projects/commit_button', ref: @ref,
diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml
index c577dfa8d55..8d66bae8cdf 100644
--- a/app/views/projects/deploy_keys/_deploy_key.html.haml
+++ b/app/views/projects/deploy_keys/_deploy_key.html.haml
@@ -2,24 +2,20 @@
.pull-right
- if @available_keys.include?(deploy_key)
= link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do
- %i.fa.fa-plus
+ = icon('plus')
Enable
- else
- if deploy_key.destroyed_when_orphaned? && deploy_key.almost_orphaned?
= link_to 'Remove', disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :put, class: "btn btn-remove delete-key btn-sm pull-right"
- else
= link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do
- %i.fa.fa-power-off
+ = icon('power-off')
Disable
- - if project = project_for_deploy_key(deploy_key)
- = link_to namespace_project_deploy_key_path(project.namespace, project, deploy_key) do
- %i.fa.fa-key
- %strong= deploy_key.title
- - else
- %i.fa.fa-key
- %strong= deploy_key.title
-
+ = icon('key')
+ %strong= deploy_key.title
+ %br
+ %code.key-fingerprint= deploy_key.fingerprint
%p.light.prepend-top-10
- if deploy_key.public?
diff --git a/app/views/projects/deploy_keys/show.html.haml b/app/views/projects/deploy_keys/show.html.haml
deleted file mode 100644
index 7d44652af72..00000000000
--- a/app/views/projects/deploy_keys/show.html.haml
+++ /dev/null
@@ -1,14 +0,0 @@
-- page_title @key.title, "Deploy Keys"
-%h3.page-title
- Deploy key:
- = @key.title
- %small
- created on
- = @key.created_at.stamp("Aug 21, 2011")
-.back-link
- = link_to namespace_project_deploy_keys_path(@project.namespace, @project) do
- &larr; To keys list
-%hr
-%pre= @key.key
-.pull-right
- = link_to 'Remove', namespace_project_deploy_key_path(@project.namespace, @project, @key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn-remove btn delete-key"
diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml
index d4b019780f5..99ee23a1ddc 100644
--- a/app/views/projects/diffs/_file.html.haml
+++ b/app/views/projects/diffs/_file.html.haml
@@ -10,8 +10,9 @@
- if @commit.parent_ids.present?
= view_file_btn(@commit.parent_id, diff_file, project)
- elsif diff_file.diff.submodule?
- - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
- = submodule_link(submodule_item, @commit.id, project.repository)
+ %span
+ - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path)
+ = submodule_link(submodule_item, @commit.id, project.repository)
- else
%span
- if diff_file.renamed_file
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index c09d794ef7f..2765f63c6bc 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -6,7 +6,7 @@
Project settings
%hr
.panel-body
- = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit_project form-horizontal" }, authenticity_token: true do |f|
+ = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit_project form-horizontal fieldset-form" }, authenticity_token: true do |f|
%fieldset
.form-group.project_name_holder
@@ -41,32 +41,40 @@
%legend
Features:
.form-group
- = f.label :issues_enabled, "Issues", class: 'control-label'
- .col-sm-10
+ .col-sm-offset-2.col-sm-10
.checkbox
- = f.check_box :issues_enabled
- %span.descr Lightweight issue tracking system for this project
+ = f.label :issues_enabled do
+ = f.check_box :issues_enabled
+ %strong Issues
+ %br
+ %span.descr Lightweight issue tracking system for this project
.form-group
- = f.label :merge_requests_enabled, "Merge Requests", class: 'control-label'
- .col-sm-10
+ .col-sm-offset-2.col-sm-10
.checkbox
- = f.check_box :merge_requests_enabled
- %span.descr Submit changes to be merged upstream.
+ = f.label :merge_requests_enabled do
+ = f.check_box :merge_requests_enabled
+ %strong Merge Requests
+ %br
+ %span.descr Submit changes to be merged upstream.
.form-group
- = f.label :wiki_enabled, "Wiki", class: 'control-label'
- .col-sm-10
+ .col-sm-offset-2.col-sm-10
.checkbox
- = f.check_box :wiki_enabled
- %span.descr Pages for project documentation
+ = f.label :wiki_enabled do
+ = f.check_box :wiki_enabled
+ %strong Wiki
+ %br
+ %span.descr Pages for project documentation
.form-group
- = f.label :snippets_enabled, "Snippets", class: 'control-label'
- .col-sm-10
+ .col-sm-offset-2.col-sm-10
.checkbox
- = f.check_box :snippets_enabled
- %span.descr Share code pastes with others out of git repository
+ = f.label :snippets_enabled do
+ = f.check_box :snippets_enabled
+ %strong Snippets
+ %br
+ %span.descr Share code pastes with others out of git repository
%fieldset.features
%legend
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index 7d7217eb2a8..8d2564be55e 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -10,5 +10,3 @@
$('#issue_assignee_id').val("#{current_user.id}").trigger("change");
e.preventDefault();
});
-
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index a4e25e5ce88..64d62b45657 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -4,7 +4,7 @@
= 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.str-truncated
+ %span.issue-title-text
= link_to_gfm issue.title, issue_path(issue), class: "row_title"
.issue-labels
- issue.labels.each do |label|
diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml
index 7d19415a7f4..d44fe486212 100644
--- a/app/views/projects/labels/index.html.haml
+++ b/app/views/projects/labels/index.html.haml
@@ -13,4 +13,7 @@
= paginate @labels, theme: 'gitlab'
- else
.light-well
- .nothing-here-block Create first label or #{link_to 'generate', generate_namespace_project_labels_path(@project.namespace, @project), method: :post} default set of labels
+ - if can? current_user, :admin_label, @project
+ .nothing-here-block Create first label or #{link_to 'generate', generate_namespace_project_labels_path(@project.namespace, @project), method: :post} default set of labels
+ - else
+ .nothing-here-block No labels created
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index 1c7160bce5f..be73f087449 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -8,5 +8,3 @@
$('#merge_request_assignee_id').val("#{current_user.id}").trigger("change");
e.preventDefault();
});
-
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index 65f5c3d6a19..c16df27ee8f 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -1,6 +1,6 @@
%li{ class: mr_css_classes(merge_request) }
.merge-request-title
- %span.str-truncated
+ %span.merge-request-title-text
= link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title"
.merge-request-labels
- merge_request.labels.each do |label|
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index e83b7649928..6792104569b 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -20,12 +20,12 @@
.mr-compare.merge-request
%ul.nav.nav-tabs.merge-request-tabs
%li.commits-tab
- = link_to '#commits', data: {action: 'commits', toggle: 'tab'} do
+ = link_to url_for(params), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
= icon('history')
Commits
%span.badge= @commits.size
%li.diffs-tab
- = link_to '#diffs', data: {action: 'diffs', toggle: 'tab'} do
+ = link_to url_for(params), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
= icon('list-alt')
Changes
%span.badge= @diffs.size
@@ -51,12 +51,10 @@
e.preventDefault();
});
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
-
:javascript
var merge_request
merge_request = new MergeRequest({
- action: 'diffs',
+ action: 'new',
diffs_loaded: true,
commits_loaded: true
});
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 0d894e360ea..74f8b9950cf 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -22,15 +22,14 @@
%span into
%strong.label-branch #{@merge_request.target_branch}
- if @merge_request.open?
- %span.pull-right
- .btn-group
- %a.btn.dropdown-toggle{ data: {toggle: :dropdown} }
- = icon('download')
- Download as
- %span.caret
- %ul.dropdown-menu
- %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
- %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
+ .btn-group.btn-group-sm.pull-right
+ %a.btn.btn-sm.dropdown-toggle{ data: {toggle: :dropdown} }
+ = icon('download')
+ Download as
+ %span.caret
+ %ul.dropdown-menu
+ %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
+ %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
= render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/show/state_widget"
@@ -38,17 +37,17 @@
- if @commits.present?
%ul.nav.nav-tabs.merge-request-tabs
%li.notes-tab
- = link_to '#notes', data: {action: 'notes', toggle: 'tab'} do
+ = link_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#notes', action: 'notes', toggle: 'tab'} do
= icon('comments')
Discussion
%span.badge= @merge_request.mr_and_commit_notes.user.count
%li.commits-tab
- = link_to '#commits', data: {action: 'commits', toggle: 'tab'} do
+ = link_to commits_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#commits', action: 'commits', toggle: 'tab'} do
= icon('history')
Commits
%span.badge= @commits.size
%li.diffs-tab
- = link_to '#diffs', data: {source: diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), action: 'diffs', toggle: 'tab'} do
+ = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), data: {target: '#diffs', action: 'diffs', toggle: 'tab'} do
= icon('list-alt')
Changes
%span.badge= @merge_request.diffs.size
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index 841d1e1cfe9..fa591b0537e 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -4,9 +4,10 @@
= render 'shared/issuable_search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
- if can? current_user, :write_merge_request, @project
- = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-new pull-left", title: "New Merge Request" do
- %i.fa.fa-plus
- New Merge Request
+ .pull-left.hidden-xs
+ = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-new", title: "New Merge Request" do
+ %i.fa.fa-plus
+ New Merge Request
= render 'shared/issuable_filter', type: :merge_requests
.merge-requests-holder
= render 'merge_requests'
diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml
index 906cc11dc67..bfd4ab6f3d8 100644
--- a/app/views/projects/merge_requests/show/_mr_accept.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml
@@ -49,7 +49,7 @@
.automerge_widget.cannot_be_merged.hide
%h4
- This pull request contains merge conflicts that must be resolved.
+ This merge request contains merge conflicts that must be resolved.
You can try it manually on the
%strong
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
@@ -63,14 +63,14 @@
.automerge_widget.work_in_progress.hide
%h4
- This request cannot be merged because it is marked as <strong>Work In Progress</strong>.
+ This merge request cannot be accepted because it is marked as Work In Progress.
%p
%button.btn.disabled{:type => 'button'}
%i.fa.fa-warning
Accept Merge Request
&nbsp;
- When the merge request is ready, remove the "WIP" prefix from the title to allow merging.
+ When the merge request is ready, remove the "WIP" prefix from the title to allow it to be accepted.
.automerge_widget.unchecked
%p
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 e4c71bfc1be..6396232db22 100644
--- a/app/views/projects/merge_requests/show/_state_widget.html.haml
+++ b/app/views/projects/merge_requests/show/_state_widget.html.haml
@@ -13,7 +13,7 @@
%h4
Rejected
- if @merge_request.closed_event
- by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)}
+ by #{link_to_member(@project, @merge_request.closed_event.author, avatar: true)}
#{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
%p Changes were not merged into target branch
@@ -21,7 +21,7 @@
%h4
Accepted
- if @merge_request.merge_event
- by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)}
+ by #{link_to_member(@project, @merge_request.merge_event.author, avatar: true)}
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
= render "projects/merge_requests/show/remove_source_branch"
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 95b7070ce5c..5650607f31f 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -50,5 +50,3 @@
dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
}).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val()));
-
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 2ada6cb6700..f28b3e9b508 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -5,7 +5,7 @@
= f.hidden_field :noteable_id
= f.hidden_field :noteable_type
- = render layout: 'projects/md_preview', locals: { preview_class: "note-text" } do
+ = render layout: 'projects/md_preview', locals: { preview_class: "note-text", referenced_users: true } do
= render 'projects/zen', f: f, attr: :note,
classes: 'note_text js-note-text'
@@ -15,10 +15,7 @@
.error-alert
.note-form-actions
- .buttons
+ .buttons.clearfix
= f.submit 'Add Comment', class: "btn comment-btn btn-grouped js-comment-button"
= yield(:note_actions)
%a.btn.grouped.js-close-discussion-note-form Cancel
-
-:javascript
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml
index 635e4d70941..860a997cff8 100644
--- a/app/views/projects/project_members/_project_member.html.haml
+++ b/app/views/projects/project_members/_project_member.html.haml
@@ -38,8 +38,9 @@
&nbsp;
- if current_user == user
- = link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: "Leave project?"}, method: :delete, class: "btn-xs btn btn-remove", title: 'Leave project' do
- %i.fa.fa-minus.fa-inverse
+ = link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: leave_project_message(@project) }, method: :delete, class: "btn-xs btn btn-remove", title: 'Leave project' do
+ = icon("sign-out")
+ Leave
- else
= link_to namespace_project_project_member_path(@project.namespace, @project, member), data: { confirm: remove_from_project_team_message(@project, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from team' do
%i.fa.fa-minus.fa-inverse
diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml
index 6edb92acd4d..162583e4b1d 100644
--- a/app/views/projects/project_members/index.html.haml
+++ b/app/views/projects/project_members/index.html.haml
@@ -11,7 +11,7 @@
.clearfix.js-toggle-container
= form_tag namespace_project_project_members_path(@project.namespace, @project), 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' }
+ = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' }
= button_tag 'Search', class: 'btn'
- if can?(current_user, :admin_project_member, @project)
diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml
index 4f3f4cab8d5..7d9bd08385a 100644
--- a/app/views/projects/update.js.haml
+++ b/app/views/projects/update.js.haml
@@ -6,4 +6,4 @@
$(".project-edit-errors").html("#{escape_javascript(render('errors'))}");
$('.save-project-loader').hide();
$('.project-edit-container').show();
- $('.project-edit-content .btn-save').enableButton();
+ $('.project-edit-content .btn-save').enable();
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 9fbfa0b1aeb..2a8ceaa2844 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -41,6 +41,3 @@
- else
= f.submit 'Create page', class: "btn-create btn"
= link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel"
-
-:javascript
- window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}";
diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml
index 47016daf1f0..5ee70be1ad6 100644
--- a/app/views/search/_form.html.haml
+++ b/app/views/search/_form.html.haml
@@ -5,7 +5,7 @@
= hidden_field_tag :scope, params[:scope]
.search-holder.clearfix
.form-group
- = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input input-mn-300", id: "dashboard_search", autofocus: true
+ = search_field_tag :search, params[:search], placeholder: "Search for projects, issues etc", class: "form-control search-text-input", id: "dashboard_search", autofocus: true
= button_tag 'Search', class: "btn btn-primary"
- unless params[:snippets].eql? 'true'
.pull-right
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index fba69dd0f3f..86921f0a777 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -4,7 +4,8 @@
- blob.data.lines.to_a.size.times do |index|
- offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset
- = link_to "#L#{i}", id: "L#{i}", rel: "#L#{i}" do
+ / We're not using `link_to` because it is too slow once we get to thousands of lines.
+ %a{href: "#L#{i}", id: "L#{i}", rel: "#L#{i}"}
%i.fa.fa-link
= i
:preserve
diff --git a/app/views/shared/_issuable_search_form.html.haml b/app/views/shared/_issuable_search_form.html.haml
index 639d203dcd6..58c3de64b77 100644
--- a/app/views/shared/_issuable_search_form.html.haml
+++ b/app/views/shared/_issuable_search_form.html.haml
@@ -1,6 +1,6 @@
= form_tag(path, 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' }
+ = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input' }
= hidden_field_tag :state, params['state']
= hidden_field_tag :scope, params['scope']
= hidden_field_tag :assignee_id, params['assignee_id']
diff --git a/app/views/shared/_project.html.haml b/app/views/shared/_project.html.haml
index 722a7f7ce0f..4537f8eec86 100644
--- a/app/views/shared/_project.html.haml
+++ b/app/views/shared/_project.html.haml
@@ -16,6 +16,3 @@
%span.pull-right.light
%i.fa.fa-star
= project.star_count
- - else
- %span.arrow
- %i.fa.fa-angle-right
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 6ed45fedfa2..1694818aef6 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -13,7 +13,7 @@
%h3
= @user.name
- if @user == current_user
- .pull-right
+ .pull-right.hidden-xs
= link_to profile_path, class: 'btn btn-sm' do
%i.fa.fa-pencil-square-o
Edit Profile settings
diff --git a/bin/guard b/bin/guard
deleted file mode 100755
index 0c1a532bd01..00000000000
--- a/bin/guard
+++ /dev/null
@@ -1,16 +0,0 @@
-#!/usr/bin/env ruby
-#
-# This file was generated by Bundler.
-#
-# The application 'guard' is installed as part of a gem, and
-# this file is here to facilitate running it.
-#
-
-require 'pathname'
-ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
- Pathname.new(__FILE__).realpath)
-
-require 'rubygems'
-require 'bundler/setup'
-
-load Gem.bin_path('guard', 'guard')
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index fbc7f515f34..f48c99fd901 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -182,12 +182,19 @@ production: &base
# Allow login via Twitter, Google, etc. using OmniAuth providers
enabled: false
+ # Uncomment this to automatically sign in with a specific omniauth provider's without
+ # showing GitLab's sign-in page (default: show the GitLab sign-in page)
+ # auto_sign_in_with_provider: saml
+
# CAUTION!
# This allows users to login without having a user account first (default: false).
# User accounts will be created automatically when authentication was successful.
allow_single_sign_on: false
# Locks down those users until they have been cleared by the admin (default: true).
block_auto_created_users: true
+ # Look up new users in LDAP servers. If a match is found (same uid), automatically
+ # link the omniauth identity with the LDAP account. (default: false)
+ auto_link_ldap_user: false
## Auth providers
# Uncomment the following lines and fill in the data of the auth provider you want to use
@@ -210,6 +217,15 @@ production: &base
# args: { scope: 'api' } }
# - { name: 'bitbucket', app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET'}
+ # - { name: 'saml',
+ # args: {
+ # assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ # idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ # idp_sso_target_url: 'https://login.example.com/idp',
+ # issuer: 'https://gitlab.example.com',
+ # name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ # } }
+
@@ -236,6 +252,9 @@ production: &base
# aws_secret_access_key: 'secret123'
# # The remote 'directory' to store your backups. For S3, this would be the bucket name.
# remote_directory: 'my.s3.bucket'
+ # # Use multipart uploads when file size reaches 100MB, see
+ # # http://docs.aws.amazon.com/AmazonS3/latest/dev/uploadobjusingmpu.html
+ # multipart_chunk_size: 104857600
## GitLab Shell settings
gitlab_shell:
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 2351ef7b0ce..c2c3c5bfde7 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -87,6 +87,11 @@ end
Settings['omniauth'] ||= Settingslogic.new({})
Settings.omniauth['enabled'] = false if Settings.omniauth['enabled'].nil?
+Settings.omniauth['auto_sign_in_with_provider'] = false if Settings.omniauth['auto_sign_in_with_provider'].nil?
+Settings.omniauth['allow_single_sign_on'] = false if Settings.omniauth['allow_single_sign_on'].nil?
+Settings.omniauth['block_auto_created_users'] = true if Settings.omniauth['block_auto_created_users'].nil?
+Settings.omniauth['auto_link_ldap_user'] = false if Settings.omniauth['auto_link_ldap_user'].nil?
+
Settings.omniauth['providers'] ||= []
Settings['issues_tracker'] ||= {}
@@ -169,6 +174,7 @@ Settings.backup['upload'] ||= Settingslogic.new({ 'remote_directory' => nil, 'co
if Settings.backup['upload']['connection']
Settings.backup['upload']['connection'] = Hash[Settings.backup['upload']['connection'].map { |k, v| [k.to_sym, v] }]
end
+Settings.backup['upload']['multipart_chunk_size'] ||= 104857600
#
# Git
diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb
index 38a5fa98dc2..5312fd8e89a 100644
--- a/config/initializers/6_rack_profiler.rb
+++ b/config/initializers/6_rack_profiler.rb
@@ -3,7 +3,8 @@ if Rails.env.development?
# initialization is skipped so trigger it
Rack::MiniProfilerRails.initialize!(Rails.application)
+
Rack::MiniProfiler.config.position = 'right'
Rack::MiniProfiler.config.start_hidden = true
- Rack::MiniProfiler.config.skip_paths << '/specs'
+ Rack::MiniProfiler.config.skip_paths << '/teaspoon'
end
diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb
index 103aa06ca32..6f1f267bf97 100644
--- a/config/initializers/7_omniauth.rb
+++ b/config/initializers/7_omniauth.rb
@@ -12,6 +12,8 @@ if Gitlab::LDAP::Config.enabled?
end
OmniAuth.config.allowed_request_methods = [:post]
+#In case of auto sign-in, the GET method is used (users don't get to click on a button)
+OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present?
OmniAuth.config.before_request_phase do |env|
OmniAuth::RequestForgeryProtection.new(env).call
end
diff --git a/config/routes.rb b/config/routes.rb
index bf2cb6421c5..f4a104664f3 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -2,7 +2,6 @@ require 'sidekiq/web'
require 'api/api'
Gitlab::Application.routes.draw do
- mount JasmineRails::Engine => '/specs' if defined?(JasmineRails)
use_doorkeeper do
controllers applications: 'oauth/applications',
authorized_applications: 'oauth/authorized_applications',
@@ -166,7 +165,7 @@ Gitlab::Application.routes.draw do
end
end
- resources :deploy_keys, only: [:index, :show, :new, :create, :destroy]
+ resources :deploy_keys, only: [:index, :new, :create, :destroy]
resources :hooks, only: [:index, :create, :destroy] do
get :test
@@ -422,7 +421,7 @@ Gitlab::Application.routes.draw do
end
end
- resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :show, :new, :create] do
+ resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :new, :create] do
member do
put :enable
put :disable
@@ -450,6 +449,7 @@ Gitlab::Application.routes.draw do
resources :merge_requests, constraints: { id: /\d+/ }, except: [:destroy] do
member do
get :diffs
+ get :commits
post :automerge
get :automerge_check
get :ci_status
diff --git a/db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb b/db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb
new file mode 100644
index 00000000000..6a78294f0b2
--- /dev/null
+++ b/db/migrate/20150529111607_add_user_oauth_applications_to_application_settings.rb
@@ -0,0 +1,5 @@
+class AddUserOauthApplicationsToApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :user_oauth_applications, :bool, default: true
+ end
+end
diff --git a/db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb b/db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb
new file mode 100644
index 00000000000..83e08101407
--- /dev/null
+++ b/db/migrate/20150529150354_add_after_sign_out_path_for_application_settings.rb
@@ -0,0 +1,5 @@
+class AddAfterSignOutPathForApplicationSettings < ActiveRecord::Migration
+ def change
+ add_column :application_settings, :after_sign_out_path, :string
+ end
+end \ No newline at end of file
diff --git a/db/schema.rb b/db/schema.rb
index 1ab91256406..aea0742cf3b 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20150516060434) do
+ActiveRecord::Schema.define(version: 20150529150354) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -33,6 +33,8 @@ ActiveRecord::Schema.define(version: 20150516060434) do
t.integer "default_project_visibility"
t.integer "default_snippet_visibility"
t.text "restricted_signup_domains"
+ t.boolean "user_oauth_applications", default: true
+ t.string "after_sign_out_path"
end
create_table "broadcast_messages", force: true do |t|
diff --git a/doc/README.md b/doc/README.md
index 4e00dceac2b..7a2181edded 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -1,34 +1,35 @@
-# Documentation
-
-## User documentation
-
-- [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.
-- [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) Using GitLab functionality and importing projects from GitHub and SVN.
-- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
-
-## Administrator documentation
-
-- [Install](install/README.md) Requirements, directory structures and installation from source.
-- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
-- [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.
-- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
-- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
-- [Operations](operations/README.md) Keeping GitLab up and running
-- [Log system](logs/logs.md) Log system
-
-## Contributor documentation
-
-- [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
-- [Legal](legal/README.md) Contributor license agreements.
-- [Release](release/README.md) How to make the monthly and security releases.
+# Documentation
+
+## User documentation
+
+- [API](api/README.md) Automate GitLab via a simple and powerful API.
+- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
+- [Importing to GitLab](workflow/importing/README.md).
+- [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.
+- [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) Using GitLab functionality and importing projects from GitHub and SVN.
+
+## Administrator documentation
+
+- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough.
+- [Install](install/README.md) Requirements, directory structures and installation from source.
+- [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter.
+- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages.
+- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars.
+- [Log system](logs/logs.md) Log system.
+- [Operations](operations/README.md) Keeping GitLab up and running
+- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects.
+- [Security](security/README.md) Learn what you can do to further secure your GitLab instance.
+- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed.
+- [Update](update/README.md) Update guides to upgrade your installation.
+- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page.
+
+## Contributor documentation
+
+- [Development](development/README.md) Explains the architecture and the guidelines for shell commands.
+- [Legal](legal/README.md) Contributor license agreements.
+- [Release](release/README.md) How to make the monthly and security releases. \ No newline at end of file
diff --git a/doc/api/README.md b/doc/api/README.md
index f6757b0a6aa..ca58c184543 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -19,6 +19,7 @@
- [Deploy Keys](deploy_keys.md)
- [System Hooks](system_hooks.md)
- [Groups](groups.md)
+- [Namespaces](namespaces.md)
## Clients
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index c1d82ad9576..7b0873a9111 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -221,7 +221,7 @@ If an error occurs, an error number and a message explaining the reason is retur
## Update MR
-Updates an existing merge request. You can change branches, title, or even close the MR.
+Updates an existing merge request. You can change the target branch, title, or even close the MR.
```
PUT /projects/:id/merge_request/:merge_request_id
@@ -231,7 +231,6 @@ Parameters:
- `id` (required) - The ID of a project
- `merge_request_id` (required) - ID of MR
-- `source_branch` - The source branch
- `target_branch` - The target branch
- `assignee_id` - Assignee user ID
- `title` - Title of MR
@@ -242,7 +241,6 @@ Parameters:
{
"id": 1,
"target_branch": "master",
- "source_branch": "test1",
"project_id": 3,
"title": "test1",
"description": "description1",
diff --git a/doc/api/namespaces.md b/doc/api/namespaces.md
new file mode 100644
index 00000000000..7b3238441f6
--- /dev/null
+++ b/doc/api/namespaces.md
@@ -0,0 +1,44 @@
+# Namespaces
+
+## List namespaces
+
+Get a list of namespaces. (As user: my namespaces, as admin: all namespaces)
+
+```
+GET /namespaces
+```
+
+```json
+[
+ {
+ "id": 1,
+ "path": "user1",
+ "kind": "user"
+ },
+ {
+ "id": 2,
+ "path": "group1",
+ "kind": "group"
+ }
+]
+```
+
+You can search for namespaces by name or path, see below.
+
+## Search for namespace
+
+Get all namespaces that match your string in their name or path.
+
+```
+GET /namespaces?search=foobar
+```
+
+```json
+[
+ {
+ "id": 1,
+ "path": "user1",
+ "kind": "user"
+ }
+]
+```
diff --git a/doc/install/installation.md b/doc/install/installation.md
index 1db2b438292..badea4de214 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -241,10 +241,7 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da
# Copy the example Rack attack config
sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb
- # Configure Git global settings for git user, useful when editing via web
- # Edit user.email according to what is set in gitlab.yml
- sudo -u git -H git config --global user.name "GitLab"
- sudo -u git -H git config --global user.email "example@example.com"
+ # Configure Git global settings for git user, used when editing via web editor
sudo -u git -H git config --global core.autocrlf input
# Configure Redis connection settings
diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md
index d82e1f8b41b..6a0fa4ce015 100644
--- a/doc/integration/bitbucket.md
+++ b/doc/integration/bitbucket.md
@@ -68,6 +68,8 @@ Bitbucket will generate an application ID and secret key for you to use.
1. Save the configuration file.
+1. If you're using the omnibus package, reconfigure GitLab (```gitlab-ctl reconfigure```).
+
1. Restart GitLab for the changes to take effect.
On the sign in page there should now be a Bitbucket icon below the regular sign in form.
@@ -80,43 +82,59 @@ To allow projects to be imported directly into GitLab, Bitbucket requires two ex
Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and instead requires GitLab to use SSH and identify itself using your GitLab server's SSH key.
-### Step 1: Known hosts
+### Step 1: Public key
-To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so:
+To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/home/git/.ssh/bitbucket_rsa.pub` in most configurations.
-1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use:
+If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following:
+
+1. Create a new SSH key:
```sh
- ssh git@bitbucket.org
+ sudo -u git -H ssh-keygen
```
-1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter):
+ When asked `Enter file in which to save the key` specify the correct path, eg. `/home/git/.ssh/bitbucket_rsa`.
+ Make sure to use an **empty passphrase**.
+
+1. Configure SSH client to use your new key:
+
+ Open the SSH configuration file of the git user.
```sh
- The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established.
- RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40.
- Are you sure you want to continue connecting (yes/no)?
+ sudo editor /home/git/.ssh/config
```
-1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts.
+ Add a host configuration for `bitbucket.org`.
+
+ ```sh
+ Host bitbucket.org
+ IdentityFile ~/.ssh/bitbucket_rsa
+ User git
+ ```
-1. Your GitLab server is now able to connect to Bitbucket over SSH. Continue to step 2:
+### Step 2: Known hosts
-### Step 2: Public key
+To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so:
-To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/home/git/.ssh/bitbucket_rsa.pub` in most configurations.
+1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use:
-If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following:
+ ```sh
+ sudo -u git -H ssh bitbucket.org
+ ```
-1. Create a new SSH key:
+1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter):
```sh
- sudo -u git -H ssh-keygen
+ The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established.
+ RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40.
+ Are you sure you want to continue connecting (yes/no)?
```
- When asked `Enter file in which to save the key` specify the correct path, eg. `/home/git/.ssh/bitbucket_rsa`.
- Make sure to use an **empty passphrase**.
+1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts.
+
+1. Your GitLab server is now able to connect to Bitbucket over SSH.
-2. Restart GitLab to allow it to find the new public key.
+1. Restart GitLab to allow it to find the new public key.
You should now see the "Import projects from Bitbucket" option on the New Project page enabled.
diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md
index b67f793c591..904d5d7fee2 100644
--- a/doc/integration/ldap.md
+++ b/doc/integration/ldap.md
@@ -6,6 +6,13 @@ The first time a user signs in with LDAP credentials, GitLab will create a new G
GitLab user attributes such as nickname and email will be copied from the LDAP user entry.
+## Security
+
+GitLab assumes that LDAP users are not able to change their LDAP 'mail', 'email' or 'userPrincipalName' attribute.
+An LDAP user who is allowed to change their email on the LDAP server can [take over any account](#enabling-ldap-sign-in-for-existing-gitlab-users) on your GitLab server.
+
+We recommend against using GitLab LDAP integration if your LDAP users are allowed to change their 'mail', 'email' or 'userPrincipalName' attribute on the LDAP server.
+
## Configuring GitLab for LDAP integration
To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`.
diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md
index 24f7b4bb4b4..8e2a602ec35 100644
--- a/doc/integration/omniauth.md
+++ b/doc/integration/omniauth.md
@@ -75,6 +75,7 @@ Now we can choose one or more of the Supported Providers below to continue confi
- [Google](google.md)
- [Shibboleth](shibboleth.md)
- [Twitter](twitter.md)
+- [SAML](saml.md)
## Enable OmniAuth for an Existing User
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
new file mode 100644
index 00000000000..a8cc5c8f74a
--- /dev/null
+++ b/doc/integration/saml.md
@@ -0,0 +1,77 @@
+# SAML OmniAuth Provider
+
+GitLab can be configured to act as a SAML 2.0 Service Provider (SP). This allows GitLab to consume assertions from a SAML 2.0 Identity Provider (IdP) such as Microsoft ADFS to authenticate users.
+
+First configure SAML 2.0 support in GitLab, then register the GitLab application in your SAML IdP:
+
+1. Make sure GitLab is configured with HTTPS. See [Using HTTPS](../install/installation.md#using-https) for instructions.
+
+1. On your GitLab server, open the configuration file.
+
+ For omnibus package:
+
+ ```sh
+ sudo editor /etc/gitlab/gitlab.rb
+ ```
+
+ For instalations from source:
+
+ ```sh
+ cd /home/git/gitlab
+
+ sudo -u git -H editor config/gitlab.yml
+ ```
+
+1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings.
+
+1. Add the provider configuration:
+
+ For omnibus package:
+
+ ```ruby
+ gitlab_rails['omniauth_providers'] = [
+ {
+ "name" => "saml",
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ }
+ }
+ ]
+ ```
+
+ For installations from source:
+
+ ```yaml
+ - { name: 'saml',
+ args: {
+ assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
+ idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
+ idp_sso_target_url: 'https://login.example.com/idp',
+ issuer: 'https://gitlab.example.com',
+ name_identifier_format: 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
+ } }
+ ```
+
+1. Change the value for 'assertion_consumer_service_url' to match the HTTPS endpoint of GitLab (append 'users/auth/saml/callback' to the HTTPS URL of your GitLab installation to generate the correct value).
+
+1. Change the values of 'idp_cert_fingerprint', 'idp_sso_target_url', 'name_identifier_format' to match your IdP. Check [the omniauth-saml documentation](https://github.com/PracticallyGreen/omniauth-saml) for details on these options.
+
+1. Change the value of 'issuer' to a unique name, which will identify the application to the IdP.
+
+1. Restart GitLab for the changes to take effect.
+
+1. Register the GitLab SP in your SAML 2.0 IdP, using the application name specified in 'issuer'.
+
+To ease configuration, most IdP accept a metadata URL for the application to provide configuration information to the IdP. To build the metadata URL for GitLab, append 'users/auth/saml/metadata' to the HTTPS URL of your GitLab installation, for instance:
+ ```
+ https://gitlab.example.com/users/auth/saml/metadata
+ ```
+
+At a minimum the IdP *must* provide a claim containing the user's email address, using claim name 'email' or 'mail'. The email will be used to automatically generate the GitLab username. GitLab will also use claims with name 'name', 'first_name', 'last_name' (see [the omniauth-saml gem](https://github.com/PracticallyGreen/omniauth-saml/blob/master/lib/omniauth/strategies/saml.rb) for supported claims).
+
+On the sign in page there should now be a SAML button below the regular sign in form. Click the icon to begin the authentication process. If everything goes well the user will be returned to GitLab and will be signed in.
+
diff --git a/doc/operations/README.md b/doc/operations/README.md
index f1456c6c8e2..6a35dab7b6c 100644
--- a/doc/operations/README.md
+++ b/doc/operations/README.md
@@ -2,3 +2,4 @@
- [Sidekiq MemoryKiller](sidekiq_memory_killer.md)
- [Cleaning up Redis sessions](cleaning_up_redis_sessions.md)
+- [Understanding Unicorn and unicorn-worker-killer](unicorn.md)
diff --git a/doc/operations/unicorn.md b/doc/operations/unicorn.md
new file mode 100644
index 00000000000..31b432cd411
--- /dev/null
+++ b/doc/operations/unicorn.md
@@ -0,0 +1,86 @@
+# Understanding Unicorn and unicorn-worker-killer
+
+## Unicorn
+
+GitLab uses [Unicorn](http://unicorn.bogomips.org/), a pre-forking Ruby web
+server, to handle web requests (web browsers and Git HTTP clients). Unicorn is
+a daemon written in Ruby and C that can load and run a Ruby on Rails
+application; in our case the Rails application is GitLab Community Edition or
+GitLab Enterprise Edition.
+
+Unicorn has a multi-process architecture to make better use of available CPU
+cores (processes can run on different cores) and to have stronger fault
+tolerance (most failures stay isolated in only one process and cannot take down
+GitLab entirely). On startup, the Unicorn 'master' process loads a clean Ruby
+environment with the GitLab application code, and then spawns 'workers' which
+inherit this clean initial environment. The 'master' never handles any
+requests, that is left to the workers. The operating system network stack
+queues incoming requests and distributes them among the workers.
+
+In a perfect world, the master would spawn its pool of workers once, and then
+the workers handle incoming web requests one after another until the end of
+time. In reality, worker processes can crash or time out: if the master notices
+that a worker takes too long to handle a request it will terminate the worker
+process with SIGKILL ('kill -9'). No matter how the worker process ended, the
+master process will replace it with a new 'clean' process again. Unicorn is
+designed to be able to replace 'crashed' workers without dropping user
+requests.
+
+This is what a Unicorn worker timeout looks like in `unicorn_stderr.log`. The
+master process has PID 56227 below.
+
+```
+[2015-06-05T10:58:08.660325 #56227] ERROR -- : worker=10 PID:53009 timeout (61s > 60s), killing
+[2015-06-05T10:58:08.699360 #56227] ERROR -- : reaped #<Process::Status: pid 53009 SIGKILL (signal 9)> worker=10
+[2015-06-05T10:58:08.708141 #62538] INFO -- : worker=10 spawned pid=62538
+[2015-06-05T10:58:08.708824 #62538] INFO -- : worker=10 ready
+```
+
+### Tunables
+
+The main tunables for Unicorn are the number of worker processes and the
+request timeout after which the Unicorn master terminates a worker process.
+See the [omnibus-gitlab Unicorn settings
+documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md)
+if you want to adjust these settings.
+
+## unicorn-worker-killer
+
+GitLab has memory leaks. These memory leaks manifest themselves in long-running
+processes, such as Unicorn workers. (The Unicorn master process is not known to
+leak memory, probably because it does not handle user requests.)
+
+To make these memory leaks manageable, GitLab comes with the
+[unicorn-worker-killer gem](https://github.com/kzk/unicorn-worker-killer). This
+gem [monkey-patches](http://en.wikipedia.org/wiki/Monkey_patch) the Unicorn
+workers to do a memory self-check after every 16 requests. If the memory of the
+Unicorn worker exceeds a pre-set limit then the worker process exits. The
+Unicorn master then automatically replaces the worker process.
+
+This is a robust way to handle memory leaks: Unicorn is designed to handle
+workers that 'crash' so no user requests will be dropped. The
+unicorn-worker-killer gem is designed to only terminate a worker process _in
+between requests_, so no user requests are affected.
+
+This is what a Unicorn worker memory restart looks like in unicorn_stderr.log.
+You see that worker 4 (PID 125918) is inspecting itself and decides to exit.
+The threshold memory value was 254802235 bytes, about 250MB. With GitLab this
+threshold is a random value between 200 and 250 MB. The master process (PID
+117565) then reaps the worker process and spawns a new 'worker 4' with PID
+127549.
+
+```
+[2015-06-05T12:07:41.828374 #125918] WARN -- : #<Unicorn::HttpServer:0x00000002734770>: worker (pid: 125918) exceeds memory limit (256413696 bytes > 254802235 bytes)
+[2015-06-05T12:07:41.828472 #125918] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 125918) alive: 23 sec (trial 1)
+[2015-06-05T12:07:42.025916 #117565] INFO -- : reaped #<Process::Status: pid 125918 exit 0> worker=4
+[2015-06-05T12:07:42.034527 #127549] INFO -- : worker=4 spawned pid=127549
+[2015-06-05T12:07:42.035217 #127549] INFO -- : worker=4 ready
+```
+
+One other thing that stands out in the log snippet above, taken from
+Gitlab.com, is that 'worker 4' was serving requests for only 23 seconds. This
+is a normal value for our current GitLab.com setup and traffic.
+
+The high frequency of Unicorn memory restarts on some GitLab sites can be a
+source of confusion for administrators. Usually they are a [red
+herring](http://en.wikipedia.org/wiki/Red_herring).
diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md
index 41a994f3f68..2aca91d5371 100644
--- a/doc/raketasks/maintenance.md
+++ b/doc/raketasks/maintenance.md
@@ -47,7 +47,6 @@ Git: /usr/bin/git
Runs the following rake tasks:
-- `gitlab:env:check`
- `gitlab:gitlab_shell:check`
- `gitlab:sidekiq:check`
- `gitlab:app:check`
@@ -147,7 +146,7 @@ Do you want to continue (yes/no)? yes
## Clear redis cache
-If for some reason the dashboard shows wrong information you might want to
+If for some reason the dashboard shows wrong information you might want to
clear Redis' cache.
For Omnibus-packages:
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index eb97f3cd7f6..57d904ff389 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -71,9 +71,14 @@ Xth: (1 working day before the 22nd)
- [ ] Update GitLab.com with the stable version (#LINK)
- [ ] Update ci.gitLab.com with the stable version (#LINK)
-22nd:
+22nd before 12AM CET:
+
+Release before 12AM CET / 3AM PST, to make sure the majority of our users
+get the new version on the 22nd and there is sufficient time in the European
+workday to quickly fix any issues.
- [ ] Release CE, EE and CI (#LINK)
+- [ ] Schedule a second tweet of the release announcement at 6PM CET / 9AM PST
```
diff --git a/doc/workflow/2fa.png b/doc/workflow/2fa.png
new file mode 100644
index 00000000000..bbf415210d5
--- /dev/null
+++ b/doc/workflow/2fa.png
Binary files differ
diff --git a/doc/workflow/2fa_auth.png b/doc/workflow/2fa_auth.png
new file mode 100644
index 00000000000..4a4fbe68984
--- /dev/null
+++ b/doc/workflow/2fa_auth.png
Binary files differ
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index 0fca68f364e..70a8179c8eb 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -1,17 +1,16 @@
-# Workflow
-
-- [Feature branch workflow](workflow.md)
-- [Project forking workflow](forking_workflow.md)
-- [Project Features](project_features.md)
-- [Authorization for merge requests](authorization_for_merge_requests.md)
-- [Groups](groups.md)
-- [Labels](labels.md)
-- [GitLab Flow](gitlab_flow.md)
-- [Notifications](notifications.md)
-- [Migrating from SVN to GitLab](migrating_from_svn.md)
-- [Project importing from GitHub to GitLab](import_projects_from_github.md)
-- [Project importing from GitLab.com to your private GitLab instance](import_projects_from_gitlab_com.md)
-- [Protected branches](protected_branches.md)
-- [Change your time zone](timezone.md)
-- [Keyboard shortcuts](shortcuts.md)
-- [Web Editor](web_editor.md) \ No newline at end of file
+# Workflow
+
+- [Authorization for merge requests](authorization_for_merge_requests.md)
+- [Change your time zone](timezone.md)
+- [Feature branch workflow](workflow.md)
+- [GitLab Flow](gitlab_flow.md)
+- [Groups](groups.md)
+- [Keyboard shortcuts](shortcuts.md)
+- [Labels](labels.md)
+- [Notifications](notifications.md)
+- [Project Features](project_features.md)
+- [Project forking workflow](forking_workflow.md)
+- [Protected branches](protected_branches.md)
+- [Two-factor Authentication (2FA)](two_factor_authentication.md)
+- [Web Editor](web_editor.md)
+- ["Work In Progress" Merge Requests](wip_merge_requests.md) \ No newline at end of file
diff --git a/doc/workflow/import_projects_from_github.md b/doc/workflow/import_projects_from_github.md
deleted file mode 100644
index 8644b4ffc73..00000000000
--- a/doc/workflow/import_projects_from_github.md
+++ /dev/null
@@ -1,13 +0,0 @@
-# Project importing from GitHub to GitLab
-
-You can import your existing GitHub projects to GitLab. But keep in mind that it is possible only if
-GitHub support is enabled on your GitLab instance. You can read more about GitHub support [here](http://doc.gitlab.com/ce/integration/github.html)
-To get to the importer page you need to go to "New project" page.
-
-![New project page](github_importer/new_project_page.png)
-
-Click on the "Import project from GitHub" link and you will be redirected to GitHub for permission to access your projects. After accepting, you'll be automatically redirected to the importer.
-
-![Importer page](github_importer/importer.png)
-
-To import a project, you can simple click "Add". The importer will import your repository and issues. Once the importer is done, a new GitLab project will be created with your imported data. \ No newline at end of file
diff --git a/doc/workflow/importing/README.md b/doc/workflow/importing/README.md
new file mode 100644
index 00000000000..2b2e9037425
--- /dev/null
+++ b/doc/workflow/importing/README.md
@@ -0,0 +1,6 @@
+# Migrating projects to a GitLab instance
+
+1. [Bitbucket](import_projects_from_bitbucket.md)
+2. [GitHub](import_projects_from_github.md)
+3. [GitLab.com](import_projects_from_gitlab_com.md)
+4. [SVN](migrating_from_svn.md)
diff --git a/doc/workflow/importing/bitbucket_importer/bitbucket_import_grant_access.png b/doc/workflow/importing/bitbucket_importer/bitbucket_import_grant_access.png
new file mode 100644
index 00000000000..df55a081803
--- /dev/null
+++ b/doc/workflow/importing/bitbucket_importer/bitbucket_import_grant_access.png
Binary files differ
diff --git a/doc/workflow/importing/bitbucket_importer/bitbucket_import_new_project.png b/doc/workflow/importing/bitbucket_importer/bitbucket_import_new_project.png
new file mode 100644
index 00000000000..5253889d251
--- /dev/null
+++ b/doc/workflow/importing/bitbucket_importer/bitbucket_import_new_project.png
Binary files differ
diff --git a/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_bitbucket.png b/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_bitbucket.png
new file mode 100644
index 00000000000..ffa87ce5b2e
--- /dev/null
+++ b/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_bitbucket.png
Binary files differ
diff --git a/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_project.png b/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_project.png
new file mode 100644
index 00000000000..0e08703f421
--- /dev/null
+++ b/doc/workflow/importing/bitbucket_importer/bitbucket_import_select_project.png
Binary files differ
diff --git a/doc/workflow/github_importer/importer.png b/doc/workflow/importing/github_importer/importer.png
index 57636717571..57636717571 100644
--- a/doc/workflow/github_importer/importer.png
+++ b/doc/workflow/importing/github_importer/importer.png
Binary files differ
diff --git a/doc/workflow/github_importer/new_project_page.png b/doc/workflow/importing/github_importer/new_project_page.png
index 002f22d81d7..002f22d81d7 100644
--- a/doc/workflow/github_importer/new_project_page.png
+++ b/doc/workflow/importing/github_importer/new_project_page.png
Binary files differ
diff --git a/doc/workflow/gitlab_importer/importer.png b/doc/workflow/importing/gitlab_importer/importer.png
index d2a286d8cac..d2a286d8cac 100644
--- a/doc/workflow/gitlab_importer/importer.png
+++ b/doc/workflow/importing/gitlab_importer/importer.png
Binary files differ
diff --git a/doc/workflow/gitlab_importer/new_project_page.png b/doc/workflow/importing/gitlab_importer/new_project_page.png
index 5e239208e1e..5e239208e1e 100644
--- a/doc/workflow/gitlab_importer/new_project_page.png
+++ b/doc/workflow/importing/gitlab_importer/new_project_page.png
Binary files differ
diff --git a/doc/workflow/importing/import_projects_from_bitbucket.md b/doc/workflow/importing/import_projects_from_bitbucket.md
new file mode 100644
index 00000000000..1e9825e2e10
--- /dev/null
+++ b/doc/workflow/importing/import_projects_from_bitbucket.md
@@ -0,0 +1,26 @@
+# Import your project from Bitbucket to GitLab
+
+It takes just a few steps to import your existing Bitbucket projects to GitLab. But keep in mind that it is possible only if Bitbucket support is enabled on your GitLab instance. You can read more about Bitbucket support [here](doc/integration/bitbucket.md).
+
+* Sign in to GitLab.com and go to your dashboard
+
+* Click on "New project"
+
+![New project in GitLab](bitbucket_importer/bitbucket_import_new_project.png)
+
+* Click on the "Bitbucket" button
+
+![Bitbucket](bitbucket_importer/bitbucket_import_select_bitbucket.png)
+
+* Grant GitLab access to your Bitbucket account
+
+![Grant access](bitbucket_importer/bitbucket_import_grant_access.png)
+
+* Click on the projects that you'd like to import or "Import all projects"
+
+![Import projects](bitbucket_importer/bitbucket_import_select_project.png)
+
+A new GitLab project will be created with your imported data.
+
+### Note
+Milestones and wiki pages are not imported from Bitbucket.
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
new file mode 100644
index 00000000000..aad2c63817d
--- /dev/null
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -0,0 +1,18 @@
+# Import your project from GitHub to GitLab
+
+It takes just a couple of steps to import your existing GitHub projects to GitLab. Keep in mind that it is possible only if
+GitHub support is enabled on your GitLab instance. You can read more about GitHub support [here](http://doc.gitlab.com/ce/integration/github.html)
+
+* Sign in to GitLab.com and go to your dashboard.
+* To get to the importer page, you need to go to the "New project" page.
+
+![New project page](github_importer/new_project_page.png)
+
+* Click on the "Import project from GitHub" link and you will be redirected to GitHub for permission to access your projects. After accepting, you'll be automatically redirected to the importer.
+
+![Importer page](github_importer/importer.png)
+
+* To import a project, you can simple click "Add". The importer will import your repository and issues. Once the importer is done, a new GitLab project will be created with your imported data.
+
+### Note
+When you import your projects from GitHub, it is not possible to keep your labels and milestones and issue numbers won't match. We are working on improving this in the near future. \ No newline at end of file
diff --git a/doc/workflow/import_projects_from_gitlab_com.md b/doc/workflow/importing/import_projects_from_gitlab_com.md
index f4c4e955d46..f4c4e955d46 100644
--- a/doc/workflow/import_projects_from_gitlab_com.md
+++ b/doc/workflow/importing/import_projects_from_gitlab_com.md
diff --git a/doc/workflow/migrating_from_svn.md b/doc/workflow/importing/migrating_from_svn.md
index 485db4834e9..485db4834e9 100644
--- a/doc/workflow/migrating_from_svn.md
+++ b/doc/workflow/importing/migrating_from_svn.md
diff --git a/doc/workflow/two_factor_authentication.md b/doc/workflow/two_factor_authentication.md
new file mode 100644
index 00000000000..7c45d23c99d
--- /dev/null
+++ b/doc/workflow/two_factor_authentication.md
@@ -0,0 +1,67 @@
+# Two-factor Authentication (2FA)
+
+Two-factor Authentication (2FA) provides an additional level of security to your
+GitLab account. Once enabled, in addition to supplying your username and
+password to login, you'll be prompted for a code generated by an application on
+your phone.
+
+By enabling 2FA, the only way someone other than you can log into your account
+is to know your username and password *and* have access to your phone.
+
+## Enabling 2FA
+
+**In GitLab:**
+
+1. Log in to your GitLab account.
+1. Go to your **Profile Settings**.
+1. Go to **Account**.
+1. Click **Enable Two-factor Authentication**.
+
+![Two-factor setup](2fa.png)
+
+**On your phone:**
+
+1. Install a compatible application. We recommend [Google Authenticator]
+\(proprietary\) or [FreeOTP] \(open source\).
+1. In the application, add a new entry in one of two ways:
+ * Scan the code with your phone's camera to add the entry automatically.
+ * Enter the details provided to add the entry manually.
+
+**In GitLab:**
+
+1. Enter the six-digit pin number from the entry on your phone into the **Pin
+ code** field.
+1. Click **Submit**.
+
+If the pin you entered was correct, you'll see a message indicating that
+Two-factor Authentication has been enabled, and you'll be presented with a list
+of recovery codes.
+
+## Recovery Codes
+
+Should you ever lose access to your phone, you can use one of the ten provided
+backup codes to login to your account. We suggest copying or printing them for
+storage in a safe place. **Each code can be used only once** to log in to your
+account.
+
+If you lose the recovery codes or just want to generate new ones, you can do so
+from the **Profile Settings** > **Acount** page where you first enabled 2FA.
+
+## Logging in with 2FA Enabled
+
+Logging in with 2FA enabled is only slightly different than a normal login.
+Enter your username and password credentials as you normally would, and you'll
+be presented with a second prompt for an authentication code. Enter the pin from
+your phone's application or a recovery code to log in.
+
+![Two-factor authentication on sign in](2fa_auth.png)
+
+## Disabling 2FA
+
+1. Log in to your GitLab account.
+1. Go to your **Profile Settings**.
+1. Go to **Acount**.
+1. Click **Disable Two-factor Authentication**.
+
+[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
+[FreeOTP]: https://fedorahosted.org/freeotp/ \ No newline at end of file
diff --git a/doc/workflow/wip_merge_requests.md b/doc/workflow/wip_merge_requests.md
new file mode 100644
index 00000000000..46035a5e6b6
--- /dev/null
+++ b/doc/workflow/wip_merge_requests.md
@@ -0,0 +1,13 @@
+# "Work In Progress" Merge Requests
+
+To prevent merge requests from accidentally being accepted before they're completely ready, GitLab blocks the "Accept" button for merge requests that have been marked a **Work In Progress**.
+
+![Blocked Accept Button](wip_merge_requests/blocked_accept_button.png)
+
+To mark a merge request a Work In Progress, simply start its title with `[WIP]` or `WIP:`.
+
+![Mark as WIP](wip_merge_requests/mark_as_wip.png)
+
+To allow a Work In Progress merge request to be accepted again when it's ready, simply remove the `WIP` prefix.
+
+![Unark as WIP](wip_merge_requests/unmark_as_wip.png)
diff --git a/doc/workflow/wip_merge_requests/blocked_accept_button.png b/doc/workflow/wip_merge_requests/blocked_accept_button.png
new file mode 100644
index 00000000000..4791e5de972
--- /dev/null
+++ b/doc/workflow/wip_merge_requests/blocked_accept_button.png
Binary files differ
diff --git a/doc/workflow/wip_merge_requests/mark_as_wip.png b/doc/workflow/wip_merge_requests/mark_as_wip.png
new file mode 100644
index 00000000000..8fa83a201ac
--- /dev/null
+++ b/doc/workflow/wip_merge_requests/mark_as_wip.png
Binary files differ
diff --git a/doc/workflow/wip_merge_requests/unmark_as_wip.png b/doc/workflow/wip_merge_requests/unmark_as_wip.png
new file mode 100644
index 00000000000..d45e68f31c5
--- /dev/null
+++ b/doc/workflow/wip_merge_requests/unmark_as_wip.png
Binary files differ
diff --git a/doc_styleguide.md b/doc_styleguide.md
index 670af765f3a..db30a737f14 100644
--- a/doc_styleguide.md
+++ b/doc_styleguide.md
@@ -12,6 +12,7 @@ This styleguide recommends best practices to improve documentation and to keep i
* Be brief and clear.
+* Whenever it applies, add documents in alphabetical order.
## When adding images to a document
diff --git a/docker/README.md b/docker/README.md
index 2e533ae9dd5..46b21348364 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -107,7 +107,7 @@ The directories on data container are:
### Configure GitLab
-These container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`.
+This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`.
To access GitLab configuration, you can start an interactive command line in a new container using the shared data volume container, you will be able to browse the 3 directories and use your favorite text editor:
@@ -122,7 +122,7 @@ You can find all available options in [Omnibus GitLab documentation](https://git
### Upgrade GitLab with app and data images
-To updgrade GitLab to new versions, stop running container, create new docker image and container from that image.
+To upgrade GitLab to new versions, stop running container, create new docker image and container from that image.
It Assumes that you're upgrading from 7.8.1 to 7.10.1 and you're in the updated GitLab repo root directory:
@@ -141,10 +141,12 @@ sudo docker rmi gitlab-app:7.8.1
### Publish images to Dockerhub
-Login to Dockerhub with `sudo docker login` and run the following (replace '7.9.2' with the version you're using and 'Sytse Sijbrandij' with your name):
+- Ensure the containers are running
+- Login to Dockerhub with `sudo docker login`
+- Run the following (replace '7.9.2' with the version you're using and 'Sytse Sijbrandij' with your name):
```bash
-sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-app:7.10.1 sytse/gitlab-app:7.10.1
+sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-app sytse/gitlab-app:7.10.1
sudo docker push sytse/gitlab-app:7.10.1
sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab_data sytse/gitlab_data
sudo docker push sytse/gitlab_data
diff --git a/features/admin/deploy_keys.feature b/features/admin/deploy_keys.feature
index 9df47eb51fd..33439cd1e85 100644
--- a/features/admin/deploy_keys.feature
+++ b/features/admin/deploy_keys.feature
@@ -8,11 +8,6 @@ Feature: Admin Deploy Keys
When I visit admin deploy keys page
Then I should see all public deploy keys
- Scenario: Deploy Keys show
- When I visit admin deploy keys page
- And I click on first deploy key
- Then I should see deploy key details
-
Scenario: Deploy Keys new
When I visit admin deploy keys page
And I click 'New Deploy Key'
diff --git a/features/dashboard/group.feature b/features/dashboard/group.feature
index cf4b8d7283b..e3c01db2ebb 100644
--- a/features/dashboard/group.feature
+++ b/features/dashboard/group.feature
@@ -24,7 +24,8 @@ Feature: Dashboard Group
When I visit dashboard groups page
Then I should see group "Owned" in group list
Then I should see group "Guest" in group list
- Then I should not see the "Leave" button for group "Owned"
+ When I click on the "Leave" button for group "Owned"
+ Then I should see the "Can not leave message"
@javascript
Scenario: Guest should be able to leave from group
diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature
index 05faad4e645..8661ea98c20 100644
--- a/features/project/active_tab.feature
+++ b/features/project/active_tab.feature
@@ -35,6 +35,11 @@ Feature: Project Active Tab
Then the active main tab should be Merge Requests
And no other main tabs should be active
+ Scenario: On Project Members
+ Given I visit my project's members page
+ Then the active main tab should be Members
+ And no other main tabs should be active
+
Scenario: On Project Wiki
Given I visit my project's wiki page
Then the active main tab should be Wiki
@@ -49,13 +54,6 @@ Feature: Project Active Tab
# Sub Tabs: Settings
- Scenario: On Project Settings/Team
- Given I visit my project's settings page
- And I click the "Team" tab
- Then the active sub nav should be Team
- And no other sub navs should be active
- And the active main tab should be Settings
-
Scenario: On Project Settings/Edit
Given I visit my project's settings page
And I click the "Edit" tab
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index 7a831901607..eb091c291e9 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -207,3 +207,11 @@ Feature: Project Merge Requests
Then I should see that I am subscribed
When I click button "Unsubscribe"
Then I should see that I am unsubscribed
+
+ @javascript
+ Scenario: I can change the target branch
+ Given I visit merge request page "Bug NS-04"
+ And I click link "Edit" for the merge request
+ When I click the "Target branch" dropdown
+ And I select a new target branch
+ Then I should see new target branch changes
diff --git a/features/steps/admin/deploy_keys.rb b/features/steps/admin/deploy_keys.rb
index fb0b611762e..844837d177d 100644
--- a/features/steps/admin/deploy_keys.rb
+++ b/features/steps/admin/deploy_keys.rb
@@ -14,17 +14,6 @@ class Spinach::Features::AdminDeployKeys < Spinach::FeatureSteps
end
end
- step 'I click on first deploy key' do
- click_link DeployKey.are_public.first.title
- end
-
- step 'I should see deploy key details' do
- deploy_key = DeployKey.are_public.first
- current_path.should == admin_deploy_key_path(deploy_key)
- page.should have_content(deploy_key.title)
- page.should have_content(deploy_key.key)
- end
-
step 'I visit admin deploy key page' do
visit admin_deploy_key_path(deploy_key)
end
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 8508b2a8096..bb1f2f444f9 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -23,8 +23,8 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
step 'I see prefilled new Merge Request page' do
current_path.should == new_namespace_project_merge_request_path(@project.namespace, @project)
find("#merge_request_target_project_id").value.should == @project.id.to_s
- find("#merge_request_source_branch").value.should == "fix"
- find("#merge_request_target_branch").value.should == "master"
+ find("input#merge_request_source_branch").value.should == "fix"
+ find("input#merge_request_target_branch").value.should == "master"
end
step 'user with name "John Doe" joined project "Shop"' do
diff --git a/features/steps/dashboard/group.rb b/features/steps/dashboard/group.rb
index 8384df2fb59..aeea49320ff 100644
--- a/features/steps/dashboard/group.rb
+++ b/features/steps/dashboard/group.rb
@@ -60,4 +60,8 @@ class Spinach::Features::DashboardGroup < Spinach::FeatureSteps
page.should have_content "Samurai"
page.should have_content "Tokugawa Shogunate"
end
+
+ step 'I should see the "Can not leave message"' do
+ page.should have_content "You can not leave Owned group because you're the last owner"
+ end
end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index b8f79f70ca4..d16e6bbea57 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -7,21 +7,23 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I change my profile info' do
- fill_in "user_skype", with: "testskype"
- fill_in "user_linkedin", with: "testlinkedin"
- fill_in "user_twitter", with: "testtwitter"
- fill_in "user_website_url", with: "testurl"
- fill_in "user_location", with: "Ukraine"
- click_button "Save changes"
+ fill_in 'user_skype', with: 'testskype'
+ fill_in 'user_linkedin', with: 'testlinkedin'
+ fill_in 'user_twitter', with: 'testtwitter'
+ fill_in 'user_website_url', with: 'testurl'
+ fill_in 'user_location', with: 'Ukraine'
+ fill_in 'user_bio', with: 'I <3 GitLab'
+ click_button 'Save changes'
@user.reload
end
step 'I should see new profile info' do
- @user.skype.should == 'testskype'
- @user.linkedin.should == 'testlinkedin'
- @user.twitter.should == 'testtwitter'
- @user.website_url.should == 'testurl'
- find("#user_location").value.should == "Ukraine"
+ expect(@user.skype).to eq 'testskype'
+ expect(@user.linkedin).to eq 'testlinkedin'
+ expect(@user.twitter).to eq 'testtwitter'
+ expect(@user.website_url).to eq 'testurl'
+ expect(@user.bio).to eq 'I <3 GitLab'
+ find('#user_location').value.should == 'Ukraine'
end
step 'I change my avatar' do
@@ -166,7 +168,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I click on my profile picture' do
- click_link 'profile-pic'
+ find(:css, '.sidebar-user').click
end
step 'I should see my user page' do
diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb
index c888e82e207..30d53333b78 100644
--- a/features/steps/project/commits/commits.rb
+++ b/features/steps/project/commits/commits.rb
@@ -69,14 +69,13 @@ class Spinach::Features::ProjectCommits < Spinach::FeatureSteps
end
step 'I visit big commit page' do
- Commit::DIFF_SAFE_FILES = 20
+ stub_const('Commit::DIFF_SAFE_FILES', 20)
visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id)
end
step 'I see big commit warning' do
page.should have_content sample_big_commit.message
page.should have_content "Too many changes"
- Commit::DIFF_SAFE_FILES = 100
end
step 'I visit a commit with an image that changed' do
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 48bb316e203..4ca7cf5e5fe 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -305,6 +305,20 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
fill_in 'issue_search', with: "Fe"
end
+ step 'I click the "Target branch" dropdown' do
+ first('.target_branch').click
+ end
+
+ step 'I select a new target branch' do
+ select "feature", from: "merge_request_target_branch"
+ click_button 'Save'
+ end
+
+ step 'I should see new target branch changes' do
+ page.should have_content 'From fix into feature'
+ page.should have_content 'Target branch changed from master to feature'
+ end
+
def merge_request
@merge_request ||= MergeRequest.find_by!(title: "Bug NS-05")
end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index d9401bd540c..46493876805 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -227,6 +227,10 @@ module SharedPaths
visit namespace_project_merge_requests_path(@project.namespace, @project)
end
+ step "I visit my project's members page" do
+ visit namespace_project_project_members_path(@project.namespace, @project)
+ end
+
step "I visit my project's wiki page" do
visit namespace_project_wiki_path(@project.namespace, @project, :home)
end
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index c5aed19331c..71fe89f634f 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -28,6 +28,10 @@ module SharedProjectTab
ensure_active_main_tab('Issues')
end
+ step 'the active main tab should be Members' do
+ ensure_active_main_tab('Members')
+ end
+
step 'the active main tab should be Merge Requests' do
ensure_active_main_tab('Merge Requests')
end
diff --git a/features/support/env.rb b/features/support/env.rb
index f34302721ed..d4a878ea4ce 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -9,7 +9,6 @@ end
ENV['RAILS_ENV'] = 'test'
require './config/environment'
-require 'rspec'
require 'rspec/expectations'
require 'sidekiq/testing/inline'
diff --git a/lib/api/commits.rb b/lib/api/commits.rb
index 23270b1c0f4..f4efb651eb6 100644
--- a/lib/api/commits.rb
+++ b/lib/api/commits.rb
@@ -62,7 +62,7 @@ module API
sha = params[:sha]
commit = user_project.commit(sha)
not_found! 'Commit' unless commit
- notes = Note.where(commit_id: commit.id)
+ notes = Note.where(commit_id: commit.id).order(:created_at)
present paginate(notes), with: Entities::CommitNote
end
diff --git a/lib/api/files.rb b/lib/api/files.rb
index e0ea6d7dd1d..c7b30cf2f07 100644
--- a/lib/api/files.rb
+++ b/lib/api/files.rb
@@ -3,6 +3,26 @@ module API
class Files < Grape::API
before { authenticate! }
+ helpers do
+ def commit_params(attrs)
+ {
+ file_path: attrs[:file_path],
+ current_branch: attrs[:branch_name],
+ target_branch: attrs[:branch_name],
+ commit_message: attrs[:commit_message],
+ file_content: attrs[:content],
+ file_content_encoding: attrs[:encoding]
+ }
+ end
+
+ def commit_response(attrs)
+ {
+ file_path: attrs[:file_path],
+ branch_name: attrs[:branch_name],
+ }
+ end
+ end
+
resource :projects do
# Get file from repository
# File content is Base64 encoded
@@ -73,17 +93,11 @@ module API
required_attributes! [:file_path, :branch_name, :content, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
- branch_name = attrs.delete(:branch_name)
- file_path = attrs.delete(:file_path)
- result = ::Files::CreateService.new(user_project, current_user, attrs, branch_name, file_path).execute
+ result = ::Files::CreateService.new(user_project, current_user, commit_params(attrs)).execute
if result[:status] == :success
status(201)
-
- {
- file_path: file_path,
- branch_name: branch_name
- }
+ commit_response(attrs)
else
render_api_error!(result[:message], 400)
end
@@ -105,17 +119,11 @@ module API
required_attributes! [:file_path, :branch_name, :content, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :content, :commit_message, :encoding]
- branch_name = attrs.delete(:branch_name)
- file_path = attrs.delete(:file_path)
- result = ::Files::UpdateService.new(user_project, current_user, attrs, branch_name, file_path).execute
+ result = ::Files::UpdateService.new(user_project, current_user, commit_params(attrs)).execute
if result[:status] == :success
status(200)
-
- {
- file_path: file_path,
- branch_name: branch_name
- }
+ commit_response(attrs)
else
http_status = result[:http_status] || 400
render_api_error!(result[:message], http_status)
@@ -138,17 +146,11 @@ module API
required_attributes! [:file_path, :branch_name, :commit_message]
attrs = attributes_for_keys [:file_path, :branch_name, :commit_message]
- branch_name = attrs.delete(:branch_name)
- file_path = attrs.delete(:file_path)
- result = ::Files::DeleteService.new(user_project, current_user, attrs, branch_name, file_path).execute
+ result = ::Files::DeleteService.new(user_project, current_user, commit_params(attrs)).execute
if result[:status] == :success
status(200)
-
- {
- file_path: file_path,
- branch_name: branch_name
- }
+ commit_response(attrs)
else
render_api_error!(result[:message], 400)
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index f768c750402..e88b6e31775 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -62,7 +62,7 @@ module API
delete ":id" do
group = find_group(params[:id])
authorize! :admin_group, group
- group.destroy
+ DestroyGroupService.new(group, current_user).execute
end
# Transfer a project to the Group namespace
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index 2216a12a87a..d835dce2ded 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -137,7 +137,6 @@ module API
# Parameters:
# id (required) - The ID of a project
# merge_request_id (required) - ID of MR
- # source_branch - The source branch
# target_branch - The target branch
# assignee_id - Assignee user ID
# title - Title of MR
@@ -148,10 +147,15 @@ module API
# PUT /projects/:id/merge_request/:merge_request_id
#
put ":id/merge_request/:merge_request_id" do
- attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :state_event, :description]
+ attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description]
merge_request = user_project.merge_requests.find(params[:merge_request_id])
authorize! :modify_merge_request, merge_request
+ # Ensure source_branch is not specified
+ if params[:source_branch].present?
+ render_api_error!('Source branch cannot be changed', 400)
+ end
+
# Validate label names in advance
if (errors = validate_label_params(params)).any?
render_api_error!({ labels: errors }, 400)
diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb
index b90ed6af5fb..50d3729449e 100644
--- a/lib/api/namespaces.rb
+++ b/lib/api/namespaces.rb
@@ -1,10 +1,7 @@
module API
# namespaces API
class Namespaces < Grape::API
- before do
- authenticate!
- authenticated_as_admin!
- end
+ before { authenticate! }
resource :namespaces do
# Get a namespaces list
@@ -12,7 +9,11 @@ module API
# Example Request:
# GET /namespaces
get do
- @namespaces = Namespace.all
+ @namespaces = if current_user.admin
+ Namespace.all
+ else
+ current_user.namespaces
+ end
@namespaces = @namespaces.search(params[:search]) if params[:search].present?
@namespaces = paginate @namespaces
diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb
index b69aebf9fe1..6fa2079d1a8 100644
--- a/lib/backup/manager.rb
+++ b/lib/backup/manager.rb
@@ -46,7 +46,8 @@ module Backup
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)
+ if directory.files.create(key: tar_file, body: File.open(tar_file), public: false,
+ multipart_chunk_size: Gitlab.config.backup.upload.multipart_chunk_size)
$progress.puts "done".green
else
puts "uploading backup to #{remote_directory} failed".red
diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb
index 530f9d93de4..172d4902add 100644
--- a/lib/gitlab/backend/shell.rb
+++ b/lib/gitlab/backend/shell.rb
@@ -244,6 +244,16 @@ module Gitlab
end
end
+ # Check if such directory exists in repositories.
+ #
+ # Usage:
+ # exists?('gitlab')
+ # exists?('gitlab/cookies.git')
+ #
+ def exists?(dir_name)
+ File.exists?(full_path(dir_name))
+ end
+
protected
def gitlab_shell_path
@@ -264,10 +274,6 @@ module Gitlab
File.join(repos_path, dir_name)
end
- def exists?(dir_name)
- File.exists?(full_path(dir_name))
- end
-
def gitlab_shell_projects_path
File.join(gitlab_shell_path, 'bin', 'gitlab-projects')
end
diff --git a/lib/gitlab/gitorious_import.rb b/lib/gitlab/gitorious_import.rb
new file mode 100644
index 00000000000..8d0132a744c
--- /dev/null
+++ b/lib/gitlab/gitorious_import.rb
@@ -0,0 +1,5 @@
+module Gitlab
+ module GitoriousImport
+ GITORIOUS_HOST = "https://gitorious.org"
+ end
+end
diff --git a/lib/gitlab/gitorious_import/client.rb b/lib/gitlab/gitorious_import/client.rb
index 1fa89dba448..99fe5bdebfc 100644
--- a/lib/gitlab/gitorious_import/client.rb
+++ b/lib/gitlab/gitorious_import/client.rb
@@ -1,7 +1,5 @@
module Gitlab
module GitoriousImport
- GITORIOUS_HOST = "https://gitorious.org"
-
class Client
attr_reader :repo_list
diff --git a/lib/gitlab/gitorious_import/repository.rb b/lib/gitlab/gitorious_import/repository.rb
index f702797dc6e..c88f1ae358d 100644
--- a/lib/gitlab/gitorious_import/repository.rb
+++ b/lib/gitlab/gitorious_import/repository.rb
@@ -1,7 +1,5 @@
module Gitlab
module GitoriousImport
- GITORIOUS_HOST = "https://gitorious.org"
-
Repository = Struct.new(:full_name) do
def id
Digest::SHA1.hexdigest(full_name)
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index 5db1566f55d..fa9c0975bb8 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -57,6 +57,9 @@ module Gitlab
pipeline = HTML::Pipeline.new(filters)
context = {
+ # SanitizationFilter
+ pipeline: options[:pipeline],
+
# EmojiFilter
asset_root: Gitlab.config.gitlab.url,
asset_host: Gitlab::Application.config.asset_host,
diff --git a/lib/gitlab/markdown/reference_filter.rb b/lib/gitlab/markdown/reference_filter.rb
index be4d26af0fc..a84bacd3d4f 100644
--- a/lib/gitlab/markdown/reference_filter.rb
+++ b/lib/gitlab/markdown/reference_filter.rb
@@ -25,12 +25,18 @@ module Gitlab
ERB::Util.html_escape_once(html)
end
- # Don't look for references in text nodes that are children of these
- # elements.
- IGNORE_PARENTS = %w(pre code a style).to_set
+ def ignore_parents
+ @ignore_parents ||= begin
+ # Don't look for references in text nodes that are children of these
+ # elements.
+ parents = %w(pre code a style)
+ parents << 'blockquote' if context[:ignore_blockquotes]
+ parents.to_set
+ end
+ end
def ignored_ancestry?(node)
- has_ancestor?(node, IGNORE_PARENTS)
+ has_ancestor?(node, ignore_parents)
end
def project
diff --git a/lib/gitlab/markdown/sanitization_filter.rb b/lib/gitlab/markdown/sanitization_filter.rb
index 88781fea0c8..74b3a8d274f 100644
--- a/lib/gitlab/markdown/sanitization_filter.rb
+++ b/lib/gitlab/markdown/sanitization_filter.rb
@@ -8,33 +8,54 @@ module Gitlab
# Extends HTML::Pipeline::SanitizationFilter with a custom whitelist.
class SanitizationFilter < HTML::Pipeline::SanitizationFilter
def whitelist
- whitelist = super
+ # Descriptions are more heavily sanitized, allowing only a few elements.
+ # See http://git.io/vkuAN
+ if pipeline == :description
+ whitelist = LIMITED
+ whitelist[:elements] -= %w(pre code img ol ul li)
+ else
+ whitelist = super
+ end
+
+ customize_whitelist(whitelist)
+
+ whitelist
+ end
+ private
+
+ def pipeline
+ context[:pipeline] || :default
+ end
+
+ def customized?(transformers)
+ transformers.last.source_location[0] == __FILE__
+ end
+
+ def customize_whitelist(whitelist)
# Only push these customizations once
- unless customized?(whitelist[:transformers])
- # Allow code highlighting
- whitelist[:attributes]['pre'] = %w(class)
- whitelist[:attributes]['span'] = %w(class)
+ return if customized?(whitelist[:transformers])
- # Allow table alignment
- whitelist[:attributes]['th'] = %w(style)
- whitelist[:attributes]['td'] = %w(style)
+ # Allow code highlighting
+ whitelist[:attributes]['pre'] = %w(class)
+ whitelist[:attributes]['span'] = %w(class)
- # Allow span elements
- whitelist[:elements].push('span')
+ # Allow table alignment
+ whitelist[:attributes]['th'] = %w(style)
+ whitelist[:attributes]['td'] = %w(style)
- # Remove `rel` attribute from `a` elements
- whitelist[:transformers].push(remove_rel)
+ # Allow span elements
+ whitelist[:elements].push('span')
- # Remove `class` attribute from non-highlight spans
- whitelist[:transformers].push(clean_spans)
- end
+ # Remove `rel` attribute from `a` elements
+ whitelist[:transformers].push(remove_rel)
+
+ # Remove `class` attribute from non-highlight spans
+ whitelist[:transformers].push(clean_spans)
whitelist
end
- private
-
def remove_rel
lambda do |env|
if env[:node_name] == 'a'
@@ -53,10 +74,6 @@ module Gitlab
end
end
end
-
- def customized?(transformers)
- transformers.last.source_location[0] == __FILE__
- end
end
end
end
diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb
index ba5caed6131..c4971b5bcc6 100644
--- a/lib/gitlab/o_auth/user.rb
+++ b/lib/gitlab/o_auth/user.rb
@@ -46,6 +46,10 @@ module Gitlab
def gl_user
@user ||= find_by_uid_and_provider
+ if auto_link_ldap_user?
+ @user ||= find_or_create_ldap_user
+ end
+
if signup_enabled?
@user ||= build_new_user
end
@@ -55,6 +59,46 @@ module Gitlab
protected
+ def find_or_create_ldap_user
+ return unless ldap_person
+
+ # If a corresponding person exists with same uid in a LDAP server,
+ # set up a Gitlab user with dual LDAP and Omniauth identities.
+ if user = Gitlab::LDAP::User.find_by_uid_and_provider(ldap_person.dn.downcase, ldap_person.provider)
+ # Case when a LDAP user already exists in Gitlab. Add the Omniauth identity to existing account.
+ user.identities.build(extern_uid: auth_hash.uid, provider: auth_hash.provider)
+ else
+ # No account in Gitlab yet: create it and add the LDAP identity
+ user = build_new_user
+ user.identities.new(provider: ldap_person.provider, extern_uid: ldap_person.dn)
+ end
+
+ user
+ end
+
+ def auto_link_ldap_user?
+ Gitlab.config.omniauth.auto_link_ldap_user
+ end
+
+ def creating_linked_ldap_user?
+ auto_link_ldap_user? && ldap_person
+ end
+
+ def ldap_person
+ return @ldap_person if defined?(@ldap_person)
+
+ # looks for a corresponding person with same uid in any of the configured LDAP providers
+ @ldap_person = Gitlab::LDAP::Config.providers.find do |provider|
+ adapter = Gitlab::LDAP::Adapter.new(provider)
+
+ Gitlab::LDAP::Person.find_by_uid(auth_hash.uid, adapter)
+ end
+ end
+
+ def ldap_config
+ Gitlab::LDAP::Config.new(ldap_person.provider) if ldap_person
+ end
+
def needs_blocking?
new? && block_after_signup?
end
@@ -64,7 +108,11 @@ module Gitlab
end
def block_after_signup?
- Gitlab.config.omniauth.block_auto_created_users
+ if creating_linked_ldap_user?
+ ldap_config.block_auto_created_users
+ else
+ Gitlab.config.omniauth.block_auto_created_users
+ end
end
def auth_hash=(auth_hash)
@@ -84,10 +132,19 @@ module Gitlab
end
def user_attributes
+ # Give preference to LDAP for sensitive information when creating a linked account
+ if creating_linked_ldap_user?
+ username = ldap_person.username
+ email = ldap_person.email.first
+ else
+ username = auth_hash.username
+ email = auth_hash.email
+ end
+
{
name: auth_hash.name,
- username: ::Namespace.clean_path(auth_hash.username),
- email: auth_hash.email,
+ username: ::Namespace.clean_path(username),
+ email: email,
password: auth_hash.password,
password_confirmation: auth_hash.password,
password_automatically_set: true
diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb
index e35f848fa6e..e836b05ff25 100644
--- a/lib/gitlab/reference_extractor.rb
+++ b/lib/gitlab/reference_extractor.rb
@@ -1,7 +1,7 @@
module Gitlab
# Extract possible GFM references from an arbitrary String for further processing.
class ReferenceExtractor
- attr_accessor :project, :current_user, :references
+ attr_accessor :project, :current_user
def initialize(project, current_user = nil)
@project = project
@@ -9,48 +9,31 @@ module Gitlab
end
def analyze(text)
- @_text = text.dup
+ references.clear
+ @text = markdown.render(text.dup)
end
- def users
- result = pipeline_result(:user)
- result.uniq
+ %i(user label issue merge_request snippet commit commit_range).each do |type|
+ define_method("#{type}s") do
+ references[type]
+ end
end
- def labels
- result = pipeline_result(:label)
- result.uniq
- end
-
- def issues
- # TODO (rspeicher): What about external issues?
-
- result = pipeline_result(:issue)
- result.uniq
- end
-
- def merge_requests
- result = pipeline_result(:merge_request)
- result.uniq
- end
+ private
- def snippets
- result = pipeline_result(:snippet)
- result.uniq
+ def markdown
+ @markdown ||= Redcarpet::Markdown.new(Redcarpet::Render::HTML, GitlabMarkdownHelper::MARKDOWN_OPTIONS)
end
- def commits
- result = pipeline_result(:commit)
- result.uniq
- end
+ def references
+ @references ||= Hash.new do |references, type|
+ type = type.to_sym
+ return references[type] if references.has_key?(type)
- def commit_ranges
- result = pipeline_result(:commit_range)
- result.uniq
+ references[type] = pipeline_result(type).uniq
+ end
end
- private
-
# Instantiate and call HTML::Pipeline with a single reference filter type,
# returning the result
#
@@ -65,11 +48,12 @@ module Gitlab
project: project,
current_user: current_user,
# We don't actually care about the links generated
- only_path: true
+ only_path: true,
+ ignore_blockquotes: true
}
pipeline = HTML::Pipeline.new([filter], context)
- result = pipeline.call(@_text)
+ result = pipeline.call(@text)
result[:references][filter_type]
end
diff --git a/lib/gitlab/satellite/files/delete_file_action.rb b/lib/gitlab/satellite/files/delete_file_action.rb
deleted file mode 100644
index 0d37b9dea85..00000000000
--- a/lib/gitlab/satellite/files/delete_file_action.rb
+++ /dev/null
@@ -1,50 +0,0 @@
-require_relative 'file_action'
-
-module Gitlab
- module Satellite
- class DeleteFileAction < FileAction
- # Deletes file and creates a new commit for it
- #
- # Returns false if committing the change fails
- # Returns false if pushing from the satellite to bare repo failed or was rejected
- # Returns true otherwise
- def commit!(content, commit_message)
- in_locked_and_timed_satellite do |repo|
- prepare_satellite!(repo)
-
- # create target branch in satellite at the corresponding commit from bare repo
- repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}")
-
- # update the file in the satellite's working dir
- file_path_in_satellite = File.join(repo.working_dir, file_path)
-
- # Prevent relative links
- unless safe_path?(file_path_in_satellite)
- Gitlab::GitLogger.error("FileAction: Relative path not allowed")
- return false
- end
-
- File.delete(file_path_in_satellite)
-
- # add removed file
- repo.remove(file_path_in_satellite)
-
- # commit the changes
- # will raise CommandFailed when commit fails
- repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
-
-
- # push commit back to bare repo
- # will raise CommandFailed when push fails
- repo.git.push({ raise: true, timeout: true }, :origin, ref)
-
- # everything worked
- true
- end
- rescue Grit::Git::CommandFailed => ex
- Gitlab::GitLogger.error(ex.message)
- false
- end
- end
- end
-end
diff --git a/lib/gitlab/satellite/files/edit_file_action.rb b/lib/gitlab/satellite/files/edit_file_action.rb
deleted file mode 100644
index 3cb9c0b5ecb..00000000000
--- a/lib/gitlab/satellite/files/edit_file_action.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-require_relative 'file_action'
-
-module Gitlab
- module Satellite
- # GitLab server-side file update and commit
- class EditFileAction < FileAction
- # Updates the files content and creates a new commit for it
- #
- # Returns false if the ref has been updated while editing the file
- # Returns false if committing the change fails
- # Returns false if pushing from the satellite to bare repo failed or was rejected
- # Returns true otherwise
- def commit!(content, commit_message, encoding, new_branch = nil)
- in_locked_and_timed_satellite do |repo|
- prepare_satellite!(repo)
-
- # create target branch in satellite at the corresponding commit from bare repo
- begin
- repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}")
- rescue Grit::Git::CommandFailed => ex
- log_and_raise(CheckoutFailed, ex.message)
- end
-
- # update the file in the satellite's working dir
- file_path_in_satellite = File.join(repo.working_dir, file_path)
-
- # Prevent relative links
- unless safe_path?(file_path_in_satellite)
- Gitlab::GitLogger.error("FileAction: Relative path not allowed")
- return false
- end
-
- # Write file
- write_file(file_path_in_satellite, content, encoding)
-
- # commit the changes
- # will raise CommandFailed when commit fails
- begin
- repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
- rescue Grit::Git::CommandFailed => ex
- log_and_raise(CommitFailed, ex.message)
- end
-
-
- target_branch = new_branch.present? ? "#{ref}:#{new_branch}" : ref
-
- # push commit back to bare repo
- # will raise CommandFailed when push fails
- begin
- repo.git.push({ raise: true, timeout: true }, :origin, target_branch)
- rescue Grit::Git::CommandFailed => ex
- log_and_raise(PushFailed, ex.message)
- end
-
- # everything worked
- true
- end
- end
-
- private
-
- def log_and_raise(errorClass, message)
- Gitlab::GitLogger.error(message)
- raise(errorClass, message)
- end
- end
- end
-end
diff --git a/lib/gitlab/satellite/files/file_action.rb b/lib/gitlab/satellite/files/file_action.rb
deleted file mode 100644
index 6446b14568a..00000000000
--- a/lib/gitlab/satellite/files/file_action.rb
+++ /dev/null
@@ -1,25 +0,0 @@
-module Gitlab
- module Satellite
- class FileAction < Action
- attr_accessor :file_path, :ref
-
- def initialize(user, project, ref, file_path)
- super user, project
- @file_path = file_path
- @ref = ref
- end
-
- def safe_path?(path)
- File.absolute_path(path) == path
- end
-
- def write_file(abs_file_path, content, file_encoding = 'text')
- if file_encoding == 'base64'
- File.open(abs_file_path, 'wb') { |f| f.write(Base64.decode64(content)) }
- else
- File.open(abs_file_path, 'w') { |f| f.write(content) }
- end
- end
- end
- end
-end
diff --git a/lib/gitlab/satellite/files/new_file_action.rb b/lib/gitlab/satellite/files/new_file_action.rb
deleted file mode 100644
index 724dfa0d042..00000000000
--- a/lib/gitlab/satellite/files/new_file_action.rb
+++ /dev/null
@@ -1,67 +0,0 @@
-require_relative 'file_action'
-
-module Gitlab
- module Satellite
- class NewFileAction < FileAction
- # Updates the files content and creates a new commit for it
- #
- # Returns false if the ref has been updated while editing the file
- # Returns false if committing the change fails
- # Returns false if pushing from the satellite to bare repo failed or was rejected
- # Returns true otherwise
- def commit!(content, commit_message, encoding, new_branch = nil)
- in_locked_and_timed_satellite do |repo|
- prepare_satellite!(repo)
-
- # create target branch in satellite at the corresponding commit from bare repo
- current_ref =
- if @project.empty_repo?
- # skip this step if we want to add first file to empty repo
- Satellite::PARKING_BRANCH
- else
- repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}")
- ref
- end
-
- file_path_in_satellite = File.join(repo.working_dir, file_path)
- dir_name_in_satellite = File.dirname(file_path_in_satellite)
-
- # Prevent relative links
- unless safe_path?(file_path_in_satellite)
- Gitlab::GitLogger.error("FileAction: Relative path not allowed")
- return false
- end
-
- # Create dir if not exists
- FileUtils.mkdir_p(dir_name_in_satellite)
-
- # Write file
- write_file(file_path_in_satellite, content, encoding)
-
- # add new file
- repo.add(file_path_in_satellite)
-
- # commit the changes
- # will raise CommandFailed when commit fails
- repo.git.commit(raise: true, timeout: true, a: true, m: commit_message)
-
- target_branch = if new_branch.present? && !@project.empty_repo?
- "#{ref}:#{new_branch}"
- else
- "#{current_ref}:#{ref}"
- end
-
- # push commit back to bare repo
- # will raise CommandFailed when push fails
- repo.git.push({ raise: true, timeout: true }, :origin, target_branch)
-
- # everything worked
- true
- end
- rescue Grit::Git::CommandFailed => ex
- Gitlab::GitLogger.error(ex.message)
- false
- end
- end
- end
-end
diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb
index 0570c2fbeb5..cf040971c6e 100644
--- a/lib/gitlab/upgrader.rb
+++ b/lib/gitlab/upgrader.rb
@@ -43,10 +43,15 @@ module Gitlab
end
def latest_version_raw
+ git_tags = fetch_git_tags
+ git_tags = git_tags.select { |version| version =~ /v\d+\.\d+\.\d+\Z/ }
+ git_versions = git_tags.map { |tag| Gitlab::VersionInfo.parse(tag.match(/v\d+\.\d+\.\d+/).to_s) }
+ "v#{git_versions.sort.last.to_s}"
+ end
+
+ def fetch_git_tags
remote_tags, _ = Gitlab::Popen.popen(%W(git ls-remote --tags https://gitlab.com/gitlab-org/gitlab-ce.git))
- git_tags = remote_tags.split("\n").grep(/tags\/v#{current_version.major}/)
- git_tags = git_tags.select { |version| version =~ /v\d\.\d\.\d\Z/ }
- last_tag = git_tags.last.match(/v\d\.\d\.\d/).to_s
+ remote_tags.split("\n").grep(/tags\/v#{current_version.major}/)
end
def update_commands
diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb
index 7dcecc2ecf6..2f7aff03c2a 100644
--- a/lib/redcarpet/render/gitlab_html.rb
+++ b/lib/redcarpet/render/gitlab_html.rb
@@ -10,6 +10,8 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML
@options = options.dup
@options.reverse_merge!(
+ # Handled further down the line by Gitlab::Markdown::SanitizationFilter
+ escape_html: false,
project: @template.instance_variable_get("@project")
)
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index b066a1a6935..946902e2f6d 100755
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -35,13 +35,14 @@ pid_path="$app_root/tmp/pids"
socket_path="$app_root/tmp/sockets"
web_server_pid_path="$pid_path/unicorn.pid"
sidekiq_pid_path="$pid_path/sidekiq.pid"
+shell_path="/bin/bash"
# Read configuration variable file if it is present
test -f /etc/default/gitlab && . /etc/default/gitlab
# Switch to the app_user if it is not he/she who is running the script.
if [ "$USER" != "$app_user" ]; then
- eval su - "$app_user" -c $(echo \")$0 "$@"$(echo \"); exit;
+ eval su - "$app_user" -s $shell_path -c $(echo \")$0 "$@"$(echo \"); exit;
fi
# Switch to the gitlab path, exit on failure.
diff --git a/lib/support/init.d/gitlab.default.example b/lib/support/init.d/gitlab.default.example
index 9951bacedf5..cf7f4198cbf 100755
--- a/lib/support/init.d/gitlab.default.example
+++ b/lib/support/init.d/gitlab.default.example
@@ -29,3 +29,8 @@ web_server_pid_path="$pid_path/unicorn.pid"
# sidekiq_pid_path defines the path in which to create the pid file for sidekiq
# The default is "$pid_path/sidekiq.pid"
sidekiq_pid_path="$pid_path/sidekiq.pid"
+
+# shell_path defines the path of shell for "$app_user" in case you are using
+# shell other than "bash"
+# The default is "/bin/bash"
+shell_path="/bin/bash"
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 1a6303b6c82..75bd41f2838 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -1,7 +1,6 @@
namespace :gitlab do
desc "GITLAB | Check the configuration of GitLab and its environment"
- task check: %w{gitlab:env:check
- gitlab:gitlab_shell:check
+ task check: %w{gitlab:gitlab_shell:check
gitlab:sidekiq:check
gitlab:ldap:check
gitlab:app:check}
@@ -14,6 +13,7 @@ namespace :gitlab do
warn_user_is_not_gitlab
start_checking "GitLab"
+ check_git_config
check_database_config_exists
check_database_is_not_sqlite
check_migrations_are_up
@@ -38,6 +38,36 @@ namespace :gitlab do
# Checks
########################
+ def check_git_config
+ print "Git configured with autocrlf=input? ... "
+
+ options = {
+ "core.autocrlf" => "input"
+ }
+
+ correct_options = options.map do |name, value|
+ run(%W(#{Gitlab.config.git.bin_path} config --global --get #{name})).try(:squish) == value
+ end
+
+ if correct_options.all?
+ puts "yes".green
+ else
+ print "Trying to fix Git error automatically. ..."
+
+ if auto_fix_git_config(options)
+ puts "Success".green
+ else
+ puts "Failed".red
+ try_fixing_it(
+ sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global core.autocrlf \"#{options["core.autocrlf"]}\"")
+ )
+ for_more_information(
+ see_installation_guide_section "GitLab"
+ )
+ end
+ end
+ end
+
def check_database_config_exists
print "Database config exists? ... "
@@ -298,58 +328,6 @@ namespace :gitlab do
end
end
-
-
- namespace :env do
- desc "GITLAB | Check the configuration of the environment"
- task check: :environment do
- warn_user_is_not_gitlab
- start_checking "Environment"
-
- check_gitlab_git_config
-
- finished_checking "Environment"
- end
-
-
- # Checks
- ########################
-
- def check_gitlab_git_config
- print "Git configured for #{gitlab_user} user? ... "
-
- options = {
- "user.name" => "GitLab",
- "user.email" => Gitlab.config.gitlab.email_from,
- "core.autocrlf" => "input"
- }
- correct_options = options.map do |name, value|
- run(%W(#{Gitlab.config.git.bin_path} config --global --get #{name})).try(:squish) == value
- end
-
- if correct_options.all?
- puts "yes".green
- else
- print "Trying to fix Git error automatically. ..."
- if auto_fix_git_config(options)
- puts "Success".green
- else
- puts "Failed".red
- try_fixing_it(
- sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global user.name \"#{options["user.name"]}\""),
- sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global user.email \"#{options["user.email"]}\""),
- sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global core.autocrlf \"#{options["core.autocrlf"]}\"")
- )
- for_more_information(
- see_installation_guide_section "GitLab"
- )
- end
- end
- end
- end
-
-
-
namespace :gitlab_shell do
desc "GITLAB | Check the configuration of GitLab Shell"
task check: :environment do
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index e835d6cb9b7..afdaba11cb0 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -59,6 +59,9 @@ namespace :gitlab do
# Launch installation process
system(*%W(bin/install))
+
+ # (Re)create hooks
+ system(*%W(bin/create-hooks))
end
# Required for debian packaging with PKGR: Setup .ssh/environment with
diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake
index 14a130be2ca..c95b6540ebc 100644
--- a/lib/tasks/gitlab/task_helpers.rake
+++ b/lib/tasks/gitlab/task_helpers.rake
@@ -118,9 +118,9 @@ namespace :gitlab do
# Returns true if all subcommands were successfull (according to their exit code)
# Returns false if any or all subcommands failed.
def auto_fix_git_config(options)
- if !@warned_user_not_gitlab && options['user.email'] != 'example@example.com' # default email should be overridden?
+ if !@warned_user_not_gitlab
command_success = options.map do |name, value|
- system(%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value}))
+ system(*%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value}))
end
command_success.all?
diff --git a/lib/tasks/jasmine.rake b/lib/tasks/jasmine.rake
index 9e2cceffa19..ac307a9e929 100644
--- a/lib/tasks/jasmine.rake
+++ b/lib/tasks/jasmine.rake
@@ -7,6 +7,6 @@ task jasmine: ['jasmine:ci']
namespace :jasmine do
task :ci do
- Rake::Task['spec:javascript'].invoke
+ Rake::Task['teaspoon'].invoke
end
end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
new file mode 100644
index 00000000000..edc1c63a0aa
--- /dev/null
+++ b/spec/features/groups_spec.rb
@@ -0,0 +1,36 @@
+require 'spec_helper'
+
+feature 'Group' do
+ describe 'description' do
+ let(:group) { create(:group) }
+ let(:path) { group_path(group) }
+
+ before do
+ login_as(:admin)
+ end
+
+ it 'parses Markdown' do
+ group.update_attribute(:description, 'This is **my** group')
+ visit path
+ expect(page).to have_css('.description > p > strong')
+ end
+
+ it 'passes through html-pipeline' do
+ group.update_attribute(:description, 'This group is the :poop:')
+ visit path
+ expect(page).to have_css('.description > p > img')
+ end
+
+ it 'sanitizes unwanted tags' do
+ group.update_attribute(:description, '# Group Description')
+ visit path
+ expect(page).not_to have_css('.description h1')
+ end
+
+ it 'permits `rel` attribute on links' do
+ group.update_attribute(:description, 'https://google.com/')
+ visit path
+ expect(page).to have_css('.description a[rel]')
+ end
+ end
+end
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index ee1b3bf749d..902968cebcb 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -18,11 +18,13 @@ require 'erb'
# -> `gfm_with_options` helper
# -> HTML::Pipeline
# -> Sanitize
+# -> RelativeLink
# -> Emoji
# -> Table of Contents
# -> Autolinks
# -> Rinku (http, https, ftp)
# -> Other schemes
+# -> ExternalLink
# -> References
# -> TaskList
# -> `html_safe`
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index cae11be7cdd..f8eea70ec4a 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,32 +1,57 @@
require 'spec_helper'
-describe "Projects", feature: true, js: true do
- before { login_as :user }
+feature 'Project' do
+ describe 'description' do
+ let(:project) { create(:project) }
+ let(:path) { namespace_project_path(project.namespace, project) }
- describe "DELETE /projects/:id" do
before do
- @project = create(:project, namespace: @user.namespace)
- @project.team << [@user, :master]
- visit edit_namespace_project_path(@project.namespace, @project)
+ login_as(:admin)
end
- it "should remove project" do
- expect { remove_project }.to change {Project.count}.by(-1)
+ it 'parses Markdown' do
+ project.update_attribute(:description, 'This is **my** project')
+ visit path
+ expect(page).to have_css('.project-home-desc > p > strong')
+ end
+
+ it 'passes through html-pipeline' do
+ project.update_attribute(:description, 'This project is the :poop:')
+ visit path
+ expect(page).to have_css('.project-home-desc > p > img')
+ end
+
+ it 'sanitizes unwanted tags' do
+ project.update_attribute(:description, '# Project Description')
+ visit path
+ expect(page).not_to have_css('.project-home-desc h1')
+ end
+
+ it 'permits `rel` attribute on links' do
+ project.update_attribute(:description, 'https://google.com/')
+ visit path
+ expect(page).to have_css('.project-home-desc a[rel]')
end
+ end
+
+ describe 'removal', js: true do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
- it 'should delete the project from disk' do
- expect(GitlabShellWorker).to(
- receive(:perform_async).with(:remove_repository,
- /#{@project.path_with_namespace}/)
- ).twice
+ before do
+ login_with(user)
+ project.team << [user, :master]
+ visit edit_namespace_project_path(project.namespace, project)
+ end
- remove_project
+ it 'should remove project' do
+ expect { remove_project }.to change {Project.count}.by(-1)
end
end
def remove_project
click_link "Remove project"
- fill_in 'confirm_name_input', with: @project.path
+ fill_in 'confirm_name_input', with: project.path
click_button 'Confirm'
end
end
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index d0b200a9ff8..bbb434638ce 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -94,6 +94,12 @@ describe GitlabMarkdownHelper do
expect(link_to_gfm(actual, commit_path)).
to match('&lt;h1&gt;test&lt;/h1&gt;')
end
+
+ it 'ignores reference links when they are the entire body' do
+ text = issues[0].to_reference
+ act = link_to_gfm(text, '/foo')
+ expect(act).to eq %Q(<a href="/foo">#{issues[0].to_reference}</a>)
+ end
end
describe '#render_wiki_content' do
diff --git a/spec/javascripts/extensions/array_spec.js.coffee b/spec/javascripts/extensions/array_spec.js.coffee
new file mode 100644
index 00000000000..4ceac619422
--- /dev/null
+++ b/spec/javascripts/extensions/array_spec.js.coffee
@@ -0,0 +1,12 @@
+#= require extensions/array
+
+describe 'Array extensions', ->
+ describe 'first', ->
+ it 'returns the first item', ->
+ arr = [0, 1, 2, 3, 4, 5]
+ expect(arr.first()).toBe(0)
+
+ describe 'last', ->
+ it 'returns the last item', ->
+ arr = [0, 1, 2, 3, 4, 5]
+ expect(arr.last()).toBe(5)
diff --git a/spec/javascripts/extensions/jquery_spec.js.coffee b/spec/javascripts/extensions/jquery_spec.js.coffee
new file mode 100644
index 00000000000..b10e16b7d01
--- /dev/null
+++ b/spec/javascripts/extensions/jquery_spec.js.coffee
@@ -0,0 +1,34 @@
+#= require extensions/jquery
+
+describe 'jQuery extensions', ->
+ describe 'disable', ->
+ beforeEach ->
+ fixture.set '<input type="text" />'
+
+ it 'adds the disabled attribute', ->
+ $input = $('input').first()
+
+ $input.disable()
+ expect($input).toHaveAttr('disabled', 'disabled')
+
+ it 'adds the disabled class', ->
+ $input = $('input').first()
+
+ $input.disable()
+ expect($input).toHaveClass('disabled')
+
+ describe 'enable', ->
+ beforeEach ->
+ fixture.set '<input type="text" disabled="disabled" class="disabled" />'
+
+ it 'removes the disabled attribute', ->
+ $input = $('input').first()
+
+ $input.enable()
+ expect($input).not.toHaveAttr('disabled')
+
+ it 'removes the disabled class', ->
+ $input = $('input').first()
+
+ $input.enable()
+ expect($input).not.toHaveClass('disabled')
diff --git a/spec/javascripts/fixtures/issuable.html.haml b/spec/javascripts/fixtures/issuable.html.haml
new file mode 100644
index 00000000000..42ab4aa68b1
--- /dev/null
+++ b/spec/javascripts/fixtures/issuable.html.haml
@@ -0,0 +1,2 @@
+%form.js-main-target-form
+ %textarea#note_note
diff --git a/spec/javascripts/fixtures/issue_note.html.haml b/spec/javascripts/fixtures/issue_note.html.haml
new file mode 100644
index 00000000000..0aecc7334fd
--- /dev/null
+++ b/spec/javascripts/fixtures/issue_note.html.haml
@@ -0,0 +1,12 @@
+%ul
+ %li.note
+ .js-task-list-container
+ .note-text
+ %ul.task-list
+ %li.task-list-item
+ %input.task-list-item-checkbox{type: 'checkbox'}
+ Task List Item
+ .note-edit-form
+ %form
+ %textarea.js-task-list-field
+ \- [ ] Task List Item
diff --git a/spec/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml
new file mode 100644
index 00000000000..db5abe0cae3
--- /dev/null
+++ b/spec/javascripts/fixtures/issues_show.html.haml
@@ -0,0 +1,13 @@
+%a.btn-close
+
+.issue-details
+ .description.js-task-list-container
+ .wiki
+ %ul.task-list
+ %li.task-list-item
+ %input.task-list-item-checkbox{type: 'checkbox'}
+ Task List Item
+ %textarea.js-task-list-field
+ \- [ ] Task List Item
+
+%form.js-issue-update{action: '/foo'}
diff --git a/spec/javascripts/fixtures/merge_requests_show.html.haml b/spec/javascripts/fixtures/merge_requests_show.html.haml
new file mode 100644
index 00000000000..c4329b8f94a
--- /dev/null
+++ b/spec/javascripts/fixtures/merge_requests_show.html.haml
@@ -0,0 +1,13 @@
+%a.btn-close
+
+.merge-request-details
+ .description.js-task-list-container
+ .wiki
+ %ul.task-list
+ %li.task-list-item
+ %input.task-list-item-checkbox{type: 'checkbox'}
+ Task List Item
+ %textarea.js-task-list-field
+ \- [ ] Task List Item
+
+%form.js-merge-request-update{action: '/foo'}
diff --git a/spec/javascripts/fixtures/zen_mode.html.haml b/spec/javascripts/fixtures/zen_mode.html.haml
new file mode 100644
index 00000000000..e867e4de2b9
--- /dev/null
+++ b/spec/javascripts/fixtures/zen_mode.html.haml
@@ -0,0 +1,9 @@
+.zennable
+ %input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' }
+ .zen-backdrop
+ %textarea#note_note.js-gfm-input.markdown-area{placeholder: 'Leave a comment'}
+ %a.zen-enter-link{tabindex: '-1'}
+ %i.fa.fa-expand
+ Edit in fullscreen
+ %a.zen-leave-link
+ %i.fa.fa-compress
diff --git a/spec/javascripts/issue_spec.js.coffee b/spec/javascripts/issue_spec.js.coffee
index 13b25862f57..268e4c68c31 100644
--- a/spec/javascripts/issue_spec.js.coffee
+++ b/spec/javascripts/issue_spec.js.coffee
@@ -1,34 +1,20 @@
-#= require jquery
-#= require jasmine-fixture
#= require issue
describe 'Issue', ->
describe 'task lists', ->
- selectors = {
- container: '.issue-details .description.js-task-list-container'
- item: '.wiki ul.task-list li.task-list-item input.task-list-item-checkbox[type=checkbox] {Task List Item}'
- textarea: '.wiki textarea.js-task-list-field{- [ ] Task List Item}'
- form: 'form.js-issue-update[action="/foo"]'
- close: 'a.btn-close'
- }
+ fixture.preload('issues_show.html')
beforeEach ->
- $container = affix(selectors.container)
-
- # # These two elements are siblings inside the container
- $container.find('.js-task-list-container').append(affix(selectors.item))
- $container.find('.js-task-list-container').append(affix(selectors.textarea))
-
- # Task lists don't get initialized unless this button exists. Not ideal.
- $container.append(affix(selectors.close))
-
- # This form is used to get the `update` URL. Not ideal.
- $container.append(affix(selectors.form))
-
+ fixture.load('issues_show.html')
@issue = new Issue()
+ it 'modifies the Markdown field', ->
+ spyOn(jQuery, 'ajax').and.stub()
+ $('input[type=checkbox]').attr('checked', true).trigger('change')
+ expect($('.js-task-list-field').val()).toBe('- [x] Task List Item')
+
it 'submits an ajax request on tasklist:changed', ->
- spyOn($, 'ajax').and.callFake (req) ->
+ spyOn(jQuery, 'ajax').and.callFake (req) ->
expect(req.type).toBe('PATCH')
expect(req.url).toBe('/foo')
expect(req.data.issue.description).not.toBe(null)
diff --git a/spec/javascripts/merge_request_spec.js.coffee b/spec/javascripts/merge_request_spec.js.coffee
index 3ebc4a4eed5..a4735af0343 100644
--- a/spec/javascripts/merge_request_spec.js.coffee
+++ b/spec/javascripts/merge_request_spec.js.coffee
@@ -1,34 +1,23 @@
-#= require jquery
-#= require jasmine-fixture
#= require merge_request
+window.disableButtonIfEmptyField = -> null
+
describe 'MergeRequest', ->
describe 'task lists', ->
- selectors = {
- container: '.merge-request-details .description.js-task-list-container'
- item: '.wiki ul.task-list li.task-list-item input.task-list-item-checkbox[type=checkbox] {Task List Item}'
- textarea: '.wiki textarea.js-task-list-field{- [ ] Task List Item}'
- form: 'form.js-merge-request-update[action="/foo"]'
- close: 'a.btn-close'
- }
+ fixture.preload('merge_requests_show.html')
beforeEach ->
- $container = affix(selectors.container)
-
- # # These two elements are siblings inside the container
- $container.find('.js-task-list-container').append(affix(selectors.item))
- $container.find('.js-task-list-container').append(affix(selectors.textarea))
-
- # Task lists don't get initialized unless this button exists. Not ideal.
- $container.append(affix(selectors.close))
+ fixture.load('merge_requests_show.html')
+ @merge = new MergeRequest({})
- # This form is used to get the `update` URL. Not ideal.
- $container.append(affix(selectors.form))
+ it 'modifies the Markdown field', ->
+ spyOn(jQuery, 'ajax').and.stub()
- @merge = new MergeRequest({})
+ $('input[type=checkbox]').attr('checked', true).trigger('change')
+ expect($('.js-task-list-field').val()).toBe('- [x] Task List Item')
it 'submits an ajax request on tasklist:changed', ->
- spyOn($, 'ajax').and.callFake (req) ->
+ spyOn(jQuery, 'ajax').and.callFake (req) ->
expect(req.type).toBe('PATCH')
expect(req.url).toBe('/foo')
expect(req.data.merge_request.description).not.toBe(null)
diff --git a/spec/javascripts/notes_spec.js.coffee b/spec/javascripts/notes_spec.js.coffee
index de2e8e7f6c8..050b6e362c6 100644
--- a/spec/javascripts/notes_spec.js.coffee
+++ b/spec/javascripts/notes_spec.js.coffee
@@ -1,5 +1,3 @@
-#= require jquery
-#= require jasmine-fixture
#= require notes
window.gon = {}
@@ -7,21 +5,18 @@ window.disableButtonIfEmptyField = -> null
describe 'Notes', ->
describe 'task lists', ->
- selectors = {
- container: 'li.note .js-task-list-container'
- item: '.note-text ul.task-list li.task-list-item input.task-list-item-checkbox[type=checkbox] {Task List Item}'
- textarea: '.note-edit-form form textarea.js-task-list-field{- [ ] Task List Item}'
- }
+ fixture.preload('issue_note.html')
beforeEach ->
- $container = affix(selectors.container)
-
- # These two elements are siblings inside the container
- $container.find('.js-task-list-container').append(affix(selectors.item))
- $container.find('.js-task-list-container').append(affix(selectors.textarea))
+ fixture.load('issue_note.html')
+ $('form').on 'submit', (e) -> e.preventDefault()
@notes = new Notes()
+ it 'modifies the Markdown field', ->
+ $('input[type=checkbox]').attr('checked', true).trigger('change')
+ expect($('.js-task-list-field').val()).toBe('- [x] Task List Item')
+
it 'submits the form on tasklist:changed', ->
submitted = false
$('form').on 'submit', (e) -> submitted = true; e.preventDefault()
diff --git a/spec/javascripts/shortcuts_issuable_spec.js.coffee b/spec/javascripts/shortcuts_issuable_spec.js.coffee
index 57dcc2161d3..a01ad7140dd 100644
--- a/spec/javascripts/shortcuts_issuable_spec.js.coffee
+++ b/spec/javascripts/shortcuts_issuable_spec.js.coffee
@@ -1,10 +1,10 @@
-#= require jquery
-#= require jasmine-fixture
-
#= require shortcuts_issuable
describe 'ShortcutsIssuable', ->
+ fixture.preload('issuable.html')
+
beforeEach ->
+ fixture.load('issuable.html')
@shortcut = new ShortcutsIssuable()
describe '#replyWithSelectedText', ->
@@ -14,7 +14,6 @@ describe 'ShortcutsIssuable', ->
beforeEach ->
@selector = 'form.js-main-target-form textarea#note_note'
- affix(@selector)
describe 'with empty selection', ->
it 'does nothing', ->
diff --git a/spec/javascripts/spec_helper.coffee b/spec/javascripts/spec_helper.coffee
new file mode 100644
index 00000000000..47b41dd2c81
--- /dev/null
+++ b/spec/javascripts/spec_helper.coffee
@@ -0,0 +1,46 @@
+# PhantomJS (Teaspoons default driver) doesn't have support for
+# Function.prototype.bind, which has caused confusion. Use this polyfill to
+# avoid the confusion.
+
+#= require support/bind-poly
+
+# You can require your own javascript files here. By default this will include
+# everything in application, however you may get better load performance if you
+# require the specific files that are being used in the spec that tests them.
+
+#= require jquery
+#= require bootstrap
+#= require underscore
+
+# Teaspoon includes some support files, but you can use anything from your own
+# support path too.
+
+# require support/jasmine-jquery-1.7.0
+# require support/jasmine-jquery-2.0.0
+#= require support/jasmine-jquery-2.1.0
+# require support/sinon
+# require support/your-support-file
+
+# Deferring execution
+
+# If you're using CommonJS, RequireJS or some other asynchronous library you can
+# defer execution. Call Teaspoon.execute() after everything has been loaded.
+# Simple example of a timeout:
+
+# Teaspoon.defer = true
+# setTimeout(Teaspoon.execute, 1000)
+
+# Matching files
+
+# By default Teaspoon will look for files that match
+# _spec.{js,js.coffee,.coffee}. Add a filename_spec.js file in your spec path
+# and it'll be included in the default suite automatically. If you want to
+# customize suites, check out the configuration in teaspoon_env.rb
+
+# Manifest
+
+# If you'd rather require your spec files manually (to control order for
+# instance) you can disable the suite matcher in the configuration and use this
+# file as a manifest.
+
+# For more information: http://github.com/modeset/teaspoon
diff --git a/spec/javascripts/support/jasmine.yml b/spec/javascripts/support/jasmine.yml
deleted file mode 100644
index 168c9618643..00000000000
--- a/spec/javascripts/support/jasmine.yml
+++ /dev/null
@@ -1,15 +0,0 @@
-# path to parent directory of spec_files
-# relative path from Rails.root
-#
-# Alternatively accept an array of directory to include external spec files
-# spec_dir:
-# - spec/javascripts
-# - ../engine/spec/javascripts
-#
-# defaults to spec/javascripts
-spec_dir: spec/javascripts
-
-# list of file expressions to include as specs into spec runner
-# relative path from spec_dir
-spec_files:
- - "**/*[Ss]pec.{js.coffee,js,coffee}"
diff --git a/spec/javascripts/support/jasmine_helper.rb b/spec/javascripts/support/jasmine_helper.rb
deleted file mode 100644
index 4d73aec5a31..00000000000
--- a/spec/javascripts/support/jasmine_helper.rb
+++ /dev/null
@@ -1,15 +0,0 @@
-#Use this file to set/override Jasmine configuration options
-#You can remove it if you don't need it.
-#This file is loaded *after* jasmine.yml is interpreted.
-#
-#Example: using a different boot file.
-#Jasmine.configure do |config|
-# config.boot_dir = '/absolute/path/to/boot_dir'
-# config.boot_files = lambda { ['/absolute/path/to/boot_dir/file.js'] }
-#end
-#
-#Example: prevent PhantomJS auto install, uses PhantomJS already on your path.
-#Jasmine.configure do |config|
-# config.prevent_phantom_js_auto_install = true
-#end
-#
diff --git a/spec/javascripts/zen_mode_spec.js.coffee b/spec/javascripts/zen_mode_spec.js.coffee
new file mode 100644
index 00000000000..1f4ea58ad48
--- /dev/null
+++ b/spec/javascripts/zen_mode_spec.js.coffee
@@ -0,0 +1,52 @@
+#= require zen_mode
+
+describe 'ZenMode', ->
+ fixture.preload('zen_mode.html')
+
+ beforeEach ->
+ fixture.load('zen_mode.html')
+
+ # Stub Dropzone.forElement(...).enable()
+ spyOn(Dropzone, 'forElement').and.callFake ->
+ enable: -> true
+
+ @zen = new ZenMode()
+
+ # Set this manually because we can't actually scroll the window
+ @zen.scroll_position = 456
+
+ # Ohmmmmmmm
+ enterZen = ->
+ $('.zen-toggle-comment').prop('checked', true).trigger('change')
+
+ # Wh- what was that?!
+ exitZen = ->
+ $('.zen-toggle-comment').prop('checked', false).trigger('change')
+
+ describe 'on enter', ->
+ it 'pauses Mousetrap', ->
+ spyOn(Mousetrap, 'pause')
+ enterZen()
+ expect(Mousetrap.pause).toHaveBeenCalled()
+
+ describe 'in use', ->
+ beforeEach ->
+ enterZen()
+
+ it 'exits on Escape', ->
+ $(document).trigger(jQuery.Event('keydown', {keyCode: 27}))
+ expect($('.zen-toggle-comment').prop('checked')).toBe(false)
+
+ describe 'on exit', ->
+ beforeEach ->
+ enterZen()
+
+ it 'unpauses Mousetrap', ->
+ spyOn(Mousetrap, 'unpause')
+ exitZen()
+ expect(Mousetrap.unpause).toHaveBeenCalled()
+
+ it 'restores the scroll position', ->
+ spyOn(@zen, 'restoreScroll')
+ exitZen()
+ expect(@zen.restoreScroll).toHaveBeenCalledWith(456)
diff --git a/spec/lib/gitlab/markdown/autolink_filter_spec.rb b/spec/lib/gitlab/markdown/autolink_filter_spec.rb
index 0bbdc11a979..a14cb2da089 100644
--- a/spec/lib/gitlab/markdown/autolink_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/autolink_filter_spec.rb
@@ -2,11 +2,9 @@ require 'spec_helper'
module Gitlab::Markdown
describe AutolinkFilter do
- let(:link) { 'http://about.gitlab.com/' }
+ include FilterSpecHelper
- def filter(html, options = {})
- described_class.call(html, options)
- end
+ let(:link) { 'http://about.gitlab.com/' }
it 'does nothing when :autolink is false' do
exp = act = link
diff --git a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
index d3695ee46d0..e8391cc7aca 100644
--- a/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_range_reference_filter_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe CommitRangeReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
let(:project) { create(:project) }
let(:commit1) { project.commit }
diff --git a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
index a0d2cd7e22b..a10d43c9a02 100644
--- a/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/commit_reference_filter_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe CommitReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
let(:project) { create(:project) }
let(:commit) { project.commit }
diff --git a/spec/lib/gitlab/markdown/emoji_filter_spec.rb b/spec/lib/gitlab/markdown/emoji_filter_spec.rb
index 18d55c4818f..11efd9bb4cd 100644
--- a/spec/lib/gitlab/markdown/emoji_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/emoji_filter_spec.rb
@@ -2,9 +2,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe EmojiFilter do
- def filter(html, contexts = {})
- described_class.call(html, contexts)
- end
+ include FilterSpecHelper
before do
ActionController::Base.asset_host = 'https://foo.com'
diff --git a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
index bf9409589fa..f16095bc2b2 100644
--- a/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/external_issue_reference_filter_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe ExternalIssueReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
def helper
IssuesHelper
diff --git a/spec/lib/gitlab/markdown/external_link_filter_spec.rb b/spec/lib/gitlab/markdown/external_link_filter_spec.rb
index c2ff4f80a42..a040b34577b 100644
--- a/spec/lib/gitlab/markdown/external_link_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/external_link_filter_spec.rb
@@ -2,9 +2,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe ExternalLinkFilter do
- def filter(html, options = {})
- described_class.call(html, options)
- end
+ include FilterSpecHelper
it 'ignores elements without an href attribute' do
exp = act = %q(<a id="ignored">Ignore Me</a>)
diff --git a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
index a838d7570c8..fa43d33794d 100644
--- a/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/issue_reference_filter_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe IssueReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
def helper
IssuesHelper
diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
index 41987f57bca..cf3337b1ba1 100644
--- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
@@ -3,7 +3,7 @@ require 'html/pipeline'
module Gitlab::Markdown
describe LabelReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
let(:project) { create(:empty_project) }
let(:label) { create(:label, project: project) }
diff --git a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
index 6aeb1093602..5945302a2da 100644
--- a/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/merge_request_reference_filter_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe MergeRequestReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
let(:project) { create(:project) }
let(:merge) { create(:merge_request, source_project: project) }
diff --git a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb
index 4a1aa766149..e50c82d0b3c 100644
--- a/spec/lib/gitlab/markdown/sanitization_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/sanitization_filter_spec.rb
@@ -2,9 +2,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe SanitizationFilter do
- def filter(html, options = {})
- described_class.call(html, options)
- end
+ include FilterSpecHelper
describe 'default whitelist' do
it 'sanitizes tags that are not whitelisted' do
@@ -42,6 +40,13 @@ module Gitlab::Markdown
end
describe 'custom whitelist' do
+ it 'customizes the whitelist only once' do
+ instance = described_class.new('Foo')
+ 3.times { instance.whitelist }
+
+ expect(instance.whitelist[:transformers].size).to eq 4
+ end
+
it 'allows syntax highlighting' do
exp = act = %q{<pre class="code highlight white c"><code><span class="k">def</span></code></pre>}
expect(filter(act).to_html).to eq exp
@@ -87,5 +92,27 @@ module Gitlab::Markdown
expect(doc.at_css('a')['href']).to be_nil
end
end
+
+ context 'when pipeline is :description' do
+ it 'uses a stricter whitelist' do
+ doc = filter('<h1>Description</h1>', pipeline: :description)
+ expect(doc.to_html.strip).to eq 'Description'
+ end
+
+ %w(pre code img ol ul li).each do |elem|
+ it "removes '#{elem}' elements" do
+ act = "<#{elem}>Description</#{elem}>"
+ expect(filter(act, pipeline: :description).to_html.strip).
+ to eq 'Description'
+ end
+ end
+
+ %w(b i strong em a ins del sup sub p).each do |elem|
+ it "still allows '#{elem}' elements" do
+ exp = act = "<#{elem}>Description</#{elem}>"
+ expect(filter(act, pipeline: :description).to_html).to eq exp
+ end
+ end
+ end
end
end
diff --git a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
index 07ece66e903..38619a3c07f 100644
--- a/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/snippet_reference_filter_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe SnippetReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
let(:project) { create(:empty_project) }
let(:snippet) { create(:project_snippet, project: project) }
diff --git a/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb b/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb
index f383a5850d5..ddf583a72c1 100644
--- a/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/table_of_contents_filter_spec.rb
@@ -4,9 +4,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe TableOfContentsFilter do
- def filter(html, options = {})
- described_class.call(html, options)
- end
+ include FilterSpecHelper
def header(level, text)
"<h#{level}>#{text}</h#{level}>\n"
diff --git a/spec/lib/gitlab/markdown/task_list_filter_spec.rb b/spec/lib/gitlab/markdown/task_list_filter_spec.rb
index 2a1e1cc5127..94f39cc966e 100644
--- a/spec/lib/gitlab/markdown/task_list_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/task_list_filter_spec.rb
@@ -2,9 +2,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe TaskListFilter do
- def filter(html, options = {})
- described_class.call(html, options)
- end
+ include FilterSpecHelper
it 'does not apply `task-list` class to non-task lists' do
exp = act = %(<ul><li>Item</li></ul>)
diff --git a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
index 0ecbdee9b9e..08e6941028f 100644
--- a/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/user_reference_filter_spec.rb
@@ -2,7 +2,7 @@ require 'spec_helper'
module Gitlab::Markdown
describe UserReferenceFilter do
- include ReferenceFilterSpecHelper
+ include FilterSpecHelper
let(:project) { create(:empty_project) }
let(:user) { create(:user) }
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index 44cdd1e4fab..2a982e8b107 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -13,6 +13,7 @@ describe Gitlab::OAuth::User do
email: 'john@mail.com'
}
end
+ let(:ldap_user) { Gitlab::LDAP::Person.new(Net::LDAP::Entry.new, 'ldapmain') }
describe :persisted? do
let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') }
@@ -32,31 +33,94 @@ describe Gitlab::OAuth::User do
let(:provider) { 'twitter' }
describe 'signup' do
- context "with allow_single_sign_on enabled" do
- before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
+ shared_examples "to verify compliance with allow_single_sign_on" 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
+ 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'
+ 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
+ before { Gitlab.config.omniauth.stub allow_single_sign_on: false }
+ it "throws an error" do
+ expect{ oauth_user.save }.to raise_error StandardError
+ end
end
end
- context "with allow_single_sign_on disabled (Default)" do
- it "throws an error" do
- expect{ oauth_user.save }.to raise_error StandardError
+ context "with auto_link_ldap_user disabled (default)" do
+ before { Gitlab.config.omniauth.stub auto_link_ldap_user: false }
+ include_examples "to verify compliance with allow_single_sign_on"
+ end
+
+ context "with auto_link_ldap_user enabled" do
+ before { Gitlab.config.omniauth.stub auto_link_ldap_user: true }
+
+ context "and a corresponding LDAP person" do
+ before do
+ ldap_user.stub(:uid) { uid }
+ ldap_user.stub(:username) { uid }
+ ldap_user.stub(:email) { ['johndoe@example.com','john2@example.com'] }
+ ldap_user.stub(:dn) { 'uid=user1,ou=People,dc=example' }
+ allow(oauth_user).to receive(:ldap_person).and_return(ldap_user)
+ end
+
+ context "and no account for the LDAP user" do
+
+ it "creates a user with dual LDAP and omniauth identities" do
+ oauth_user.save
+
+ expect(gl_user).to be_valid
+ expect(gl_user.username).to eql uid
+ expect(gl_user.email).to eql 'johndoe@example.com'
+ expect(gl_user.identities.length).to eql 2
+ identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+ expect(identities_as_hash).to match_array(
+ [ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'twitter', extern_uid: uid }
+ ])
+ end
+ end
+
+ context "and LDAP user has an account already" do
+ let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
+ it "adds the omniauth identity to the LDAP account" do
+ oauth_user.save
+
+ expect(gl_user).to be_valid
+ expect(gl_user.username).to eql 'john'
+ expect(gl_user.email).to eql 'john@example.com'
+ expect(gl_user.identities.length).to eql 2
+ identities_as_hash = gl_user.identities.map { |id| { provider: id.provider, extern_uid: id.extern_uid } }
+ expect(identities_as_hash).to match_array(
+ [ { provider: 'ldapmain', extern_uid: 'uid=user1,ou=People,dc=example' },
+ { provider: 'twitter', extern_uid: uid }
+ ])
+ end
+ end
+ end
+
+ context "and no corresponding LDAP person" do
+ before { allow(oauth_user).to receive(:ldap_person).and_return(nil) }
+
+ include_examples "to verify compliance with allow_single_sign_on"
end
end
+
end
describe 'blocking' do
let(:provider) { 'twitter' }
before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
- context 'signup' do
+ context 'signup with omniauth only' do
context 'dont block on create' do
before { Gitlab.config.omniauth.stub block_auto_created_users: false }
@@ -78,6 +142,64 @@ describe Gitlab::OAuth::User do
end
end
+ context 'signup with linked omniauth and LDAP account' do
+ before do
+ Gitlab.config.omniauth.stub auto_link_ldap_user: true
+ ldap_user.stub(:uid) { uid }
+ ldap_user.stub(:username) { uid }
+ ldap_user.stub(:email) { ['johndoe@example.com','john2@example.com'] }
+ ldap_user.stub(:dn) { 'uid=user1,ou=People,dc=example' }
+ allow(oauth_user).to receive(:ldap_person).and_return(ldap_user)
+ end
+
+ context "and no account for the LDAP user" do
+ context 'dont block on create (LDAP)' do
+ before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).to be_blocked
+ end
+ end
+ end
+
+ context 'and LDAP user has an account already' do
+ let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
+
+ context 'dont block on create (LDAP)' do
+ before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+ end
+ end
+
+
context 'sign-in' do
before do
oauth_user.save
@@ -103,6 +225,26 @@ describe Gitlab::OAuth::User do
expect(gl_user).not_to be_blocked
end
end
+
+ context 'dont block on create (LDAP)' do
+ before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
+
+ context 'block on create (LDAP)' do
+ before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true }
+
+ it do
+ oauth_user.save
+ expect(gl_user).to be_valid
+ expect(gl_user).not_to be_blocked
+ end
+ end
end
end
end
diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb
index c14f4ac6bf6..f921dd9cc09 100644
--- a/spec/lib/gitlab/reference_extractor_spec.rb
+++ b/spec/lib/gitlab/reference_extractor_spec.rb
@@ -16,6 +16,30 @@ describe Gitlab::ReferenceExtractor do
expect(subject.users).to eq([@u_foo, @u_bar, @u_offteam])
end
+ it 'ignores user mentions inside specific elements' do
+ @u_foo = create(:user, username: 'foo')
+ @u_bar = create(:user, username: 'bar')
+ @u_offteam = create(:user, username: 'offteam')
+
+ project.team << [@u_foo, :reporter]
+ project.team << [@u_bar, :guest]
+
+ subject.analyze(%Q{
+ Inline code: `@foo`
+
+ Code block:
+
+ ```
+ @bar
+ ```
+
+ Quote:
+
+ > @offteam
+ })
+ expect(subject.users).to eq([])
+ end
+
it 'accesses valid issue objects' do
@i0 = create(:issue, project: project)
@i1 = create(:issue, project: project)
diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb
index ce3ea6c260a..baa4bd0f28f 100644
--- a/spec/lib/gitlab/upgrader_spec.rb
+++ b/spec/lib/gitlab/upgrader_spec.rb
@@ -20,5 +20,20 @@ describe Gitlab::Upgrader do
upgrader.stub(current_version_raw: "5.3.0")
expect(upgrader.latest_version_raw).to eq("v5.4.2")
end
+
+ it 'should get the latest version from tags' do
+ allow(upgrader).to receive(:fetch_git_tags).and_return([
+ '6f0733310546402c15d3ae6128a95052f6c8ea96 refs/tags/v7.1.1',
+ 'facfec4b242ce151af224e20715d58e628aa5e74 refs/tags/v7.1.1^{}',
+ 'f7068d99c79cf79befbd388030c051bb4b5e86d4 refs/tags/v7.10.4',
+ '337225a4fcfa9674e2528cb6d41c46556bba9dfa refs/tags/v7.10.4^{}',
+ '880e0ba0adbed95d087f61a9a17515e518fc6440 refs/tags/v7.11.1',
+ '6584346b604f981f00af8011cd95472b2776d912 refs/tags/v7.11.1^{}',
+ '43af3e65a486a9237f29f56d96c3b3da59c24ae0 refs/tags/v7.11.2',
+ 'dac18e7728013a77410e926a1e64225703754a2d refs/tags/v7.11.2^{}',
+ '0bf21fd4b46c980c26fd8c90a14b86a4d90cc950 refs/tags/v7.9.4',
+ 'b10de29edbaff7219547dc506cb1468ee35065c3 refs/tags/v7.9.4^{}'])
+ expect(upgrader.latest_version_raw).to eq("v7.11.2")
+ end
end
end
diff --git a/spec/lib/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb
index af399f3a731..37240d51310 100644
--- a/spec/lib/repository_cache_spec.rb
+++ b/spec/lib/repository_cache_spec.rb
@@ -1,4 +1,3 @@
-require 'rspec'
require_relative '../../lib/repository_cache'
describe RepositoryCache do
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 557c71b4d2c..86c395a8e8e 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -16,7 +16,7 @@ describe Issue, "Issuable" do
it { is_expected.to validate_presence_of(:iid) }
it { is_expected.to validate_presence_of(:author) }
it { is_expected.to validate_presence_of(:title) }
- it { is_expected.to ensure_length_of(:title).is_at_least(0).is_at_most(255) }
+ it { is_expected.to validate_length_of(:title).is_at_least(0).is_at_most(255) }
end
describe "Scope" do
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index eadb941a3fa..22237f2e9f2 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -1,14 +1,31 @@
require 'spec_helper'
describe Issue, "Mentionable" do
- describe :mentioned_users do
+ describe '#mentioned_users' do
let!(:user) { create(:user, username: 'stranger') }
let!(:user2) { create(:user, username: 'john') }
- let!(:issue) { create(:issue, description: '@stranger mentioned') }
+ let!(:issue) { create(:issue, description: "#{user.to_reference} mentioned") }
subject { issue.mentioned_users }
it { is_expected.to include(user) }
it { is_expected.not_to include(user2) }
end
+
+ describe '#create_cross_references!' do
+ let(:project) { create(:project) }
+ let(:author) { double('author') }
+ let(:commit) { project.commit }
+ let(:commit2) { project.commit }
+
+ let!(:issue) do
+ create(:issue, project: project, description: commit.to_reference)
+ end
+
+ it 'correctly removes already-mentioned Commits' do
+ expect(Note).not_to receive(:create_cross_reference_note)
+
+ issue.create_cross_references!(project, author, [commit2])
+ end
+ end
end
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index 7032b777144..705ef257d86 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -36,9 +36,7 @@ describe DeployKeysProject do
it "doesn't destroy the deploy key" do
subject.destroy
- expect {
- deploy_key.reload
- }.not_to raise_error(ActiveRecord::RecordNotFound)
+ expect { deploy_key.reload }.not_to raise_error
end
end
@@ -63,9 +61,7 @@ describe DeployKeysProject do
it "doesn't destroy the deploy key" do
subject.destroy
- expect {
- deploy_key.reload
- }.not_to raise_error(ActiveRecord::RecordNotFound)
+ expect { deploy_key.reload }.not_to raise_error
end
end
end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index 6eb1208a7f2..fbb9e162952 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -26,8 +26,8 @@ describe Key do
describe "Validation" do
it { is_expected.to validate_presence_of(:title) }
it { is_expected.to validate_presence_of(:key) }
- it { is_expected.to ensure_length_of(:title).is_within(0..255) }
- it { is_expected.to ensure_length_of(:key).is_within(0..5000) }
+ it { is_expected.to validate_length_of(:title).is_within(0..255) }
+ it { is_expected.to validate_length_of(:key).is_within(0..5000) }
end
describe "Methods" do
diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb
index e5bf9125313..ebd8b545aa7 100644
--- a/spec/models/project_services/gitlab_ci_service_spec.rb
+++ b/spec/models/project_services/gitlab_ci_service_spec.rb
@@ -48,6 +48,21 @@ describe GitlabCiService do
it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://ci.gitlab.org/projects/2/refs/master/commits/2ab7834c")}
it { expect(@service.build_page("issue#2", 'master')).to eq("http://ci.gitlab.org/projects/2/refs/master/commits/issue%232")}
end
+
+ describe "execute" do
+ let(:user) { create(:user, username: 'username') }
+ let(:project) { create(:project, name: 'project') }
+ let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
+
+ it "calls ci_yaml_file" do
+ service_hook = double
+ service_hook.should_receive(:execute)
+ @service.should_receive(:service_hook).and_return(service_hook)
+ @service.should_receive(:ci_yaml_file).with(push_sample_data)
+
+ @service.execute(push_sample_data)
+ end
+ end
end
describe "Fork registration" do
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 48568e2a3ff..87c67fa32c3 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -69,14 +69,14 @@ describe Project do
it { is_expected.to validate_presence_of(:name) }
it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) }
- it { is_expected.to ensure_length_of(:name).is_within(0..255) }
+ it { is_expected.to validate_length_of(:name).is_within(0..255) }
it { is_expected.to validate_presence_of(:path) }
it { is_expected.to validate_uniqueness_of(:path).scoped_to(:namespace_id) }
- it { is_expected.to ensure_length_of(:path).is_within(0..255) }
- it { is_expected.to ensure_length_of(:description).is_within(0..2000) }
+ it { is_expected.to validate_length_of(:path).is_within(0..255) }
+ it { is_expected.to validate_length_of(:description).is_within(0..2000) }
it { is_expected.to validate_presence_of(:creator) }
- it { is_expected.to ensure_length_of(:issues_tracker_id).is_within(0..255) }
+ it { is_expected.to validate_length_of(:issues_tracker_id).is_within(0..255) }
it { is_expected.to validate_presence_of(:namespace) }
it 'should not allow new projects beyond user limits' do
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index c81dd36ef4b..c786d0bf103 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -38,10 +38,10 @@ describe Snippet do
it { is_expected.to validate_presence_of(:author) }
it { is_expected.to validate_presence_of(:title) }
- it { is_expected.to ensure_length_of(:title).is_within(0..255) }
+ it { is_expected.to validate_length_of(:title).is_within(0..255) }
it { is_expected.to validate_presence_of(:file_name) }
- it { is_expected.to ensure_length_of(:file_name).is_within(0..255) }
+ it { is_expected.to validate_length_of(:file_name).is_within(0..255) }
it { is_expected.to validate_presence_of(:content) }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 49c7b7d99ce..f1b8afa5854 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -96,7 +96,7 @@ describe User do
it { is_expected.to allow_value(0).for(:projects_limit) }
it { is_expected.not_to allow_value(-1).for(:projects_limit) }
- it { is_expected.to ensure_length_of(:bio).is_within(0..255) }
+ it { is_expected.to validate_length_of(:bio).is_within(0..255) }
describe 'email' do
it 'accepts info@example.com' do
@@ -248,6 +248,7 @@ describe User do
it { expect(@user.several_namespaces?).to be_truthy }
it { expect(@user.authorized_groups).to eq([@group]) }
it { expect(@user.owned_groups).to eq([@group]) }
+ it { expect(@user.namespaces).to match_array([@user.namespace, @group]) }
end
describe 'group multiple owners' do
@@ -270,6 +271,7 @@ describe User do
end
it { expect(@user.several_namespaces?).to be_falsey }
+ it { expect(@user.namespaces).to eq([@user.namespace]) }
end
describe 'blocking user' do
diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb
index 9ea60e1a4ad..a1c248c636e 100644
--- a/spec/requests/api/commits_spec.rb
+++ b/spec/requests/api/commits_spec.rb
@@ -9,6 +9,7 @@ describe API::API, api: true do
let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) }
let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) }
let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') }
+ let!(:another_note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'another comment on a commit') }
before { project.team << [user, :reporter] }
@@ -89,7 +90,7 @@ describe API::API, api: true do
get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
- expect(json_response.length).to eq(1)
+ expect(json_response.length).to eq(2)
expect(json_response.first['note']).to eq('a comment on a commit')
expect(json_response.first['author']['id']).to eq(user.id)
end
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index bab8888a631..15f547e128d 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -49,10 +49,6 @@ describe API::API, api: true do
}
it "should create a new file in project repo" do
- Gitlab::Satellite::NewFileAction.any_instance.stub(
- commit!: true,
- )
-
post api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(201)
expect(json_response['file_path']).to eq('newfile.rb')
@@ -63,9 +59,9 @@ describe API::API, api: true do
expect(response.status).to eq(400)
end
- it "should return a 400 if satellite fails to create file" do
- Gitlab::Satellite::NewFileAction.any_instance.stub(
- commit!: false,
+ it "should return a 400 if editor fails to create file" do
+ Repository.any_instance.stub(
+ commit_file: false,
)
post api("/projects/#{project.id}/repository/files", user), valid_params
@@ -84,10 +80,6 @@ describe API::API, api: true do
}
it "should update existing file in project repo" do
- Gitlab::Satellite::EditFileAction.any_instance.stub(
- commit!: true,
- )
-
put api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(200)
expect(json_response['file_path']).to eq(file_path)
@@ -97,35 +89,6 @@ describe API::API, api: true do
put api("/projects/#{project.id}/repository/files", user)
expect(response.status).to eq(400)
end
-
- it 'should return a 400 if the checkout fails' do
- Gitlab::Satellite::EditFileAction.any_instance.stub(:commit!)
- .and_raise(Gitlab::Satellite::CheckoutFailed)
-
- put api("/projects/#{project.id}/repository/files", user), valid_params
- expect(response.status).to eq(400)
-
- ref = valid_params[:branch_name]
- expect(response.body).to match("ref '#{ref}' could not be checked out")
- end
-
- it 'should return a 409 if the file was not modified' do
- Gitlab::Satellite::EditFileAction.any_instance.stub(:commit!)
- .and_raise(Gitlab::Satellite::CommitFailed)
-
- put api("/projects/#{project.id}/repository/files", user), valid_params
- expect(response.status).to eq(409)
- expect(response.body).to match("Maybe there was nothing to commit?")
- end
-
- it 'should return a 409 if the push fails' do
- Gitlab::Satellite::EditFileAction.any_instance.stub(:commit!)
- .and_raise(Gitlab::Satellite::PushFailed)
-
- put api("/projects/#{project.id}/repository/files", user), valid_params
- expect(response.status).to eq(409)
- expect(response.body).to match("Maybe the file was changed by another process?")
- end
end
describe "DELETE /projects/:id/repository/files" do
@@ -138,10 +101,6 @@ describe API::API, api: true do
}
it "should delete existing file in project repo" do
- Gitlab::Satellite::DeleteFileAction.any_instance.stub(
- commit!: true,
- )
-
delete api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(200)
expect(json_response['file_path']).to eq(file_path)
@@ -153,8 +112,8 @@ describe API::API, api: true do
end
it "should return a 400 if satellite fails to create file" do
- Gitlab::Satellite::DeleteFileAction.any_instance.stub(
- commit!: false,
+ Repository.any_instance.stub(
+ remove_file: false,
)
delete api("/projects/#{project.id}/repository/files", user), valid_params
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index dcd50f73326..0ed5883914b 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -349,10 +349,10 @@ describe API::API, api: true do
expect(json_response['description']).to eq('New description')
end
- it "should return 422 when source_branch and target_branch are renamed the same" do
+ it "should return 400 when source_branch is specified" do
put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user),
source_branch: "master", target_branch: "master"
- expect(response.status).to eq(422)
+ expect(response.status).to eq(400)
end
it "should return merge_request with renamed target_branch" do
diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb
index 6ddaaa0a6dd..21787fdd895 100644
--- a/spec/requests/api/namespaces_spec.rb
+++ b/spec/requests/api/namespaces_spec.rb
@@ -3,6 +3,7 @@ require 'spec_helper'
describe API::API, api: true do
include ApiHelpers
let(:admin) { create(:admin) }
+ let(:user) { create(:user) }
let!(:group1) { create(:group) }
let!(:group2) { create(:group) }
@@ -14,7 +15,7 @@ describe API::API, api: true do
end
end
- context "when authenticated as admin" do
+ context "when authenticated as admin" do
it "admin: should return an array of all namespaces" do
get api("/namespaces", admin)
expect(response.status).to eq(200)
@@ -22,6 +23,32 @@ describe API::API, api: true do
expect(json_response.length).to eq(Namespace.count)
end
+
+ it "admin: should return an array of matched namespaces" do
+ get api("/namespaces?search=#{group1.name}", admin)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+
+ expect(json_response.length).to eq(1)
+ end
+ end
+
+ context "when authenticated as a regular user" do
+ it "user: should return an array of namespaces" do
+ get api("/namespaces", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+
+ expect(json_response.length).to eq(1)
+ end
+
+ it "admin: should return an array of matched namespaces" do
+ get api("/namespaces?search=#{user.username}", user)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Array
+
+ expect(json_response.length).to eq(1)
+ end
end
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 46cd26eb927..dbfd72e5f19 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -57,14 +57,14 @@ describe API::API, api: true do
expect(json_response.first['name']).to eq(project.name)
expect(json_response.first['owner']['username']).to eq(user.username)
end
-
+
it 'should include the project labels as the tag_list' do
get api('/projects', user)
response.status.should == 200
json_response.should be_an Array
json_response.first.keys.should include('tag_list')
end
-
+
context 'and using search' do
it 'should return searched project' do
get api('/projects', user), { search: project.name }
@@ -792,11 +792,6 @@ describe API::API, api: true do
describe 'DELETE /projects/:id' do
context 'when authenticated as user' do
it 'should remove project' do
- expect(GitlabShellWorker).to(
- receive(:perform_async).with(:remove_repository,
- /#{project.path_with_namespace}/)
- ).twice
-
delete api("/projects/#{project.id}", user)
expect(response.status).to eq(200)
end
diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb
index 042352311da..0040718d9be 100644
--- a/spec/routing/project_routing_spec.rb
+++ b/spec/routing/project_routing_spec.rb
@@ -172,7 +172,7 @@ end
# DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy
describe Projects::DeployKeysController, 'routing' do
it_behaves_like 'RESTful project resources' do
- let(:actions) { [:index, :show, :new, :create] }
+ let(:actions) { [:index, :new, :create] }
let(:controller) { 'deploy_keys' }
end
end
@@ -208,23 +208,31 @@ describe Projects::RefsController, 'routing' do
end
end
-# diffs_project_merge_request GET /:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs
-# automerge_project_merge_request POST /:project_id/merge_requests/:id/automerge(.:format) projects/merge_requests#automerge
-# automerge_check_project_merge_request GET /:project_id/merge_requests/:id/automerge_check(.:format) projects/merge_requests#automerge_check
-# branch_from_project_merge_requests GET /:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from
-# branch_to_project_merge_requests GET /:project_id/merge_requests/branch_to(.:format) projects/merge_requests#branch_to
-# project_merge_requests GET /:project_id/merge_requests(.:format) projects/merge_requests#index
-# POST /:project_id/merge_requests(.:format) projects/merge_requests#create
-# new_project_merge_request GET /:project_id/merge_requests/new(.:format) projects/merge_requests#new
-# edit_project_merge_request GET /:project_id/merge_requests/:id/edit(.:format) projects/merge_requests#edit
-# project_merge_request GET /:project_id/merge_requests/:id(.:format) projects/merge_requests#show
-# PUT /:project_id/merge_requests/:id(.:format) projects/merge_requests#update
-# DELETE /:project_id/merge_requests/:id(.:format) projects/merge_requests#destroy
+# diffs_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/diffs(.:format) projects/merge_requests#diffs
+# commits_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/commits(.:format) projects/merge_requests#commits
+# automerge_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/automerge(.:format) projects/merge_requests#automerge
+# automerge_check_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/automerge_check(.:format) projects/merge_requests#automerge_check
+# ci_status_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/ci_status(.:format) projects/merge_requests#ci_status
+# toggle_subscription_namespace_project_merge_request POST /:namespace_id/:project_id/merge_requests/:id/toggle_subscription(.:format) projects/merge_requests#toggle_subscription
+# branch_from_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_from(.:format) projects/merge_requests#branch_from
+# branch_to_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/branch_to(.:format) projects/merge_requests#branch_to
+# update_branches_namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests/update_branches(.:format) projects/merge_requests#update_branches
+# namespace_project_merge_requests GET /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#index
+# POST /:namespace_id/:project_id/merge_requests(.:format) projects/merge_requests#create
+# new_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/new(.:format) projects/merge_requests#new
+# edit_namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id/edit(.:format) projects/merge_requests#edit
+# namespace_project_merge_request GET /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#show
+# PATCH /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update
+# PUT /:namespace_id/:project_id/merge_requests/:id(.:format) projects/merge_requests#update
describe Projects::MergeRequestsController, 'routing' do
it 'to #diffs' do
expect(get('/gitlab/gitlabhq/merge_requests/1/diffs')).to route_to('projects/merge_requests#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
end
+ it 'to #commits' do
+ expect(get('/gitlab/gitlabhq/merge_requests/1/commits')).to route_to('projects/merge_requests#commits', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1')
+ end
+
it 'to #automerge' do
expect(post('/gitlab/gitlabhq/merge_requests/1/automerge')).to route_to(
'projects/merge_requests#automerge',
diff --git a/spec/services/destroy_group_service_spec.rb b/spec/services/destroy_group_service_spec.rb
new file mode 100644
index 00000000000..24e439503e7
--- /dev/null
+++ b/spec/services/destroy_group_service_spec.rb
@@ -0,0 +1,44 @@
+require 'spec_helper'
+
+describe DestroyGroupService do
+ let!(:user) { create(:user) }
+ let!(:group) { create(:group) }
+ let!(:project) { create(:project, namespace: group) }
+ let!(:gitlab_shell) { Gitlab::Shell.new }
+ let!(:remove_path) { group.path + "+#{group.id}+deleted" }
+
+ context 'database records' do
+ before do
+ destroy_group(group, user)
+ end
+
+ it { Group.all.should_not include(group) }
+ it { Project.all.should_not include(project) }
+ end
+
+ context 'file system' do
+ context 'Sidekiq inline' do
+ before do
+ # Run sidekiq immediatly to check that renamed dir will be removed
+ Sidekiq::Testing.inline! { destroy_group(group, user) }
+ end
+
+ it { gitlab_shell.exists?(group.path).should be_falsey }
+ it { gitlab_shell.exists?(remove_path).should be_falsey }
+ end
+
+ context 'Sidekiq fake' do
+ before do
+ # Dont run sidekiq to check if renamed repository exists
+ Sidekiq::Testing.fake! { destroy_group(group, user) }
+ end
+
+ it { gitlab_shell.exists?(group.path).should be_falsey }
+ it { gitlab_shell.exists?(remove_path).should be_truthy }
+ end
+ end
+
+ def destroy_group(group, user)
+ DestroyGroupService.new(group, user).execute
+ end
+end
diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb
index 0a0760056cf..c75173c1452 100644
--- a/spec/services/merge_requests/update_service_spec.rb
+++ b/spec/services/merge_requests/update_service_spec.rb
@@ -20,7 +20,8 @@ describe MergeRequests::UpdateService do
description: 'Also please fix',
assignee_id: user2.id,
state_event: 'close',
- label_ids: [label.id]
+ label_ids: [label.id],
+ target_branch: 'target'
}
end
@@ -39,6 +40,7 @@ describe MergeRequests::UpdateService do
it { expect(@merge_request).to be_closed }
it { expect(@merge_request.labels.count).to eq(1) }
it { expect(@merge_request.labels.first.title).to eq('Bug') }
+ it { expect(@merge_request.target_branch).to eq('target') }
it 'should execute hooks with update action' do
expect(service).to have_received(:execute_hooks).
@@ -77,6 +79,13 @@ describe MergeRequests::UpdateService do
expect(note).not_to be_nil
expect(note.note).to eq 'Title changed from **Old title** to **New title**'
end
+
+ it 'creates system note about branch change' do
+ note = find_note('Target')
+
+ expect(note).not_to be_nil
+ expect(note.note).to eq 'Target branch changed from `master` to `target`'
+ end
end
end
end
diff --git a/spec/services/projects/destroy_service_spec.rb b/spec/services/projects/destroy_service_spec.rb
new file mode 100644
index 00000000000..cdf576cc0c1
--- /dev/null
+++ b/spec/services/projects/destroy_service_spec.rb
@@ -0,0 +1,34 @@
+require 'spec_helper'
+
+describe Projects::DestroyService do
+ let!(:user) { create(:user) }
+ let!(:project) { create(:project, namespace: user.namespace) }
+ let!(:path) { project.repository.path_to_repo }
+ let!(:remove_path) { path.sub(/\.git\Z/, "+#{project.id}+deleted.git") }
+
+ context 'Sidekiq inline' do
+ before do
+ # Run sidekiq immediatly to check that renamed repository will be removed
+ Sidekiq::Testing.inline! { destroy_project(project, user, {}) }
+ end
+
+ it { Project.all.should_not include(project) }
+ it { Dir.exists?(path).should be_falsey }
+ it { Dir.exists?(remove_path).should be_falsey }
+ end
+
+ context 'Sidekiq fake' do
+ before do
+ # Dont run sidekiq to check if renamed repository exists
+ Sidekiq::Testing.fake! { destroy_project(project, user, {}) }
+ end
+
+ it { Project.all.should_not include(project) }
+ it { Dir.exists?(path).should be_falsey }
+ it { Dir.exists?(remove_path).should be_truthy }
+ end
+
+ def destroy_project(project, user, params)
+ Projects::DestroyService.new(project, user, params).execute
+ end
+end
diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb
index 0dcc94e8bd4..700286b585a 100644
--- a/spec/services/system_note_service_spec.rb
+++ b/spec/services/system_note_service_spec.rb
@@ -228,6 +228,20 @@ describe SystemNoteService do
end
end
+ describe '.change_branch' do
+ subject { described_class.change_branch(noteable, project, author, 'target', old_branch, new_branch) }
+ let(:old_branch) { 'old_branch'}
+ let(:new_branch) { 'new_branch'}
+
+ it_behaves_like 'a system note'
+
+ context 'when target branch name changed' do
+ it 'sets the note text' do
+ expect(subject.note).to eq "Target branch changed from `#{old_branch}` to `#{new_branch}`"
+ end
+ end
+ end
+
describe '.cross_reference' do
subject { described_class.cross_reference(noteable, mentioner, author) }
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 8fe51cf4add..9c8004ab555 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,16 +1,7 @@
-if ENV['SIMPLECOV']
- require 'simplecov'
-end
-
-if ENV['COVERALLS']
- require 'coveralls'
- Coveralls.wear_merged!
-end
-
ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails'
-require 'webmock/rspec'
+require 'shoulda/matchers'
require 'email_spec'
require 'sidekiq/testing/inline'
@@ -18,8 +9,6 @@ require 'sidekiq/testing/inline'
# in spec/support/ and its subdirectories.
Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
-WebMock.disable_net_connect!(allow_localhost: true)
-
RSpec.configure do |config|
config.use_transactional_fixtures = false
config.use_instantiated_fixtures = false
diff --git a/spec/support/api_helpers.rb b/spec/support/api_helpers.rb
index ec9a326a1ea..f63322776d4 100644
--- a/spec/support/api_helpers.rb
+++ b/spec/support/api_helpers.rb
@@ -29,6 +29,6 @@ module ApiHelpers
end
def json_response
- JSON.parse(response.body)
+ @_json_response ||= JSON.parse(response.body)
end
end
diff --git a/spec/support/coverage.rb b/spec/support/coverage.rb
new file mode 100644
index 00000000000..a54bf03380c
--- /dev/null
+++ b/spec/support/coverage.rb
@@ -0,0 +1,8 @@
+if ENV['SIMPLECOV']
+ require 'simplecov'
+end
+
+if ENV['COVERALLS']
+ require 'coveralls'
+ Coveralls.wear_merged!
+end
diff --git a/spec/support/reference_filter_spec_helper.rb b/spec/support/filter_spec_helper.rb
index afbea55ab99..755964e9a3d 100644
--- a/spec/support/reference_filter_spec_helper.rb
+++ b/spec/support/filter_spec_helper.rb
@@ -1,46 +1,23 @@
-# Common methods and setup for Gitlab::Markdown reference filter specs
+# Helper methods for Gitlab::Markdown filter specs
#
# Must be included into specs manually
-module ReferenceFilterSpecHelper
+module FilterSpecHelper
extend ActiveSupport::Concern
- # Shortcut to Rails' auto-generated routes helpers, to avoid including the
- # module
- def urls
- Rails.application.routes.url_helpers
- end
-
- # Modify a String reference to make it invalid
- #
- # Commit SHAs get reversed, IDs get incremented by 1, all other Strings get
- # their word characters reversed.
- #
- # reference - String reference to modify
- #
- # Returns a String
- def invalidate_reference(reference)
- if reference =~ /\A(.+)?.\d+\z/
- # Integer-based reference with optional project prefix
- reference.gsub(/\d+\z/) { |i| i.to_i + 1 }
- elsif reference =~ /\A(.+@)?(\h{6,40}\z)/
- # SHA-based reference with optional prefix
- reference.gsub(/\h{6,40}\z/) { |v| v.reverse }
- else
- reference.gsub(/\w+\z/) { |v| v.reverse }
- end
- end
-
# Perform `call` on the described class
#
- # Automatically passes the current `project` value to the context if none is
- # provided.
+ # Automatically passes the current `project` value, if defined, to the context
+ # if none is provided.
#
- # html - String text to pass to the filter's `call` method.
+ # html - HTML String to pass to the filter's `call` method.
# contexts - Hash context for the filter. (default: {project: project})
#
- # Returns the String text returned by the filter's `call` method.
+ # Returns a Nokogiri::XML::DocumentFragment
def filter(html, contexts = {})
- contexts.reverse_merge!(project: project)
+ if defined?(project)
+ contexts.reverse_merge!(project: project)
+ end
+
described_class.call(html, contexts)
end
@@ -50,7 +27,7 @@ module ReferenceFilterSpecHelper
# body - String text to run through the pipeline
# contexts - Hash context for the filter. (default: {project: project})
#
- # Returns the Hash of the pipeline result
+ # Returns the Hash
def pipeline_result(body, contexts = {})
contexts.reverse_merge!(project: project)
@@ -58,13 +35,43 @@ module ReferenceFilterSpecHelper
pipeline.call(body)
end
+ # Modify a String reference to make it invalid
+ #
+ # Commit SHAs get reversed, IDs get incremented by 1, all other Strings get
+ # their word characters reversed.
+ #
+ # reference - String reference to modify
+ #
+ # Returns a String
+ def invalidate_reference(reference)
+ if reference =~ /\A(.+)?.\d+\z/
+ # Integer-based reference with optional project prefix
+ reference.gsub(/\d+\z/) { |i| i.to_i + 1 }
+ elsif reference =~ /\A(.+@)?(\h{6,40}\z)/
+ # SHA-based reference with optional prefix
+ reference.gsub(/\h{6,40}\z/) { |v| v.reverse }
+ else
+ reference.gsub(/\w+\z/) { |v| v.reverse }
+ end
+ end
+
+ # Stub CrossProjectReference#user_can_reference_project? to return true for
+ # the current test
def allow_cross_reference!
allow_any_instance_of(described_class).
to receive(:user_can_reference_project?).and_return(true)
end
+ # Stub CrossProjectReference#user_can_reference_project? to return false for
+ # the current test
def disallow_cross_reference!
allow_any_instance_of(described_class).
to receive(:user_can_reference_project?).and_return(false)
end
+
+ # Shortcut to Rails' auto-generated routes helpers, to avoid including the
+ # module
+ def urls
+ Rails.application.routes.url_helpers
+ end
end
diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb
index 52b11bd6323..f8cce2ea5a3 100644
--- a/spec/support/matchers.rb
+++ b/spec/support/matchers.rb
@@ -70,7 +70,7 @@ end
# Extend shoulda-matchers
module Shoulda::Matchers::ActiveModel
- class EnsureLengthOfMatcher
+ class ValidateLengthOfMatcher
# Shortcut for is_at_least and is_at_most
def is_within(range)
is_at_least(range.min) && is_at_most(range.max)
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index ede62e8f37a..d29c8a55c82 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -107,17 +107,26 @@ shared_examples 'an editable mentionable' do
it 'creates new cross-reference notes when the mentionable text is edited' do
subject.save
- new_text = <<-MSG
+ new_text = <<-MSG.strip_heredoc
These references already existed:
- Issue: #{mentioned_issue.to_reference}
- Commit: #{mentioned_commit.to_reference}
+
+ Issue: #{mentioned_issue.to_reference}
+
+ Commit: #{mentioned_commit.to_reference}
+
+ ---
This cross-project reference already existed:
- Issue: #{ext_issue.to_reference(project)}
+
+ Issue: #{ext_issue.to_reference(project)}
+
+ ---
These two references are introduced in an edit:
- Issue: #{new_issues[0].to_reference}
- Cross: #{new_issues[1].to_reference(project)}
+
+ Issue: #{new_issues[0].to_reference}
+
+ Cross: #{new_issues[1].to_reference(project)}
MSG
# These three objects were already referenced, and should not receive new
diff --git a/spec/support/webmock.rb b/spec/support/webmock.rb
new file mode 100644
index 00000000000..af2906b7568
--- /dev/null
+++ b/spec/support/webmock.rb
@@ -0,0 +1,4 @@
+require 'webmock'
+require 'webmock/rspec'
+
+WebMock.disable_net_connect!(allow_localhost: true)
diff --git a/spec/teaspoon_env.rb b/spec/teaspoon_env.rb
new file mode 100644
index 00000000000..58f45ff8610
--- /dev/null
+++ b/spec/teaspoon_env.rb
@@ -0,0 +1,178 @@
+Teaspoon.configure do |config|
+ # Determines where the Teaspoon routes will be mounted. Changing this to "/jasmine" would allow you to browse to
+ # `http://localhost:3000/jasmine` to run your tests.
+ config.mount_at = "/teaspoon"
+
+ # Specifies the root where Teaspoon will look for files. If you're testing an engine using a dummy application it can
+ # be useful to set this to your engines root (e.g. `Teaspoon::Engine.root`).
+ # Note: Defaults to `Rails.root` if nil.
+ config.root = nil
+
+ # Paths that will be appended to the Rails assets paths
+ # Note: Relative to `config.root`.
+ config.asset_paths = ["spec/javascripts", "spec/javascripts/stylesheets"]
+
+ # Fixtures are rendered through a controller, which allows using HAML, RABL/JBuilder, etc. Files in these paths will
+ # be rendered as fixtures.
+ config.fixture_paths = ["spec/javascripts/fixtures"]
+
+ # SUITES
+ #
+ # You can modify the default suite configuration and create new suites here. Suites are isolated from one another.
+ #
+ # When defining a suite you can provide a name and a block. If the name is left blank, :default is assumed. You can
+ # omit various directives and the ones defined in the default suite will be used.
+ #
+ # To run a specific suite
+ # - in the browser: http://localhost/teaspoon/[suite_name]
+ # - with the rake task: rake teaspoon suite=[suite_name]
+ # - with the cli: teaspoon --suite=[suite_name]
+ config.suite do |suite|
+ # Specify the framework you would like to use. This allows you to select versions, and will do some basic setup for
+ # you -- which you can override with the directives below. This should be specified first, as it can override other
+ # directives.
+ # Note: If no version is specified, the latest is assumed.
+ #
+ # Versions: 1.3.1, 2.0.3, 2.1.3, 2.2.0
+ suite.use_framework :jasmine, "2.2.0"
+
+ # Specify a file matcher as a regular expression and all matching files will be loaded when the suite is run. These
+ # files need to be within an asset path. You can add asset paths using the `config.asset_paths`.
+ suite.matcher = "{spec/javascripts,app/assets}/**/*_spec.{js,js.coffee,coffee}"
+
+ # Load additional JS files, but requiring them in your spec helper is the preferred way to do this.
+ #suite.javascripts = []
+
+ # You can include your own stylesheets if you want to change how Teaspoon looks.
+ # Note: Spec related CSS can and should be loaded using fixtures.
+ #suite.stylesheets = ["teaspoon"]
+
+ # This suites spec helper, which can require additional support files. This file is loaded before any of your test
+ # files are loaded.
+ suite.helper = "spec_helper"
+
+ # Partial to be rendered in the head tag of the runner. You can use the provided ones or define your own by creating
+ # a `_boot.html.erb` in your fixtures path, and adjust the config to `"/boot"` for instance.
+ #
+ # Available: boot, boot_require_js
+ suite.boot_partial = "boot"
+
+ # Partial to be rendered in the body tag of the runner. You can define your own to create a custom body structure.
+ suite.body_partial = "body"
+
+ # Hooks allow you to use `Teaspoon.hook("fixtures")` before, after, or during your spec run. This will make a
+ # synchronous Ajax request to the server that will call all of the blocks you've defined for that hook name.
+ #suite.hook :fixtures, &proc{}
+
+ # Determine whether specs loaded into the test harness should be embedded as individual script tags or concatenated
+ # into a single file. Similar to Rails' asset `debug: true` and `config.assets.debug = true` options. By default,
+ # Teaspoon expands all assets to provide more valuable stack traces that reference individual source files.
+ #suite.expand_assets = true
+ end
+
+ # Example suite. Since we're just filtering to files already within the root test/javascripts, these files will also
+ # be run in the default suite -- but can be focused into a more specific suite.
+ #config.suite :targeted do |suite|
+ # suite.matcher = "spec/javascripts/targeted/*_spec.{js,js.coffee,coffee}"
+ #end
+
+ # CONSOLE RUNNER SPECIFIC
+ #
+ # These configuration directives are applicable only when running via the rake task or command line interface. These
+ # directives can be overridden using the command line interface arguments or with ENV variables when using the rake
+ # task.
+ #
+ # Command Line Interface:
+ # teaspoon --driver=phantomjs --server-port=31337 --fail-fast=true --format=junit --suite=my_suite /spec/file_spec.js
+ #
+ # Rake:
+ # teaspoon DRIVER=phantomjs SERVER_PORT=31337 FAIL_FAST=true FORMATTERS=junit suite=my_suite
+
+ # Specify which headless driver to use. Supports PhantomJS and Selenium Webdriver.
+ #
+ # Available: :phantomjs, :selenium, :capybara_webkit
+ # PhantomJS: https://github.com/modeset/teaspoon/wiki/Using-PhantomJS
+ # Selenium Webdriver: https://github.com/modeset/teaspoon/wiki/Using-Selenium-WebDriver
+ # Capybara Webkit: https://github.com/modeset/teaspoon/wiki/Using-Capybara-Webkit
+ #config.driver = :phantomjs
+
+ # Specify additional options for the driver.
+ #
+ # PhantomJS: https://github.com/modeset/teaspoon/wiki/Using-PhantomJS
+ # Selenium Webdriver: https://github.com/modeset/teaspoon/wiki/Using-Selenium-WebDriver
+ # Capybara Webkit: https://github.com/modeset/teaspoon/wiki/Using-Capybara-Webkit
+ #config.driver_options = nil
+
+ # Specify the timeout for the driver. Specs are expected to complete within this time frame or the run will be
+ # considered a failure. This is to avoid issues that can arise where tests stall.
+ #config.driver_timeout = 180
+
+ # Specify a server to use with Rack (e.g. thin, mongrel). If nil is provided Rack::Server is used.
+ #config.server = nil
+
+ # Specify a port to run on a specific port, otherwise Teaspoon will use a random available port.
+ #config.server_port = nil
+
+ # Timeout for starting the server in seconds. If your server is slow to start you may have to bump this, or you may
+ # want to lower this if you know it shouldn't take long to start.
+ #config.server_timeout = 20
+
+ # Force Teaspoon to fail immediately after a failing suite. Can be useful to make Teaspoon fail early if you have
+ # several suites, but in environments like CI this may not be desirable.
+ #config.fail_fast = true
+
+ # Specify the formatters to use when outputting the results.
+ # Note: Output files can be specified by using `"junit>/path/to/output.xml"`.
+ #
+ # Available: :dot, :clean, :documentation, :json, :junit, :pride, :rspec_html, :snowday, :swayze_or_oprah, :tap, :tap_y, :teamcity
+ #config.formatters = [:dot]
+
+ # Specify if you want color output from the formatters.
+ #config.color = true
+
+ # Teaspoon pipes all console[log/debug/error] to $stdout. This is useful to catch places where you've forgotten to
+ # remove them, but in verbose applications this may not be desirable.
+ #config.suppress_log = false
+
+ # COVERAGE REPORTS / THRESHOLD ASSERTIONS
+ #
+ # Coverage reports requires Istanbul (https://github.com/gotwarlost/istanbul) to add instrumentation to your code and
+ # display coverage statistics.
+ #
+ # Coverage configurations are similar to suites. You can define several, and use different ones under different
+ # conditions.
+ #
+ # To run with a specific coverage configuration
+ # - with the rake task: rake teaspoon USE_COVERAGE=[coverage_name]
+ # - with the cli: teaspoon --coverage=[coverage_name]
+
+ # Specify that you always want a coverage configuration to be used. Otherwise, specify that you want coverage
+ # on the CLI.
+ # Set this to "true" or the name of your coverage config.
+ #config.use_coverage = nil
+
+ # You can have multiple coverage configs by passing a name to config.coverage.
+ # e.g. config.coverage :ci do |coverage|
+ # The default coverage config name is :default.
+ config.coverage do |coverage|
+ # Which coverage reports Istanbul should generate. Correlates directly to what Istanbul supports.
+ #
+ # Available: text-summary, text, html, lcov, lcovonly, cobertura, teamcity
+ #coverage.reports = ["text-summary", "html"]
+
+ # The path that the coverage should be written to - when there's an artifact to write to disk.
+ # Note: Relative to `config.root`.
+ #coverage.output_path = "coverage"
+
+ # Assets to be ignored when generating coverage reports. Accepts an array of filenames or regular expressions. The
+ # default excludes assets from vendor, gems and support libraries.
+ #coverage.ignore = [%r{/lib/ruby/gems/}, %r{/vendor/assets/}, %r{/support/}, %r{/(.+)_helper.}]
+
+ # Various thresholds requirements can be defined, and those thresholds will be checked at the end of a run. If any
+ # aren't met the run will fail with a message. Thresholds can be defined as a percentage (0-100), or nil.
+ #coverage.statements = nil
+ #coverage.functions = nil
+ #coverage.branches = nil
+ #coverage.lines = nil
+ end
+end
diff --git a/vendor/assets/javascripts/jasmine-fixture.js b/vendor/assets/javascripts/jasmine-fixture.js
deleted file mode 100755
index 9980aec6ddb..00000000000
--- a/vendor/assets/javascripts/jasmine-fixture.js
+++ /dev/null
@@ -1,433 +0,0 @@
-/* jasmine-fixture - 1.3.1
- * Makes injecting HTML snippets into the DOM easy & clean!
- * https://github.com/searls/jasmine-fixture
- */
-(function() {
- var createHTMLBlock,
- __slice = [].slice;
-
- (function($) {
- var ewwSideEffects, jasmineFixture, originalAffix, originalJasmineDotFixture, originalJasmineFixture, root, _, _ref;
- root = (1, eval)('this');
- originalJasmineFixture = root.jasmineFixture;
- originalJasmineDotFixture = (_ref = root.jasmine) != null ? _ref.fixture : void 0;
- originalAffix = root.affix;
- _ = function(list) {
- return {
- inject: function(iterator, memo) {
- var item, _i, _len, _results;
- _results = [];
- for (_i = 0, _len = list.length; _i < _len; _i++) {
- item = list[_i];
- _results.push(memo = iterator(memo, item));
- }
- return _results;
- }
- };
- };
- root.jasmineFixture = function($) {
- var $whatsTheRootOf, affix, create, jasmineFixture, noConflict;
- affix = function(selectorOptions) {
- return create.call(this, selectorOptions, true);
- };
- create = function(selectorOptions, attach) {
- var $top;
- $top = null;
- _(selectorOptions.split(/[ ](?![^\{]*\})(?=[^\]]*?(?:\[|$))/)).inject(function($parent, elementSelector) {
- var $el;
- if (elementSelector === ">") {
- return $parent;
- }
- $el = createHTMLBlock($, elementSelector);
- if (attach || $top) {
- $el.appendTo($parent);
- }
- $top || ($top = $el);
- return $el;
- }, $whatsTheRootOf(this));
- return $top;
- };
- noConflict = function() {
- var currentJasmineFixture, _ref1;
- currentJasmineFixture = jasmine.fixture;
- root.jasmineFixture = originalJasmineFixture;
- if ((_ref1 = root.jasmine) != null) {
- _ref1.fixture = originalJasmineDotFixture;
- }
- root.affix = originalAffix;
- return currentJasmineFixture;
- };
- $whatsTheRootOf = function(that) {
- if (that.jquery != null) {
- return that;
- } else if ($('#jasmine_content').length > 0) {
- return $('#jasmine_content');
- } else {
- return $('<div id="jasmine_content"></div>').appendTo('body');
- }
- };
- jasmineFixture = {
- affix: affix,
- create: create,
- noConflict: noConflict
- };
- ewwSideEffects(jasmineFixture);
- return jasmineFixture;
- };
- ewwSideEffects = function(jasmineFixture) {
- var _ref1;
- if ((_ref1 = root.jasmine) != null) {
- _ref1.fixture = jasmineFixture;
- }
- $.fn.affix = root.affix = jasmineFixture.affix;
- return afterEach(function() {
- return $('#jasmine_content').remove();
- });
- };
- if ($) {
- return jasmineFixture = root.jasmineFixture($);
- } else {
- return root.affix = function() {
- var nowJQueryExists;
- nowJQueryExists = window.jQuery || window.$;
- if (nowJQueryExists != null) {
- jasmineFixture = root.jasmineFixture(nowJQueryExists);
- return affix.call.apply(affix, [this].concat(__slice.call(arguments)));
- } else {
- throw new Error("jasmine-fixture requires jQuery to be defined at window.jQuery or window.$");
- }
- };
- }
- })(window.jQuery || window.$);
-
- createHTMLBlock = (function() {
- var bindData, bindEvents, parseAttributes, parseClasses, parseContents, parseEnclosure, parseReferences, parseVariableScope, regAttr, regAttrDfn, regAttrs, regCBrace, regClass, regClasses, regData, regDatas, regEvent, regEvents, regExclamation, regId, regReference, regTag, regTagNotContent, regZenTagDfn;
- createHTMLBlock = function($, ZenObject, data, functions, indexes) {
- var ZenCode, arr, block, blockAttrs, blockClasses, blockHTML, blockId, blockTag, blocks, el, el2, els, forScope, indexName, inner, len, obj, origZenCode, paren, result, ret, zc, zo;
- if ($.isPlainObject(ZenObject)) {
- ZenCode = ZenObject.main;
- } else {
- ZenCode = ZenObject;
- ZenObject = {
- main: ZenCode
- };
- }
- origZenCode = ZenCode;
- if (indexes === undefined) {
- indexes = {};
- }
- if (ZenCode.charAt(0) === "!" || $.isArray(data)) {
- if ($.isArray(data)) {
- forScope = ZenCode;
- } else {
- obj = parseEnclosure(ZenCode, "!");
- obj = obj.substring(obj.indexOf(":") + 1, obj.length - 1);
- forScope = parseVariableScope(ZenCode);
- }
- while (forScope.charAt(0) === "@") {
- forScope = parseVariableScope("!for:!" + parseReferences(forScope, ZenObject));
- }
- zo = ZenObject;
- zo.main = forScope;
- el = $();
- if (ZenCode.substring(0, 5) === "!for:" || $.isArray(data)) {
- if (!$.isArray(data) && obj.indexOf(":") > 0) {
- indexName = obj.substring(0, obj.indexOf(":"));
- obj = obj.substr(obj.indexOf(":") + 1);
- }
- arr = ($.isArray(data) ? data : data[obj]);
- zc = zo.main;
- if ($.isArray(arr) || $.isPlainObject(arr)) {
- $.map(arr, function(value, index) {
- var next;
- zo.main = zc;
- if (indexName !== undefined) {
- indexes[indexName] = index;
- }
- if (!$.isPlainObject(value)) {
- value = {
- value: value
- };
- }
- next = createHTMLBlock($, zo, value, functions, indexes);
- if (el.length !== 0) {
- return $.each(next, function(index, value) {
- return el.push(value);
- });
- }
- });
- }
- if (!$.isArray(data)) {
- ZenCode = ZenCode.substr(obj.length + 6 + forScope.length);
- } else {
- ZenCode = "";
- }
- } else if (ZenCode.substring(0, 4) === "!if:") {
- result = parseContents("!" + obj + "!", data, indexes);
- if (result !== "undefined" || result !== "false" || result !== "") {
- el = createHTMLBlock($, zo, data, functions, indexes);
- }
- ZenCode = ZenCode.substr(obj.length + 5 + forScope.length);
- }
- ZenObject.main = ZenCode;
- } else if (ZenCode.charAt(0) === "(") {
- paren = parseEnclosure(ZenCode, "(", ")");
- inner = paren.substring(1, paren.length - 1);
- ZenCode = ZenCode.substr(paren.length);
- zo = ZenObject;
- zo.main = inner;
- el = createHTMLBlock($, zo, data, functions, indexes);
- } else {
- blocks = ZenCode.match(regZenTagDfn);
- block = blocks[0];
- if (block.length === 0) {
- return "";
- }
- if (block.indexOf("@") >= 0) {
- ZenCode = parseReferences(ZenCode, ZenObject);
- zo = ZenObject;
- zo.main = ZenCode;
- return createHTMLBlock($, zo, data, functions, indexes);
- }
- block = parseContents(block, data, indexes);
- blockClasses = parseClasses($, block);
- if (regId.test(block)) {
- blockId = regId.exec(block)[1];
- }
- blockAttrs = parseAttributes(block, data);
- blockTag = (block.charAt(0) === "{" ? "span" : "div");
- if (ZenCode.charAt(0) !== "#" && ZenCode.charAt(0) !== "." && ZenCode.charAt(0) !== "{") {
- blockTag = regTag.exec(block)[1];
- }
- if (block.search(regCBrace) !== -1) {
- blockHTML = block.match(regCBrace)[1];
- }
- blockAttrs = $.extend(blockAttrs, {
- id: blockId,
- "class": blockClasses,
- html: blockHTML
- });
- el = $("<" + blockTag + ">", blockAttrs);
- el.attr(blockAttrs);
- el = bindEvents(block, el, functions);
- el = bindData(block, el, data);
- ZenCode = ZenCode.substr(blocks[0].length);
- ZenObject.main = ZenCode;
- }
- if (ZenCode.length > 0) {
- if (ZenCode.charAt(0) === ">") {
- if (ZenCode.charAt(1) === "(") {
- zc = parseEnclosure(ZenCode.substr(1), "(", ")");
- ZenCode = ZenCode.substr(zc.length + 1);
- } else if (ZenCode.charAt(1) === "!") {
- obj = parseEnclosure(ZenCode.substr(1), "!");
- forScope = parseVariableScope(ZenCode.substr(1));
- zc = obj + forScope;
- ZenCode = ZenCode.substr(zc.length + 1);
- } else {
- len = Math.max(ZenCode.indexOf("+"), ZenCode.length);
- zc = ZenCode.substring(1, len);
- ZenCode = ZenCode.substr(len);
- }
- zo = ZenObject;
- zo.main = zc;
- els = $(createHTMLBlock($, zo, data, functions, indexes));
- els.appendTo(el);
- }
- if (ZenCode.charAt(0) === "+") {
- zo = ZenObject;
- zo.main = ZenCode.substr(1);
- el2 = createHTMLBlock($, zo, data, functions, indexes);
- $.each(el2, function(index, value) {
- return el.push(value);
- });
- }
- }
- ret = el;
- return ret;
- };
- bindData = function(ZenCode, el, data) {
- var datas, i, split;
- if (ZenCode.search(regDatas) === 0) {
- return el;
- }
- datas = ZenCode.match(regDatas);
- if (datas === null) {
- return el;
- }
- i = 0;
- while (i < datas.length) {
- split = regData.exec(datas[i]);
- if (split[3] === undefined) {
- $(el).data(split[1], data[split[1]]);
- } else {
- $(el).data(split[1], data[split[3]]);
- }
- i++;
- }
- return el;
- };
- bindEvents = function(ZenCode, el, functions) {
- var bindings, fn, i, split;
- if (ZenCode.search(regEvents) === 0) {
- return el;
- }
- bindings = ZenCode.match(regEvents);
- if (bindings === null) {
- return el;
- }
- i = 0;
- while (i < bindings.length) {
- split = regEvent.exec(bindings[i]);
- if (split[2] === undefined) {
- fn = functions[split[1]];
- } else {
- fn = functions[split[2]];
- }
- $(el).bind(split[1], fn);
- i++;
- }
- return el;
- };
- parseAttributes = function(ZenBlock, data) {
- var attrStrs, attrs, i, parts;
- if (ZenBlock.search(regAttrDfn) === -1) {
- return undefined;
- }
- attrStrs = ZenBlock.match(regAttrDfn);
- attrs = {};
- i = 0;
- while (i < attrStrs.length) {
- parts = regAttr.exec(attrStrs[i]);
- attrs[parts[1]] = "";
- if (parts[3] !== undefined) {
- attrs[parts[1]] = parseContents(parts[3], data);
- }
- i++;
- }
- return attrs;
- };
- parseClasses = function($, ZenBlock) {
- var classes, clsString, i;
- ZenBlock = ZenBlock.match(regTagNotContent)[0];
- if (ZenBlock.search(regClasses) === -1) {
- return undefined;
- }
- classes = ZenBlock.match(regClasses);
- clsString = "";
- i = 0;
- while (i < classes.length) {
- clsString += " " + regClass.exec(classes[i])[1];
- i++;
- }
- return $.trim(clsString);
- };
- parseContents = function(ZenBlock, data, indexes) {
- var html;
- if (indexes === undefined) {
- indexes = {};
- }
- html = ZenBlock;
- if (data === undefined) {
- return html;
- }
- while (regExclamation.test(html)) {
- html = html.replace(regExclamation, function(str, str2) {
- var begChar, fn, val;
- begChar = "";
- if (str.indexOf("!for:") > 0 || str.indexOf("!if:") > 0) {
- return str;
- }
- if (str.charAt(0) !== "!") {
- begChar = str.charAt(0);
- str = str.substring(2, str.length - 1);
- }
- fn = new Function("data", "indexes", "var r=undefined;" + "with(data){try{r=" + str + ";}catch(e){}}" + "with(indexes){try{if(r===undefined)r=" + str + ";}catch(e){}}" + "return r;");
- val = unescape(fn(data, indexes));
- return begChar + val;
- });
- }
- html = html.replace(/\\./g, function(str) {
- return str.charAt(1);
- });
- return unescape(html);
- };
- parseEnclosure = function(ZenCode, open, close, count) {
- var index, ret;
- if (close === undefined) {
- close = open;
- }
- index = 1;
- if (count === undefined) {
- count = (ZenCode.charAt(0) === open ? 1 : 0);
- }
- if (count === 0) {
- return;
- }
- while (count > 0 && index < ZenCode.length) {
- if (ZenCode.charAt(index) === close && ZenCode.charAt(index - 1) !== "\\") {
- count--;
- } else {
- if (ZenCode.charAt(index) === open && ZenCode.charAt(index - 1) !== "\\") {
- count++;
- }
- }
- index++;
- }
- ret = ZenCode.substring(0, index);
- return ret;
- };
- parseReferences = function(ZenCode, ZenObject) {
- ZenCode = ZenCode.replace(regReference, function(str) {
- var fn;
- str = str.substr(1);
- fn = new Function("objs", "var r=\"\";" + "with(objs){try{" + "r=" + str + ";" + "}catch(e){}}" + "return r;");
- return fn(ZenObject, parseReferences);
- });
- return ZenCode;
- };
- parseVariableScope = function(ZenCode) {
- var forCode, rest, tag;
- if (ZenCode.substring(0, 5) !== "!for:" && ZenCode.substring(0, 4) !== "!if:") {
- return undefined;
- }
- forCode = parseEnclosure(ZenCode, "!");
- ZenCode = ZenCode.substr(forCode.length);
- if (ZenCode.charAt(0) === "(") {
- return parseEnclosure(ZenCode, "(", ")");
- }
- tag = ZenCode.match(regZenTagDfn)[0];
- ZenCode = ZenCode.substr(tag.length);
- if (ZenCode.length === 0 || ZenCode.charAt(0) === "+") {
- return tag;
- } else if (ZenCode.charAt(0) === ">") {
- rest = "";
- rest = parseEnclosure(ZenCode.substr(1), "(", ")", 1);
- return tag + ">" + rest;
- }
- return undefined;
- };
- regZenTagDfn = /([#\.\@]?[\w-]+|\[([\w-!?=:"']+(="([^"]|\\")+")? {0,})+\]|\~[\w$]+=[\w$]+|&[\w$]+(=[\w$]+)?|[#\.\@]?!([^!]|\\!)+!){0,}(\{([^\}]|\\\})+\})?/i;
- regTag = /(\w+)/i;
- regId = /(?:^|\b)#([\w-!]+)/i;
- regTagNotContent = /((([#\.]?[\w-]+)?(\[([\w!]+(="([^"]|\\")+")? {0,})+\])?)+)/i;
- /*
- See lookahead syntax (?!) at https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp
- */
-
- regClasses = /(\.[\w-]+)(?!["\w])/g;
- regClass = /\.([\w-]+)/i;
- regReference = /(@[\w$_][\w$_\d]+)/i;
- regAttrDfn = /(\[([\w-!]+(="?([^"]|\\")+"?)? {0,})+\])/ig;
- regAttrs = /([\w-!]+(="([^"]|\\")+")?)/g;
- regAttr = /([\w-!]+)(="?((([\w]+(\[.*?\])+)|[^"\]]|\\")+)"?)?/i;
- regCBrace = /\{(([^\}]|\\\})+)\}/i;
- regExclamation = /(?:([^\\]|^))!([^!]|\\!)+!/g;
- regEvents = /\~[\w$]+(=[\w$]+)?/g;
- regEvent = /\~([\w$]+)=([\w$]+)/i;
- regDatas = /&[\w$]+(=[\w$]+)?/g;
- regData = /&([\w$]+)(=([\w$]+))?/i;
- return createHTMLBlock;
- })();
-
-}).call(this);