Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG37
-rw-r--r--Gemfile26
-rw-r--r--Gemfile.lock15
-rw-r--r--README.md76
-rw-r--r--app/assets/images/msapplication-tile.pngbin0 -> 6102 bytes
-rw-r--r--app/assets/images/touch-icon-ipad-retina.pngbin0 -> 8130 bytes
-rw-r--r--app/assets/images/touch-icon-ipad.pngbin0 -> 3493 bytes
-rw-r--r--app/assets/images/touch-icon-iphone-retina.pngbin0 -> 4997 bytes
-rw-r--r--app/assets/images/touch-icon-iphone.pngbin0 -> 2766 bytes
-rw-r--r--app/assets/javascripts/application.js.coffee3
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee8
-rw-r--r--app/assets/javascripts/dropzone_input.js.coffee6
-rw-r--r--app/assets/javascripts/line_highlighter.js.coffee2
-rw-r--r--app/assets/javascripts/merge_request.js.coffee12
-rw-r--r--app/assets/javascripts/notes.js.coffee2
-rw-r--r--app/assets/javascripts/pager.js.coffee2
-rw-r--r--app/assets/javascripts/project_show.js.coffee14
-rw-r--r--app/assets/javascripts/shortcuts_navigation.coffee1
-rw-r--r--app/assets/stylesheets/base/mixins.scss2
-rw-r--r--app/assets/stylesheets/generic/mobile.scss24
-rw-r--r--app/assets/stylesheets/generic/sidebar.scss29
-rw-r--r--app/assets/stylesheets/generic/typography.scss8
-rw-r--r--app/assets/stylesheets/pages/notes.scss16
-rw-r--r--app/assets/stylesheets/pages/projects.scss73
-rw-r--r--app/assets/stylesheets/themes/gitlab-theme.scss2
-rw-r--r--app/controllers/admin/projects_controller.rb3
-rw-r--r--app/controllers/admin/users_controller.rb6
-rw-r--r--app/controllers/application_controller.rb6
-rw-r--r--app/controllers/autocomplete_controller.rb37
-rw-r--r--app/controllers/profiles/preferences_controller.rb1
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb8
-rw-r--r--app/controllers/projects/branches_controller.rb4
-rw-r--r--app/controllers/projects/graphs_controller.rb7
-rw-r--r--app/controllers/projects/milestones_controller.rb7
-rw-r--r--app/controllers/projects/refs_controller.rb26
-rw-r--r--app/controllers/projects/tree_controller.rb4
-rw-r--r--app/controllers/projects_controller.rb38
-rw-r--r--app/finders/issuable_finder.rb4
-rw-r--r--app/helpers/application_helper.rb8
-rw-r--r--app/helpers/application_settings_helper.rb2
-rw-r--r--app/helpers/gitlab_markdown_helper.rb2
-rw-r--r--app/helpers/gitlab_routing_helper.rb4
-rw-r--r--app/helpers/milestones_helper.rb2
-rw-r--r--app/helpers/preferences_helper.rb12
-rw-r--r--app/helpers/projects_helper.rb104
-rw-r--r--app/helpers/visibility_level_helper.rb6
-rw-r--r--app/models/ability.rb55
-rw-r--r--app/models/concerns/mentionable.rb42
-rw-r--r--app/models/key.rb1
-rw-r--r--app/models/milestone.rb4
-rw-r--r--app/models/note.rb2
-rw-r--r--app/models/project.rb4
-rw-r--r--app/models/project_services/gitlab_ci_service.rb8
-rw-r--r--app/models/project_services/irker_service.rb2
-rw-r--r--app/models/repository.rb80
-rw-r--r--app/models/user.rb14
-rw-r--r--app/services/git_push_service.rb2
-rw-r--r--app/services/git_tag_push_service.rb6
-rw-r--r--app/services/issues/update_service.rb13
-rw-r--r--app/services/merge_requests/update_service.rb14
-rw-r--r--app/services/projects/transfer_service.rb13
-rw-r--r--app/views/admin/users/show.html.haml1
-rw-r--r--app/views/events/_event.html.haml1
-rw-r--r--app/views/explore/projects/_project.html.haml2
-rw-r--r--app/views/help/_shortcuts.html.haml6
-rw-r--r--app/views/layouts/_head.html.haml19
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--app/views/layouts/nav/_group.html.haml10
-rw-r--r--app/views/layouts/nav/_group_settings.html.haml2
-rw-r--r--app/views/layouts/nav/_profile.html.haml8
-rw-r--r--app/views/layouts/nav/_project.html.haml22
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml2
-rw-r--r--app/views/profiles/applications.html.haml2
-rw-r--r--app/views/profiles/preferences/show.html.haml8
-rw-r--r--app/views/projects/_activity.html.haml15
-rw-r--r--app/views/projects/_aside.html.haml106
-rw-r--r--app/views/projects/_home_panel.html.haml59
-rw-r--r--app/views/projects/_last_push.html.haml14
-rw-r--r--app/views/projects/_readme.html.haml24
-rw-r--r--app/views/projects/_section.html.haml36
-rw-r--r--app/views/projects/_zen.html.haml2
-rw-r--r--app/views/projects/activity.html.haml1
-rw-r--r--app/views/projects/blob/show.html.haml3
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml31
-rw-r--r--app/views/projects/buttons/_fork.html.haml13
-rw-r--r--app/views/projects/buttons/_star.html.haml22
-rw-r--r--app/views/projects/diffs/_warning.html.haml2
-rw-r--r--app/views/projects/edit.html.haml4
-rw-r--r--app/views/projects/empty.html.haml34
-rw-r--r--app/views/projects/graphs/commits.html.haml4
-rw-r--r--app/views/projects/graphs/show.html.haml6
-rw-r--r--app/views/projects/labels/_label.html.haml4
-rw-r--r--app/views/projects/merge_requests/_show.html.haml10
-rw-r--r--app/views/projects/merge_requests/branch_from.js.haml1
-rw-r--r--app/views/projects/merge_requests/branch_to.js.haml1
-rw-r--r--app/views/projects/merge_requests/index.html.haml1
-rw-r--r--app/views/projects/milestones/_milestone.html.haml4
-rw-r--r--app/views/projects/milestones/show.html.haml3
-rw-r--r--app/views/projects/new.html.haml2
-rw-r--r--app/views/projects/notes/_edit_form.html.haml4
-rw-r--r--app/views/projects/notes/_form.html.haml10
-rw-r--r--app/views/projects/notes/_note.html.haml2
-rw-r--r--app/views/projects/show.html.haml57
-rw-r--r--app/views/projects/tree/show.html.haml4
-rw-r--r--app/views/search/results/_blob.html.haml1
-rw-r--r--app/views/search/results/_wiki_blob.html.haml1
-rw-r--r--app/views/shared/_clone_panel.html.haml2
-rw-r--r--app/views/shared/_field.html.haml4
-rw-r--r--app/views/shared/_visibility_radios.html.haml1
-rw-r--r--app/views/shared/issuable/_context.html.haml4
-rw-r--r--app/views/shared/issuable/_filter.html.haml8
-rw-r--r--app/workers/project_cache_worker.rb15
-rw-r--r--app/workers/repository_import_worker.rb2
-rw-r--r--config/application.rb1
-rw-r--r--config/initializers/7_omniauth.rb1
-rw-r--r--config/initializers/doorkeeper.rb3
-rw-r--r--config/routes.rb4
-rw-r--r--db/fixtures/development/01_admin.rb2
-rw-r--r--db/fixtures/development/04_project.rb35
-rw-r--r--db/fixtures/production/001_admin.rb2
-rw-r--r--db/migrate/20150713160110_add_project_view_to_users.rb5
-rw-r--r--db/migrate/20150717130904_add_commits_count_to_project.rb5
-rw-r--r--db/schema.rb8
-rw-r--r--doc/api/README.md3
-rw-r--r--doc/api/merge_requests.md15
-rw-r--r--doc/api/projects.md11
-rw-r--r--doc/api/settings.md88
-rw-r--r--doc/gitlab-basics/README.md6
-rw-r--r--doc/gitlab-basics/basic-git-commands.md32
-rw-r--r--doc/gitlab-basics/basicsimages/click-on-new-group.pngbin0 -> 2063 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/select-group.pngbin0 -> 6075 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/select-group2.pngbin0 -> 5049 bytes
-rw-r--r--doc/gitlab-basics/command-line-commands.md30
-rw-r--r--doc/gitlab-basics/create-branch.md39
-rw-r--r--doc/gitlab-basics/create-group.md43
-rw-r--r--doc/gitlab-basics/create-project.md8
-rw-r--r--doc/gitlab-basics/create-your-ssh-keys.md20
-rw-r--r--doc/gitlab-basics/fork-project.md19
-rw-r--r--doc/gitlab-basics/start-using-git.md36
-rw-r--r--doc/install/installation.md2
-rw-r--r--doc/install/requirements.md27
-rw-r--r--doc/profile/preferences.md6
-rw-r--r--doc/profile/two_factor_authentication.md5
-rw-r--r--doc/project_services/irker.md2
-rw-r--r--doc/raketasks/backup_restore.md35
-rw-r--r--doc/release/monthly.md1
-rw-r--r--doc/ssh/README.md3
-rw-r--r--doc/update/6.x-or-7.x-to-7.13.md (renamed from doc/update/6.x-or-7.x-to-7.12.md)22
-rw-r--r--doc/update/7.12-to-7.13.md129
-rw-r--r--doc/update/mysql_to_postgresql.md7
-rw-r--r--doc/workflow/README.md2
-rw-r--r--doc/workflow/labels.md4
-rw-r--r--doc/workflow/notifications.md6
-rw-r--r--docker/Dockerfile (renamed from docker/single/Dockerfile)19
-rw-r--r--docker/README.md181
-rw-r--r--docker/app/Dockerfile32
-rwxr-xr-xdocker/app/assets/wrapper17
-rwxr-xr-xdocker/assets/wrapper (renamed from docker/single/assets/wrapper)5
-rw-r--r--docker/data/Dockerfile8
-rw-r--r--docker/data/assets/gitlab.rb37
-rw-r--r--docker/fig.yml2
-rw-r--r--docker/marathon.json31
-rw-r--r--docker/single/assets/gitlab.rb37
-rw-r--r--docker/single/marathon.json14
-rw-r--r--docker/troubleshooting.md43
-rw-r--r--features/groups.feature4
-rw-r--r--features/project/issues/issues.feature1
-rw-r--r--features/project/issues/milestones.feature4
-rw-r--r--features/project/project.feature27
-rw-r--r--features/project/shortcuts.feature11
-rw-r--r--features/steps/groups.rb4
-rw-r--r--features/steps/project/issues/issues.rb5
-rw-r--r--features/steps/project/issues/milestones.rb8
-rw-r--r--features/steps/project/project.rb24
-rw-r--r--features/steps/project/project_shortcuts.rb5
-rw-r--r--features/steps/project/source/browse_files.rb2
-rw-r--r--features/steps/project/star.rb2
-rw-r--r--features/steps/shared/paths.rb4
-rw-r--r--features/steps/shared/project_tab.rb4
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/entities.rb23
-rw-r--r--lib/api/groups.rb4
-rw-r--r--lib/api/settings.rb35
-rw-r--r--lib/backup/database.rb22
-rw-r--r--lib/gitlab/google_code_import/importer.rb2
-rw-r--r--lib/gitlab/markup_helper.rb10
-rw-r--r--lib/gitlab/visibility_level.rb4
-rw-r--r--lib/repository_cache.rb8
-rwxr-xr-xlib/support/init.d/gitlab2
-rw-r--r--lib/tasks/gitlab/db/drop_all_postgres_sequences.rake10
-rw-r--r--lib/tasks/gitlab/db/drop_all_tables.rake10
-rw-r--r--lib/tasks/gitlab/test.rake2
-rw-r--r--spec/controllers/admin/users_controller_spec.rb28
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb92
-rw-r--r--spec/controllers/branches_controller_spec.rb26
-rw-r--r--spec/controllers/profiles/two_factor_auths_controller_spec.rb13
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb28
-rw-r--r--spec/controllers/projects/tree_controller_spec.rb (renamed from spec/controllers/tree_controller_spec.rb)29
-rw-r--r--spec/factories.rb1
-rw-r--r--spec/features/admin/admin_disables_two_factor_spec.rb33
-rw-r--r--spec/features/groups_spec.rb2
-rw-r--r--spec/features/issues/filter_by_milestone_spec.rb36
-rw-r--r--spec/features/issues_spec.rb16
-rw-r--r--spec/features/login_spec.rb2
-rw-r--r--spec/features/markdown_spec.rb2
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb36
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb2
-rw-r--r--spec/features/password_reset_spec.rb2
-rw-r--r--spec/features/profile_spec.rb6
-rw-r--r--spec/features/profiles/preferences_spec.rb2
-rw-r--r--spec/features/projects_spec.rb2
-rw-r--r--spec/features/users_spec.rb2
-rw-r--r--spec/fixtures/GoogleCodeProjectHosting.json5
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb2
-rw-r--r--spec/helpers/projects_helper_spec.rb44
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb39
-rw-r--r--spec/javascripts/fixtures/line_highlighter.html.haml4
-rw-r--r--spec/javascripts/line_highlighter_spec.js.coffee8
-rw-r--r--spec/lib/gitlab/google_code_import/importer_spec.rb1
-rw-r--r--spec/models/concerns/mentionable_spec.rb49
-rw-r--r--spec/models/key_spec.rb8
-rw-r--r--spec/models/project_services/gitlab_ci_service_spec.rb27
-rw-r--r--spec/models/repository_spec.rb24
-rw-r--r--spec/models/user_spec.rb18
-rw-r--r--spec/requests/api/branches_spec.rb5
-rw-r--r--spec/requests/api/projects_spec.rb6
-rw-r--r--spec/requests/api/services_spec.rb14
-rw-r--r--spec/requests/api/settings_spec.rb29
-rw-r--r--spec/services/create_snippet_service_spec.rb6
-rw-r--r--spec/services/git_push_service_spec.rb8
-rw-r--r--spec/services/projects/create_service_spec.rb4
-rw-r--r--spec/services/projects/transfer_service_spec.rb10
-rw-r--r--spec/services/projects/update_service_spec.rb4
-rw-r--r--spec/services/update_snippet_service_spec.rb6
-rw-r--r--spec/spec_helper.rb11
-rw-r--r--spec/support/coverage.rb8
-rw-r--r--spec/support/mentionable_shared_examples.rb2
-rw-r--r--spec/support/stub_configuration.rb26
-rw-r--r--spec/support/test_env.rb11
-rw-r--r--vendor/assets/javascripts/jquery.nicescroll.min.js118
240 files changed, 2566 insertions, 1133 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 1c2155c0f9c..d0f7f775916 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,24 +1,39 @@
Please view this file on the master branch, on stable branches it's out of date.
+v 7.14.0 (unreleased)
+ - Fix full screen mode for snippet comments (Daniel Gerhardt)
+ - Fix 404 error in files view after deleting the last file in a repository (Stan Hu)
+ - Fix label read access for unauthenticated users (Daniel Gerhardt)
+ - Fix access to disabled features for unauthenticated users (Daniel Gerhardt)
+ - Fix OAuth provider bug where GitLab would not go return to the redirect_uri after sign-in (Stan Hu)
+ - Fix file upload dialog for comment editing (Daniel Gerhardt)
+ - Set OmniAuth full_host parameter to ensure redirect URIs are correct (Stan Hu)
+ - Expire Rails cache entries after two weeks to prevent endless Redis growth
+ - Add support for destroying project milestones (Stan Hu)
+ - Add fetch command to the MR page.
+
v 7.13.0 (unreleased)
+ - Remove repository graph log to fix slow cache updates after push event (Stan Hu)
+ - Only enable HSTS header for HTTPS and port 443 (Stan Hu)
+ - Fix user autocomplete for unauthenticated users accessing public projects (Stan Hu)
- Fix redirection to home page URL for unauthorized users (Daniel Gerhardt)
+ - Add branch switching support for graphs (Daniel Gerhardt)
- Fix external issue tracker hook/test for HTTPS URLs (Daniel Gerhardt)
- Remove link leading to a 404 error in Deploy Keys page (Stan Hu)
- Add support for unlocking users in admin settings (Stan Hu)
- Add Irker service configuration options (Stan Hu)
- - Fix order of issues imported form GitHub (Hiroyuki Sato)
+ - Fix order of issues imported from GitHub (Hiroyuki Sato)
- Bump rugments to 1.0.0beta8 to fix C prototype function highlighting (Jonathon Reinhart)
- Fix Merge Request webhook to properly fire "merge" action when accepted from the web UI
- Add `two_factor_enabled` field to admin user API (Stan Hu)
- Fix invalid timestamps in RSS feeds (Rowan Wookey)
- - Fix error when deleting a user who has projects (Stan Hu)
- Fix downloading of patches on public merge requests when user logged out (Stan Hu)
- - The password for the default administrator (root) account has been changed from "5iveL!fe" to "password".
- Fix Error 500 when relative submodule resolves to a namespace that has a different name from its path (Stan Hu)
- Extract the longest-matching ref from a commit path when multiple matches occur (Stan Hu)
- Update maintenance documentation to explain no need to recompile asssets for omnibus installations (Stan Hu)
- Support commenting on diffs in side-by-side mode (Stan Hu)
- Fix JavaScript error when clicking on the comment button on a diff line that has a comment already (Stan Hu)
+ - Return 40x error codes if branch could not be deleted in UI (Stan Hu)
- Remove project visibility icons from dashboard projects list
- Rename "Design" profile settings page to "Preferences".
- Allow users to customize their default Dashboard page.
@@ -38,11 +53,21 @@ v 7.13.0 (unreleased)
- Query Optimization in MySQL.
- Allow users to be blocked and unblocked via the API
- Allow custom backup archive permissions
+ - Use native Postgres database cleaning during backup restore
+ - Redesign project page. Show README as default instead of activity. Move project activity to separate page
+ - Make left menu more hierarchical and less contextual by adding back item at top
+ - A fork can’t have a visibility level that is greater than the original project.
+ - Faster code search in repository and wiki. Fixes search page timeout for big repositories
+ - Allow administrators to disable 2FA for a specific user
+ - Add error message for SSH key linebreaks
+ - Store commits count in database (will populate with valid values only after first push)
+ - Rebuild cache after push to repository in background job
v 7.12.2
- Correctly show anonymous authorized applications under Profile > Applications.
- Faster automerge check and merge itself when source and target branches are in same repository
- Audit log for user authentication
+ - Fix transferring of project to another group using the API.
v 7.12.1
- Fix error when deleting a user who has projects (Stan Hu)
@@ -52,6 +77,8 @@ v 7.12.1
- Fix closed merge request scope at milestone page (Dmitriy Zaporozhets)
- Revert merge request states renaming
- Fix hooks for web based events with external issue references (Daniel Gerhardt)
+ - Improve performance for issue and merge request pages
+ - Compress database dumps to reduce backup size
v 7.12.0
- Fix Error 500 when one user attempts to access a personal, internal snippet (Stan Hu)
@@ -110,6 +137,7 @@ v 7.12.0
- Improve group removing logic
- Trigger create-hooks on backup restore task
- Add option to automatically link omniauth and LDAP identities
+ - Allow special character in users bio. I.e.: I <3 GitLab
v 7.11.4
- Fix missing bullets when creating lists
@@ -128,9 +156,6 @@ 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)
diff --git a/Gemfile b/Gemfile
index f2cd20ada73..ba32feba3e7 100644
--- a/Gemfile
+++ b/Gemfile
@@ -46,7 +46,7 @@ gem "gitlab_git", '~> 7.2.5'
gem 'gitlab-grack', '~> 2.0.2', require: 'grack'
# LDAP Auth
-# GitLab fork with several improvements to original library. For full list of changes
+# 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"
@@ -54,9 +54,9 @@ gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
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
+# 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"
@@ -203,7 +203,7 @@ gem 'jquery-ui-rails'
gem 'nprogress-rails'
gem 'raphael-rails', '~> 2.1.2'
gem 'request_store'
-gem 'select2-rails'
+gem 'select2-rails', '~> 3.5.9'
gem 'virtus'
group :development do
@@ -227,30 +227,24 @@ end
group :development, :test do
gem 'awesome_print'
- gem 'byebug'
+ gem 'byebug', platform: :mri
gem 'fuubar', '~> 2.0.0'
gem 'pry-rails'
- gem 'coveralls', require: false
+ gem 'coveralls', '~> 0.8.2', require: false
gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails'
- gem 'rspec-rails', '~> 3.3.0'
- gem 'rubocop', '0.28.0', require: false
+ gem 'rspec-rails', '~> 3.3.0'
+ gem 'rubocop', '0.28.0', require: false
gem 'spinach-rails'
- # rest-client is a coveralls dependency and not used directly in GitLab, but
- # we specify a version here to pick up some security fixes.
- # See https://github.com/rest-client/rest-client/issues/369
- # and http://www.osvdb.org/show/osvdb/117461
- gem 'rest-client', '~> 1.8.0'
-
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.3.0'
# Generate Fake data
gem 'ffaker', '~> 2.0.0'
- gem 'capybara', '~> 2.3.0'
+ gem 'capybara', '~> 2.4.0'
gem 'capybara-screenshot', '~> 1.0.0'
gem 'poltergeist', '~> 1.6.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 7641d908131..6e571072a4c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -82,7 +82,7 @@ GEM
columnize (~> 0.8)
debugger-linecache (~> 1.2)
cal-heatmap-rails (0.0.1)
- capybara (2.3.0)
+ capybara (2.4.4)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
@@ -598,7 +598,7 @@ GEM
seed-fu (2.3.5)
activerecord (>= 3.1, < 4.3)
activesupport (>= 3.1, < 4.3)
- select2-rails (3.5.2)
+ select2-rails (3.5.9.3)
thor (~> 0.14)
settingslogic (2.0.9)
sexp_processor (4.4.5)
@@ -703,7 +703,7 @@ GEM
underscore-rails (1.4.4)
unf (0.1.4)
unf_ext
- unf_ext (0.0.6)
+ unf_ext (0.0.7.1)
unicorn (4.6.3)
kgio (~> 2.6)
rack
@@ -753,13 +753,13 @@ DEPENDENCIES
browser (~> 0.8.0)
byebug
cal-heatmap-rails (~> 0.0.1)
- capybara (~> 2.3.0)
+ capybara (~> 2.4.0)
capybara-screenshot (~> 1.0.0)
carrierwave
charlock_holmes
coffee-rails
colored
- coveralls
+ coveralls (~> 0.8.2)
creole (~> 0.3.6)
d3_rails (~> 3.5.5)
database_cleaner (~> 1.4.0)
@@ -833,7 +833,6 @@ DEPENDENCIES
redis-rails
request_store
rerun (~> 0.10.0)
- rest-client (~> 1.8.0)
rqrcode-rails3
rspec-rails (~> 3.3.0)
rubocop (= 0.28.0)
@@ -842,7 +841,7 @@ DEPENDENCIES
sass-rails (~> 4.0.5)
sdoc
seed-fu
- select2-rails
+ select2-rails (~> 3.5.9)
settingslogic
shoulda-matchers (~> 2.8.0)
sidekiq (~> 3.3)
@@ -878,4 +877,4 @@ DEPENDENCIES
wikicloth (= 0.8.1)
BUNDLED WITH
- 1.10.4
+ 1.10.5
diff --git a/README.md b/README.md
index 37badd448c1..bd663b30f3e 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,14 @@
+# GitLab
+
+[![build status](https://ci.gitlab.com/projects/1/status.png?ref=master)](https://ci.gitlab.com/projects/1?ref=master)
+[![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq)
+[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
+[![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
+
## Canonical source
The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible.
-# ![logo](https://about.gitlab.com/images/gitlab_logo.png) GitLab
-
## Open source software to collaborate on code
To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
@@ -17,21 +22,12 @@ To see how GitLab looks please see the [features page on our website](https://ab
## Editions
-There are two editions of GitLab.
-*GitLab [Community Edition](https://about.gitlab.com/features/) (CE)* is available without any costs under an MIT license.
-
-*GitLab Enterprise Edition (EE)* includes [extra features](https://about.gitlab.com/features/#compare) that are most useful for organizations with more than 100 users.
-To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/).
-
-## Code status
-
-- [![build status](https://ci.gitlab.org/projects/1/status.png?ref=master)](https://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch)
+There are two editions of GitLab:
-- [![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq)
+- GitLab Community Edition (CE) is available freely under the MIT Expat license.
+- GitLab Enterprise Edition (EE) includes [extra features](https://about.gitlab.com/features/#compare) that are more useful for organizations with more than 100 users. To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/).
-- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
-
-- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
+Included with the GitLab Omnibus Packages is [GitLab CI](https://about.gitlab.com/gitlab-ci/) that can easily build, test and deploy code.
## Website
@@ -46,23 +42,39 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a
## Requirements
-GitLab requires the following software:
-
-- Ubuntu/Debian/CentOS/RHEL
-- Ruby (MRI) 2.0 or 2.1
-- Git 1.7.10+
-- Redis 2.0+
-- MySQL or PostgreSQL
-
Please see the [requirements documentation](doc/install/requirements.md) for system requirements and more information about the supported operating systems.
## Installation
-The recommended way to install GitLab is using the provided [Omnibus packages](https://about.gitlab.com/downloads/). Compared to an installation from source, this is faster and less error prone. Just select your operating system, download the respective package (Debian or RPM) and install it using the system's package manager.
+The recommended way to install GitLab is with the [Omnibus packages](https://about.gitlab.com/downloads/) on our package server.
+Compared to an installation from source, this is faster and less error prone.
+Just select your operating system, download the respective package (Debian or RPM) and install it using the system's package manager.
There are various other options to install GitLab, please refer to the [installation page on the GitLab website](https://about.gitlab.com/installation/) for more information.
-You can access a new installation with the login **`root`** and password **`password`**, after login you are required to set a unique password.
+You can access a new installation with the login **`root`** and password **`5iveL!fe`**, after login you are required to set a unique password.
+
+## Install a development environment
+
+To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
+If you do not use the GitLab Development Kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone.
+One small thing you also have to do when installing it yourself is to copy the example development unicorn configuration file:
+
+ cp config/unicorn.rb.example.development config/unicorn.rb
+
+Instructions on how to start GitLab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development).
+
+## Software stack
+
+GitLab is a Ruby on Rails application that runs on the following software:
+
+- Ubuntu/Debian/CentOS/RHEL
+- Ruby (MRI) 2.0 or 2.1
+- Git 1.7.10+
+- Redis 2.0+
+- MySQL or PostgreSQL
+
+For more information please see the [architecture documentation](http://doc.gitlab.com/ce/development/architecture.html).
## Third-party applications
@@ -74,17 +86,7 @@ Since 2011 a minor or major version of GitLab is released on the 22nd of every m
## Upgrading
-For updating the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For installations from source there is an [upgrader script](doc/update/upgrader.md) and there are [upgrade guides](doc/update) detailing all necessary commands to migrate to the next version.
-
-## Install a development environment
-
-To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
-If you do not use the GitLab Development Kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone.
-One small thing you also have to do when installing it yourself is to copy the example development unicorn configuration file:
-
- cp config/unicorn.rb.example.development config/unicorn.rb
-
-Instructions on how to start GitLab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development).
+For upgrading information please see our [update page](https://about.gitlab.com/update/).
## Documentation
@@ -101,4 +103,4 @@ Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on
## Is it awesome?
Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua.
-[These people](https://twitter.com/gitlab/favorites) seem to like it.
+[These people](https://twitter.com/gitlab/favorites) seem to like it. \ No newline at end of file
diff --git a/app/assets/images/msapplication-tile.png b/app/assets/images/msapplication-tile.png
new file mode 100644
index 00000000000..f8c5c8b28b4
--- /dev/null
+++ b/app/assets/images/msapplication-tile.png
Binary files differ
diff --git a/app/assets/images/touch-icon-ipad-retina.png b/app/assets/images/touch-icon-ipad-retina.png
new file mode 100644
index 00000000000..feb32b48ec9
--- /dev/null
+++ b/app/assets/images/touch-icon-ipad-retina.png
Binary files differ
diff --git a/app/assets/images/touch-icon-ipad.png b/app/assets/images/touch-icon-ipad.png
new file mode 100644
index 00000000000..a6ddc543509
--- /dev/null
+++ b/app/assets/images/touch-icon-ipad.png
Binary files differ
diff --git a/app/assets/images/touch-icon-iphone-retina.png b/app/assets/images/touch-icon-iphone-retina.png
new file mode 100644
index 00000000000..8bf7ccb7534
--- /dev/null
+++ b/app/assets/images/touch-icon-iphone-retina.png
Binary files differ
diff --git a/app/assets/images/touch-icon-iphone.png b/app/assets/images/touch-icon-iphone.png
new file mode 100644
index 00000000000..87da550f8be
--- /dev/null
+++ b/app/assets/images/touch-icon-iphone.png
Binary files differ
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index bb120424ccf..8b041c490d8 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -40,6 +40,7 @@
#= require shortcuts_issuable
#= require shortcuts_network
#= require cal-heatmap
+#= require jquery.nicescroll.min
#= require_tree .
window.slugify = (text) ->
@@ -104,6 +105,8 @@ if location.hash
window.addEventListener "hashchange", shiftWindow
$ ->
+ $(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF")
+
# Click a .js-select-on-focus field, select the contents
$(".js-select-on-focus").on "focusin", ->
# Prevent a mouseup event from deselecting the input
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index a8ec0abc264..81e73799271 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -62,8 +62,9 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
when 'projects:commits:show'
shortcut_handler = new ShortcutsNavigation()
+ when 'projects:activity'
+ shortcut_handler = new ShortcutsNavigation()
when 'projects:show'
- new Activities()
shortcut_handler = new ShortcutsNavigation()
when 'groups:show'
new Activities()
@@ -127,7 +128,10 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
new ZenMode()
new DropzoneInput($('.wiki-form'))
- when 'snippets', 'labels', 'graphs'
+ when 'snippets'
+ shortcut_handler = new ShortcutsNavigation()
+ new ZenMode() if path[2] == 'show'
+ when 'labels', 'graphs'
shortcut_handler = new ShortcutsNavigation()
when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation()
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index a7476146010..a4f511301c1 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -25,10 +25,10 @@ class @DropzoneInput
form_dropzone = $(form).find('.div-dropzone')
form_dropzone.parent().addClass "div-dropzone-wrapper"
form_dropzone.append divHover
- $(".div-dropzone-hover").append iconPaperclip
+ form_dropzone.find(".div-dropzone-hover").append iconPaperclip
form_dropzone.append divSpinner
- $(".div-dropzone-spinner").append iconSpinner
- $(".div-dropzone-spinner").css
+ form_dropzone.find(".div-dropzone-spinner").append iconSpinner
+ form_dropzone.find(".div-dropzone-spinner").css
"opacity": 0
"display": "none"
diff --git a/app/assets/javascripts/line_highlighter.js.coffee b/app/assets/javascripts/line_highlighter.js.coffee
index a8b3c1fa33e..e604e6025c2 100644
--- a/app/assets/javascripts/line_highlighter.js.coffee
+++ b/app/assets/javascripts/line_highlighter.js.coffee
@@ -70,7 +70,7 @@ class @LineHighlighter
@clearHighlight()
- lineNumber = $(event.target).data('line-number')
+ lineNumber = $(event.target).closest('a').data('line-number')
current = @hashToRange(@_hash)
unless current[0] && event.shiftKey
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
index 7462975bd3d..b21cb7904b5 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -15,9 +15,7 @@ class @MergeRequest
this.$('.show-all-commits').on 'click', =>
this.showAllCommits()
- # `MergeRequests#new` has no tab-persisting or lazy-loading behavior
- unless @opts.action == 'new'
- new MergeRequestTabs(@opts)
+ @initTabs()
# Prevent duplicate event bindings
@disableTaskList()
@@ -29,6 +27,14 @@ class @MergeRequest
$: (selector) ->
this.$el.find(selector)
+ initTabs: ->
+ if @opts.action != 'new'
+ # `MergeRequests#new` has no tab-persisting or lazy-loading behavior
+ new MergeRequestTabs(@opts)
+ else
+ # Show the first tab (Commits)
+ $('.merge-request-tabs a[data-toggle="tab"]:first').tab('show')
+
showAllCommits: ->
this.$('.first-commits').remove()
this.$('.all-commits').removeClass 'hide'
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 1c05a2b9fe8..bcff7bcc49e 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -298,7 +298,7 @@ class @Notes
note.find(".note-header").hide()
base_form = note.find(".note-edit-form")
form = base_form.clone().insertAfter(base_form)
- form.addClass('current-note-edit-form')
+ form.addClass('current-note-edit-form gfm-form')
form.find('.div-dropzone').remove()
# Show the attachment delete link
diff --git a/app/assets/javascripts/pager.js.coffee b/app/assets/javascripts/pager.js.coffee
index fe83dc0410e..d639303aed3 100644
--- a/app/assets/javascripts/pager.js.coffee
+++ b/app/assets/javascripts/pager.js.coffee
@@ -12,7 +12,7 @@
@loading.show()
$.ajax
type: "GET"
- url: location.href
+ url: $(".content_list").data('href') || location.href
data: "limit=" + @limit + "&offset=" + @offset
complete: =>
@loading.hide()
diff --git a/app/assets/javascripts/project_show.js.coffee b/app/assets/javascripts/project_show.js.coffee
index 6828ae471e5..1fdf28f2528 100644
--- a/app/assets/javascripts/project_show.js.coffee
+++ b/app/assets/javascripts/project_show.js.coffee
@@ -1,15 +1,3 @@
class @ProjectShow
constructor: ->
- $('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) ->
- $(@).toggleClass('on').find('.count').html(data.star_count)
- .on 'ajax:error', (e, xhr, status, error) ->
- new Flash('Star toggle failed. Try again later.', 'alert')
-
- $("a[data-toggle='tab']").on "shown.bs.tab", (e) ->
- $.cookie "default_view", $(e.target).attr("href"), { expires: 30, path: '/' }
-
- defaultView = $.cookie("default_view")
- if defaultView
- $("a[href=" + defaultView + "]").tab "show"
- else
- $("a[data-toggle='tab']:first").tab "show"
+ # I kept class for future
diff --git a/app/assets/javascripts/shortcuts_navigation.coffee b/app/assets/javascripts/shortcuts_navigation.coffee
index 31895fbf2bc..5b6f9e7e3f2 100644
--- a/app/assets/javascripts/shortcuts_navigation.coffee
+++ b/app/assets/javascripts/shortcuts_navigation.coffee
@@ -4,6 +4,7 @@ class @ShortcutsNavigation extends Shortcuts
constructor: ->
super()
Mousetrap.bind('g p', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project'))
+ Mousetrap.bind('g e', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity'))
Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'))
Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits'))
Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network'))
diff --git a/app/assets/stylesheets/base/mixins.scss b/app/assets/stylesheets/base/mixins.scss
index 08cbe911672..d64e79170b9 100644
--- a/app/assets/stylesheets/base/mixins.scss
+++ b/app/assets/stylesheets/base/mixins.scss
@@ -109,7 +109,7 @@
font-size: 1.2em;
}
- blockquote p {
+ blockquote {
color: #888;
font-size: 15px;
line-height: 1.5;
diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss
index a49775daf8b..24c828e0d97 100644
--- a/app/assets/stylesheets/generic/mobile.scss
+++ b/app/assets/stylesheets/generic/mobile.scss
@@ -44,20 +44,18 @@
.project-home-panel {
padding-left: 0 !important;
- .project-home-row {
- .project-home-desc {
- margin-right: 0 !important;
- float: none !important;
- }
-
- .project-repo-buttons {
- position: static;
- margin-top: 15px;
- width: 100%;
- float: none;
- text-align: left;
- }
+ .project-avatar {
+ display: block;
}
+
+ .project-repo-buttons,
+ .git-clone-holder {
+ display: none;
+ }
+ }
+
+ .project-stats {
+ display: none;
}
.container .title {
diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss
index 00e0534b81e..b96664d30db 100644
--- a/app/assets/stylesheets/generic/sidebar.scss
+++ b/app/assets/stylesheets/generic/sidebar.scss
@@ -2,6 +2,9 @@
.sidebar-wrapper {
position: fixed;
top: 0;
+ bottom: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
left: 0;
height: 100%;
transition-duration: .3s;
@@ -21,8 +24,9 @@
}
.nav-sidebar {
+ margin-top: 29 + $header-height;
+ margin-bottom: 50px;
transition-duration: .3s;
- margin: 0;
list-style: none;
overflow: hidden;
@@ -39,12 +43,12 @@
}
a {
+ padding: 8px 15px;
+ font-size: 13px;
+ line-height: 18px;
color: $gray;
display: block;
text-decoration: none;
- padding: 8px 15px;
- font-size: 14px;
- line-height: 20px;
padding-left: 16px;
&:hover {
@@ -88,14 +92,17 @@
width: $sidebar_width;
.nav-sidebar {
- margin-top: 29px;
- position: fixed;
- top: $header-height;
width: $sidebar_width;
}
.nav-sidebar li a{
width: 230px;
+
+ &.back-link {
+ i {
+ visibility: hidden;
+ }
+ }
}
}
}
@@ -108,15 +115,9 @@
width: $sidebar_collapsed_width;
.nav-sidebar {
- margin-top: 29px;
- position: fixed;
- top: $header-height;
width: $sidebar_collapsed_width;
li a {
- font-size: 14px;
- padding: 8px 15px;
- text-align: left;
padding-left: 16px;
}
}
@@ -175,7 +176,7 @@
}
.sidebar-user {
- position: absolute;
+ position: fixed;
bottom: 0;
width: $sidebar_width;
padding: 10px;
diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss
index 66767cb13cb..2db4213159a 100644
--- a/app/assets/stylesheets/generic/typography.scss
+++ b/app/assets/stylesheets/generic/typography.scss
@@ -17,6 +17,14 @@ pre {
background: #333;
color: $background-color;
}
+
+ &.plain-readme {
+ background: none;
+ border: none;
+ padding: 0;
+ margin: 0;
+ font-size: 14px;
+ }
}
.monospace {
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 42b8ecabb38..4da65b28743 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -72,13 +72,28 @@ ul.notes {
.note {
display: block;
position:relative;
+
.note-body {
overflow: auto;
+
.note-text {
overflow: auto;
word-wrap: break-word;
@include md-typography;
+ // Reset ul style types since we're nested inside a ul already
+ & > ul {
+ list-style-type: disc;
+
+ ul {
+ list-style-type: circle;
+
+ ul {
+ list-style-type: square;
+ }
+ }
+ }
+
// Reduce left padding of first task list ul element
ul.task-list:first-child {
padding-left: 10px;
@@ -94,6 +109,7 @@ ul.notes {
}
}
}
+
.note-header {
padding-bottom: 3px;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index e19b2eafa43..5f415f2d78f 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -15,48 +15,31 @@
}
.project-home-panel {
- margin-top: 10px;
- margin-bottom: 15px;
- position: relative;
- padding-left: 65px;
- min-height: 50px;
+ text-align: center;
+ margin-bottom: 20px;
.project-identicon-holder {
- position: absolute;
- left: 0;
- top: -14px;
+ margin-bottom: 15px;
- .avatar {
- width: 50px;
- height: 50px;
+ .avatar, .identicon {
+ margin: 0 auto;
+ float: none;
}
.identicon {
- font-size: 26px;
- line-height: 50px;
+ @include border-radius(50%);
}
}
- .project-home-row {
- @extend .clearfix;
- margin-bottom: 15px;
-
- &.project-home-row-top {
- margin-bottom: 15px;
+ .lead {
+ p {
+ display: inline;
}
+ }
- .project-home-desc {
- color: $gray;
- float: left;
- font-size: 16px;
- line-height: 1.3;
- margin-right: 250px;
-
- // Render Markdown-generated HTML inline for this block
- p {
- display: inline;
- }
- }
+ .git-clone-holder {
+ max-width: 600px;
+ margin: 0 auto;
}
.visibility-level-label {
@@ -67,22 +50,22 @@
}
.project-repo-buttons {
- margin-top: -3px;
- position: absolute;
- right: 0;
- width: 265px;
- text-align: right;
+ margin-top: 25px;
+ margin-bottom: 25px;
.btn {
+ @extend .btn-info;
+
+ margin-left: 10px;
font-weight: bold;
font-size: 14px;
line-height: 16px;
+ padding: 8px 12px;
.count {
- padding-left: 10px;
- border-left: 1px solid #ccc;
+ padding-left: 7px;
display: inline-block;
- margin-left: 10px;
+ margin-left: 7px;
}
}
}
@@ -307,3 +290,15 @@ table.table.protected-branches-list tr.no-border {
float: left;
margin-right: 10px;
}
+
+.project-stats {
+ text-align: center;
+
+ ul.nav-pills { display:inline-block; }
+ li { display:inline; }
+ a { float:left; }
+}
+
+pre.light-well {
+ border-color: #f1f1f1;
+}
diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss
index dc47b108100..3589cb88d03 100644
--- a/app/assets/stylesheets/themes/gitlab-theme.scss
+++ b/app/assets/stylesheets/themes/gitlab-theme.scss
@@ -35,9 +35,9 @@
.sidebar-wrapper {
background: $color-darker;
- border-right: 1px solid $color-darker;
.sidebar-user {
+ background: $color-darker;
color: $color-light;
&:hover {
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index f616ccf5684..da5f5bb83fa 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -23,7 +23,8 @@ class Admin::ProjectsController < Admin::ApplicationController
end
def transfer
- ::Projects::TransferService.new(@project, current_user, params.dup).execute
+ namespace = Namespace.find_by(id: params[:new_namespace_id])
+ ::Projects::TransferService.new(@project, current_user, params.dup).execute(namespace)
@project.reload
redirect_to admin_namespace_project_path(@project.namespace, @project)
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 7a683098df3..770fe00af51 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -55,6 +55,12 @@ class Admin::UsersController < Admin::ApplicationController
end
end
+ def disable_two_factor
+ user.disable_two_factor!
+ redirect_to admin_user_path(user),
+ notice: 'Two-factor Authentication has been disabled for this user'
+ end
+
def create
opts = {
force_random_password: true,
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 63fc146f1d1..362b03e0d5e 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -183,7 +183,10 @@ class ApplicationController < ActionController::Base
headers['X-XSS-Protection'] = '1; mode=block'
headers['X-UA-Compatible'] = 'IE=edge'
headers['X-Content-Type-Options'] = 'nosniff'
- headers['Strict-Transport-Security'] = 'max-age=31536000' if Gitlab.config.gitlab.https
+ # Enabling HSTS for non-standard ports would send clients to the wrong port
+ if Gitlab.config.gitlab.https and Gitlab.config.gitlab.port == 443
+ headers['Strict-Transport-Security'] = 'max-age=31536000'
+ end
end
def add_gon_variables
@@ -265,6 +268,7 @@ class ApplicationController < ActionController::Base
params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank?
+ @sort = params[:sort]
@filter_params = params.dup
if @project
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 11af9895261..52e9c58b47c 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -1,22 +1,35 @@
class AutocompleteController < ApplicationController
+ skip_before_action :authenticate_user!, only: [:users]
+
def users
- @users =
- if params[:project_id].present?
- project = Project.find(params[:project_id])
+ begin
+ @users =
+ if params[:project_id].present?
+ project = Project.find(params[:project_id])
- if can?(current_user, :read_project, project)
- project.team.users
- end
- elsif params[:group_id]
- group = Group.find(params[:group_id])
+ if can?(current_user, :read_project, project)
+ project.team.users
+ end
+ elsif params[:group_id]
+ group = Group.find(params[:group_id])
- if can?(current_user, :read_group, group)
- group.users
+ if can?(current_user, :read_group, group)
+ group.users
+ end
+ elsif current_user
+ User.all
end
- else
- User.all
+ rescue ActiveRecord::RecordNotFound
+ if current_user
+ return render json: {}, status: 404
end
+ end
+
+ if @users.nil? && current_user.nil?
+ authenticate_user!
+ end
+ @users ||= User.none
@users = @users.search(params[:search]) if params[:search].present?
@users = @users.active
@users = @users.page(params[:page]).per(PER_PAGE)
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index 538b09ca54d..f83b4abd1e2 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -32,6 +32,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController
params.require(:user).permit(
:color_scheme_id,
:dashboard,
+ :project_view,
:theme_id
)
end
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index 03845f1e1ec..f9af0871cf1 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -29,13 +29,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
end
def destroy
- current_user.update_attributes({
- two_factor_enabled: false,
- encrypted_otp_secret: nil,
- encrypted_otp_secret_iv: nil,
- encrypted_otp_secret_salt: nil,
- otp_backup_codes: nil
- })
+ current_user.disable_two_factor!
redirect_to profile_account_path
end
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 696011b94b9..117ae3aaa3d 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -32,7 +32,7 @@ class Projects::BranchesController < Projects::ApplicationController
end
def destroy
- DeleteBranchService.new(project, current_user).execute(params[:id])
+ status = DeleteBranchService.new(project, current_user).execute(params[:id])
@branch_name = params[:id]
respond_to do |format|
@@ -40,7 +40,7 @@ class Projects::BranchesController < Projects::ApplicationController
redirect_to namespace_project_branches_path(@project.namespace,
@project)
end
- format.js
+ format.js { render status: status[:return_code] }
end
end
end
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index a060ea6f998..0b6f7f5c91e 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -1,6 +1,9 @@
class Projects::GraphsController < Projects::ApplicationController
+ include ExtractsPath
+
# Authorize
before_action :require_non_empty_project
+ before_action :assign_ref_vars
before_action :authorize_download_code!
def show
@@ -13,7 +16,7 @@ class Projects::GraphsController < Projects::ApplicationController
end
def commits
- @commits = @project.repository.commits(nil, nil, 2000, 0, true)
+ @commits = @project.repository.commits(@ref, nil, 2000, 0, true)
@commits_graph = Gitlab::Graphs::Commits.new(@commits)
@commits_per_week_days = @commits_graph.commits_per_week_days
@commits_per_time = @commits_graph.commits_per_time
@@ -23,7 +26,7 @@ class Projects::GraphsController < Projects::ApplicationController
private
def fetch_graph
- @commits = @project.repository.commits(nil, nil, 6000, 0, true)
+ @commits = @project.repository.commits(@ref, nil, 6000, 0, true)
@log = []
@commits.each do |commit|
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 61689488d13..9efe9704d1e 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -64,7 +64,12 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def destroy
- return access_denied! unless can?(current_user, :admin_milestone, @milestone)
+ return access_denied! unless can?(current_user, :admin_milestone, @project)
+
+ update_params = { milestone: nil }
+ @milestone.issues.each do |issue|
+ Issues::UpdateService.new(@project, current_user, update_params).execute(issue)
+ end
@milestone.destroy
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 01ca1537c0e..d83561cf32a 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -8,17 +8,21 @@ class Projects::RefsController < Projects::ApplicationController
def switch
respond_to do |format|
format.html do
- new_path = if params[:destination] == "tree"
- namespace_project_tree_path(@project.namespace, @project,
- (@id))
- elsif params[:destination] == "blob"
- namespace_project_blob_path(@project.namespace, @project,
- (@id))
- elsif params[:destination] == "graph"
- namespace_project_network_path(@project.namespace, @project, @id, @options)
- else
- namespace_project_commits_path(@project.namespace, @project, @id)
- end
+ new_path =
+ case params[:destination]
+ when "tree"
+ namespace_project_tree_path(@project.namespace, @project, @id)
+ when "blob"
+ namespace_project_blob_path(@project.namespace, @project, @id)
+ when "graph"
+ namespace_project_network_path(@project.namespace, @project, @id, @options)
+ when "graphs"
+ namespace_project_graph_path(@project.namespace, @project, @id)
+ when "graphs_commits"
+ commits_namespace_project_graph_path(@project.namespace, @project, @id)
+ else
+ namespace_project_commits_path(@project.namespace, @project, @id)
+ end
redirect_to new_path
end
diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb
index b659e15f242..92e4bc16d9d 100644
--- a/app/controllers/projects/tree_controller.rb
+++ b/app/controllers/projects/tree_controller.rb
@@ -7,13 +7,15 @@ class Projects::TreeController < Projects::ApplicationController
before_action :authorize_download_code!
def show
+ return not_found! unless @repository.commit(@ref)
+
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
redirect_to(
namespace_project_blob_path(@project.namespace, @project,
File.join(@ref, @path))
) and return
- else
+ elsif @path.present?
return not_found!
end
end
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index be5968cd7b0..586359f3080 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -1,12 +1,12 @@
class ProjectsController < ApplicationController
prepend_before_filter :render_go_import, only: [:show]
- skip_before_action :authenticate_user!, only: [:show]
+ skip_before_action :authenticate_user!, only: [:show, :activity]
before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create]
# Authorize
before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
- before_action :event_filter, only: :show
+ before_action :event_filter, only: [:show, :activity]
layout :determine_layout
@@ -52,10 +52,21 @@ class ProjectsController < ApplicationController
end
def transfer
- transfer_params = params.permit(:new_namespace_id)
- ::Projects::TransferService.new(project, current_user, transfer_params).execute
- if @project.errors[:namespace_id].present?
- flash[:alert] = @project.errors[:namespace_id].first
+ namespace = Namespace.find_by(id: params[:new_namespace_id])
+ ::Projects::TransferService.new(project, current_user).execute(namespace)
+
+ if @project.errors[:new_namespace].present?
+ flash[:alert] = @project.errors[:new_namespace].first
+ end
+ end
+
+ def activity
+ respond_to do |format|
+ format.html
+ format.json do
+ load_events
+ pager_json('events/_events', @events.count)
+ end
end
end
@@ -65,15 +76,12 @@ class ProjectsController < ApplicationController
return
end
- @show_star = !(current_user && current_user.starred?(@project))
-
respond_to do |format|
format.html do
if @project.repository_exists?
if @project.empty_repo?
render 'projects/empty'
else
- @last_push = current_user.recent_push(@project.id) if current_user
render :show
end
else
@@ -81,11 +89,6 @@ class ProjectsController < ApplicationController
end
end
- format.json do
- load_events
- pager_json('events/_events', @events.count)
- end
-
format.atom do
load_events
render layout: false
@@ -147,11 +150,14 @@ class ProjectsController < ApplicationController
def toggle_star
current_user.toggle_star(@project)
@project.reload
- render json: { star_count: @project.star_count }
+
+ render json: {
+ html: view_to_html_string("projects/buttons/_star")
+ }
end
def markdown_preview
- text = params[:text]
+ text = params[:text]
ext = Gitlab::ReferenceExtractor.new(@project, current_user)
ext.analyze(text)
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 2eccc0ee31f..ab89aa2c53a 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -10,7 +10,7 @@
# state: 'open' or 'closed' or 'all'
# group_id: integer
# project_id: integer
-# milestone_id: integer
+# milestone_title: string
# assignee_id: integer
# search: string
# label_name: string
@@ -76,7 +76,7 @@ class IssuableFinder
return @milestones if defined?(@milestones)
@milestones =
- if milestones? && params[:milestone_title] != NONE
+ if milestones? && params[:milestone_title] != Milestone::None.title
Milestone.where(title: params[:milestone_title])
else
nil
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 0b46db4b1c3..a803b66c502 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -213,6 +213,10 @@ module ApplicationHelper
Haml::Helpers.preserve(markdown(file_content))
elsif asciidoc?(file_name)
asciidoc(file_content)
+ elsif plain?(file_name)
+ content_tag :pre, class: 'plain-readme' do
+ file_content
+ end
else
GitHub::Markup.render(file_name, file_content).
force_encoding(file_content.encoding).html_safe
@@ -221,6 +225,10 @@ module ApplicationHelper
simple_format(file_content)
end
+ def plain?(filename)
+ Gitlab::MarkupHelper.plain?(filename)
+ end
+
def markup?(filename)
Gitlab::MarkupHelper.markup?(filename)
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 63c3ff5674d..61d14383945 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -28,7 +28,7 @@ module ApplicationSettingsHelper
def restricted_level_checkboxes(help_block_id)
Gitlab::VisibilityLevel.options.map do |name, level|
checked = restricted_visibility_levels(true).include?(level)
- css_class = 'btn btn-primary'
+ css_class = 'btn'
css_class += ' active' if checked
checkbox_name = 'application_setting[restricted_visibility_levels][]'
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index a801d3b10aa..eb3f72a307d 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -118,7 +118,7 @@ module GitlabMarkdownHelper
# Returns a random markdown tip for use as a textarea placeholder
def random_markdown_tip
- "Tip: #{MARKDOWN_TIPS.sample}"
+ MARKDOWN_TIPS.sample
end
private
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 9d072f81092..d0fae255a04 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -17,6 +17,10 @@ module GitlabRoutingHelper
namespace_project_path(project.namespace, project, *args)
end
+ def activity_project_path(project, *args)
+ activity_namespace_project_path(project.namespace, project, *args)
+ end
+
def edit_project_path(project, *args)
edit_namespace_project_path(project.namespace, project, *args)
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 93e33ebefd8..132a893e532 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -29,6 +29,8 @@ module MilestonesHelper
end.active
grouped_milestones = Milestones::GroupService.new(milestones).execute
+ grouped_milestones.unshift(Milestone::None)
+
options_from_collection_for_select(grouped_milestones, 'title', 'title', params[:milestone_title])
end
end
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index bceff4fd52e..ea774e28ecf 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -42,6 +42,13 @@ module PreferencesHelper
end
end
+ def project_view_choices
+ [
+ ['Readme (default)', :readme],
+ ['Activity view', :activity]
+ ]
+ end
+
def user_application_theme
theme = Gitlab::Themes.by_id(current_user.try(:theme_id))
theme.css_class
@@ -50,4 +57,9 @@ module PreferencesHelper
def user_color_scheme_class
COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
end
+
+ def prefer_readme?
+ !current_user ||
+ current_user.project_view == 'readme'
+ end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index ec65e473919..3cd52b381bd 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -84,53 +84,6 @@ module ProjectsHelper
@project.milestones.active.order("due_date, title ASC")
end
- def link_to_toggle_star(title, starred)
- cls = 'star-btn btn btn-sm btn-default'
-
- toggle_text =
- if starred
- ' Unstar'
- else
- ' Star'
- end
-
- toggle_html = content_tag('span', class: 'toggle') do
- icon('star') + toggle_text
- end
-
- count_html = content_tag('span', class: 'count') do
- @project.star_count.to_s
- end
-
- link_opts = {
- title: title,
- class: cls,
- method: :post,
- remote: true,
- data: { type: 'json' }
- }
-
- path = toggle_star_namespace_project_path(@project.namespace, @project)
-
- content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do
- link_to(path, link_opts) do
- toggle_html + ' ' + count_html
- end
- end
- end
-
- def link_to_toggle_fork
- html = content_tag('span') do
- icon('code-fork') + ' Fork'
- end
-
- count_html = content_tag(:span, class: 'count') do
- @project.forks_count.to_s
- end
-
- html + count_html
- end
-
def project_for_deploy_key(deploy_key)
if deploy_key.projects.include?(@project)
@project
@@ -139,6 +92,16 @@ module ProjectsHelper
end
end
+ def can_change_visibility_level?(project, current_user)
+ return false unless can?(current_user, :change_visibility_level, project)
+
+ if project.forked?
+ project.forked_from_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE
+ else
+ true
+ end
+ end
+
private
def get_project_nav_tabs(project, current_user)
@@ -168,8 +131,12 @@ module ProjectsHelper
nav_tabs << :snippets
end
+ if can?(current_user, :read_label, project)
+ nav_tabs << :labels
+ end
+
if can?(current_user, :read_milestone, project)
- nav_tabs << [:milestones, :labels]
+ nav_tabs << :milestones
end
nav_tabs.flatten
@@ -285,16 +252,6 @@ module ProjectsHelper
end
end
- def service_field_value(type, value)
- return value unless type == 'password'
-
- if value.present?
- "***********"
- else
- nil
- end
- end
-
def user_max_access_in_project(user, project)
level = project.team.max_member_access(user)
@@ -306,4 +263,35 @@ module ProjectsHelper
def leave_project_message(project)
"Are you sure you want to leave \"#{project.name}\" project?"
end
+
+ def new_readme_path
+ ref = @repository.root_ref if @repository
+ ref ||= 'master'
+
+ namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md')
+ end
+
+ def last_push_event
+ if current_user
+ current_user.recent_push(@project.id)
+ end
+ end
+
+ def readme_cache_key
+ [@project.id, @project.commit.sha, "readme"].join('-')
+ end
+
+ def round_commit_count(project)
+ count = project.commit_count
+
+ if count > 10000
+ '10000+'
+ elsif count > 5000
+ '5000+'
+ elsif count > 1000
+ '1000+'
+ else
+ count
+ end
+ end
end
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 00d4c7f1051..b52cd23aba2 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -86,4 +86,10 @@ module VisibilityLevelHelper
def default_snippet_visibility
current_application_settings.default_snippet_visibility
end
+
+ def skip_level?(form_model, level)
+ form_model.is_a?(Project) &&
+ form_model.forked? &&
+ !Gitlab::VisibilityLevel.allowed_fork_levels(form_model.forked_from_project.visibility_level).include?(level)
+ end
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index d3631d49ec6..9258d981ac9 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -31,10 +31,11 @@ class Ability
end
if project && project.public?
- [
+ rules = [
:read_project,
:read_wiki,
:read_issue,
+ :read_label,
:read_milestone,
:read_project_snippet,
:read_project_member,
@@ -42,6 +43,8 @@ class Ability
:read_note,
:download_code
]
+
+ rules - project_disabled_features_rules(project)
else
group = if subject.kind_of?(Group)
subject
@@ -102,28 +105,7 @@ class Ability
rules -= project_archived_rules
end
- unless project.issues_enabled
- rules -= named_abilities('issue')
- end
-
- unless project.merge_requests_enabled
- rules -= named_abilities('merge_request')
- end
-
- unless project.issues_enabled or project.merge_requests_enabled
- rules -= named_abilities('label')
- rules -= named_abilities('milestone')
- end
-
- unless project.snippets_enabled
- rules -= named_abilities('project_snippet')
- end
-
- unless project.wiki_enabled
- rules -= named_abilities('wiki')
- end
-
- rules
+ rules - project_disabled_features_rules(project)
end
end
@@ -205,6 +187,33 @@ class Ability
]
end
+ def project_disabled_features_rules(project)
+ rules = []
+
+ unless project.issues_enabled
+ rules += named_abilities('issue')
+ end
+
+ unless project.merge_requests_enabled
+ rules += named_abilities('merge_request')
+ end
+
+ unless project.issues_enabled or project.merge_requests_enabled
+ rules += named_abilities('label')
+ rules += named_abilities('milestone')
+ end
+
+ unless project.snippets_enabled
+ rules += named_abilities('project_snippet')
+ end
+
+ unless project.wiki_enabled
+ rules += named_abilities('wiki')
+ end
+
+ rules
+ end
+
def group_abilities(user, group)
rules = []
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 56849f28ff0..5b0ae411642 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -79,22 +79,36 @@ module Mentionable
end
end
- # If the mentionable_text field is about to change, locate any *added* references and create cross references for
- # them. Invoke from an observer's #before_save implementation.
- def notice_added_references(p = project, a = author)
- ch = changed_attributes
- original, mentionable_changed = "", false
- self.class.mentionable_attrs.each do |attr|
- if ch[attr]
- original << ch[attr]
- mentionable_changed = true
- end
- end
+ # When a mentionable field is changed, creates cross-reference notes that
+ # don't already exist
+ def create_new_cross_references!(p = project, a = author)
+ changes = detect_mentionable_changes
+
+ return if changes.empty?
- # Only proceed if the saved changes actually include a chance to an attr_mentionable field.
- return unless mentionable_changed
+ original_text = changes.collect { |_, vals| vals.first }.join(' ')
- preexisting = references(p, self.author, original)
+ preexisting = references(p, self.author, original_text)
create_cross_references!(p, a, preexisting)
end
+
+ private
+
+ # Returns a Hash of changed mentionable fields
+ #
+ # Preference is given to the `changes` Hash, but falls back to
+ # `previous_changes` if it's empty (i.e., the changes have already been
+ # persisted).
+ #
+ # See ActiveModel::Dirty.
+ #
+ # Returns a Hash.
+ def detect_mentionable_changes
+ source = (changes.present? ? changes : previous_changes).dup
+
+ mentionable = self.class.mentionable_attrs
+
+ # Only include changed fields that are mentionable
+ source.select { |key, val| mentionable.include?(key) }
+ end
end
diff --git a/app/models/key.rb b/app/models/key.rb
index bbc28678177..2dcae059c0e 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -24,6 +24,7 @@ class Key < ActiveRecord::Base
validates :title, presence: true, length: { within: 0..255 }
validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true
+ validates :key, format: { without: /\n|\r/, message: 'should be a single line' }
validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' }
delegate :name, :email, to: :user, prefix: true
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index e0c5fec97b7..d28f3c8d3f9 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -14,6 +14,10 @@
#
class Milestone < ActiveRecord::Base
+ # Represents a "No Milestone" state used for filtering Issues and Merge
+ # Requests that have no milestone assigned.
+ None = Struct.new(:title).new('No Milestone')
+
include InternalId
include Sortable
diff --git a/app/models/note.rb b/app/models/note.rb
index 68b9d433ae0..62567f471dc 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -356,7 +356,7 @@ class Note < ActiveRecord::Base
end
def set_references
- notice_added_references(project, author)
+ create_new_cross_references!(project, author)
end
def editable?
diff --git a/app/models/project.rb b/app/models/project.rb
index b161cbe86b9..ff372ea9aa5 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -683,6 +683,10 @@ class Project < ActiveRecord::Base
update_attribute(:repository_size, repository.size)
end
+ def update_commit_count
+ update_attribute(:commit_count, repository.commit_count)
+ end
+
def forks_count
ForkedProjectLink.where(forked_from_project_id: self.id).count
end
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index c284e19fe50..5aaa4e85cbc 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -22,8 +22,12 @@ class GitlabCiService < CiService
API_PREFIX = "api/v1"
prop_accessor :project_url, :token
- validates :project_url, presence: true, if: :activated?
- validates :token, presence: true, if: :activated?
+ validates :project_url,
+ presence: true,
+ format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
+ validates :token,
+ presence: true,
+ format: { with: /\A([A-Za-z0-9]+)\z/ }, if: :activated?
after_save :compose_service_hook, if: :activated?
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index a4d0914dbe7..d24aa317cf3 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -63,7 +63,7 @@ class IrkerService < Service
help: 'Irker daemon hostname (defaults to localhost)' },
{ type: 'text', name: 'server_port', placeholder: 6659,
help: 'Irker daemon port (defaults to 6659)' },
- { type: 'text', name: 'default_irc_uri',
+ { type: 'text', name: 'default_irc_uri', title: 'Default IRC URI',
help: 'A default IRC URI to prepend before each recipient (optional)',
placeholder: 'irc://irc.network.net:6697/' },
{ type: 'textarea', name: 'recipients',
diff --git a/app/models/repository.rb b/app/models/repository.rb
index c767d1051d1..807b33b2a3e 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -94,18 +94,6 @@ class Repository
gitlab_shell.rm_tag(path_with_namespace, tag_name)
end
- def round_commit_count
- if commit_count > 10000
- '10000+'
- elsif commit_count > 5000
- '5000+'
- elsif commit_count > 1000
- '1000+'
- else
- commit_count
- end
- end
-
def branch_names
cache.fetch(:branch_names) { raw_repository.branch_names }
end
@@ -130,28 +118,29 @@ class Repository
cache.fetch(:size) { raw_repository.size }
end
+ def cache_keys
+ %i(size branch_names tag_names commit_count
+ readme version contribution_guide changelog license)
+ end
+
+ def build_cache
+ cache_keys.each do |key|
+ unless cache.exist?(key)
+ send(key)
+ end
+ end
+ end
+
def expire_cache
- %i(size branch_names tag_names commit_count graph_log
- readme version contribution_guide changelog license).each do |key|
+ cache_keys.each do |key|
cache.expire(key)
end
end
- def graph_log
- cache.fetch(:graph_log) do
- commits = raw_repository.log(limit: 6000, skip_merges: true,
- ref: root_ref)
-
- commits.map do |rugged_commit|
- commit = Gitlab::Git::Commit.new(rugged_commit)
-
- {
- author_name: commit.author_name,
- author_email: commit.author_email,
- additions: commit.stats.additions,
- deletions: commit.stats.deletions,
- }
- end
+ def rebuild_cache
+ cache_keys.each do |key|
+ cache.expire(key)
+ send(key)
end
end
@@ -431,6 +420,39 @@ class Repository
end
end
+ def search_files(query, ref)
+ offset = 2
+ args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref})
+ Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
+ end
+
+ def parse_search_result(result)
+ ref = nil
+ filename = nil
+ startline = 0
+
+ result.each_line.each_with_index do |line, index|
+ if line =~ /^.*:.*:\d+:/
+ ref, filename, startline = line.split(':')
+ startline = startline.to_i - index
+ break
+ end
+ end
+
+ data = ""
+
+ result.each_line do |line|
+ data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
+ end
+
+ OpenStruct.new(
+ filename: filename,
+ ref: ref,
+ startline: startline,
+ data: data
+ )
+ end
+
private
def cache
diff --git a/app/models/user.rb b/app/models/user.rb
index dc84f5141d8..fb330ff7185 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -177,6 +177,10 @@ class User < ActiveRecord::Base
# Note: When adding an option, it MUST go on the end of the array.
enum dashboard: [:projects, :stars]
+ # User's Project preference
+ # Note: When adding an option, it MUST go on the end of the array.
+ enum project_view: [:readme, :activity]
+
alias_attribute :private_token, :authentication_token
delegate :path, to: :namespace, allow_nil: true, prefix: true
@@ -322,6 +326,16 @@ class User < ActiveRecord::Base
@reset_token
end
+ def disable_two_factor!
+ update_attributes(
+ two_factor_enabled: false,
+ encrypted_otp_secret: nil,
+ encrypted_otp_secret_iv: nil,
+ encrypted_otp_secret_salt: nil,
+ otp_backup_codes: nil
+ )
+ end
+
def namespace_uniq
namespace_name = self.username
existing_namespace = Namespace.by_path(namespace_name)
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 6135ae65007..3511392d1d8 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -21,7 +21,6 @@ class GitPushService
project.ensure_satellite_exists
project.repository.expire_cache
- project.update_repository_size
if push_remove_branch?(ref, newrev)
@push_commits = []
@@ -61,6 +60,7 @@ class GitPushService
EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup, :push_hooks)
+ ProjectCacheWorker.perform_async(project.id)
end
protected
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 075a6118da2..1cc42b0b0ad 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -2,15 +2,15 @@ class GitTagPushService
attr_accessor :project, :user, :push_data
def execute(project, user, oldrev, newrev, ref)
- @project, @user = project, user
+ project.repository.expire_cache
+ @project, @user = project, user
@push_data = build_push_data(oldrev, newrev, ref)
EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks)
-
- project.repository.expire_cache
+ ProjectCacheWorker.perform_async(project.id)
true
end
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 3220facaf7c..eabab65c9b0 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -1,17 +1,11 @@
module Issues
class UpdateService < Issues::BaseService
def execute(issue)
- state = params[:state_event]
-
- case state
+ case params.delete(:state_event)
when 'reopen'
Issues::ReopenService.new(project, current_user, {}).execute(issue)
when 'close'
Issues::CloseService.new(project, current_user, {}).execute(issue)
- when 'task_check'
- issue.update_nth_task(params[:task_num].to_i, true)
- when 'task_uncheck'
- issue.update_nth_task(params[:task_num].to_i, false)
end
params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
@@ -20,8 +14,7 @@ module Issues
filter_params
old_labels = issue.labels.to_a
- if params.present? && issue.update_attributes(params.except(:state_event,
- :task_num))
+ if params.present? && issue.update_attributes(params)
issue.reset_events_cache
if issue.labels != old_labels
@@ -42,7 +35,7 @@ module Issues
create_title_change_note(issue, issue.previous_changes['title'].first)
end
- issue.notice_added_references(issue.project, current_user)
+ issue.create_new_cross_references!(issue.project, current_user)
execute_hooks(issue, 'update')
end
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index f6570f52241..589fad16165 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -11,17 +11,11 @@ module MergeRequests
params.except!(:target_project_id)
params.except!(:source_branch)
- state = params[:state_event]
-
- case state
+ case params.delete(:state_event)
when 'reopen'
MergeRequests::ReopenService.new(project, current_user, {}).execute(merge_request)
when 'close'
MergeRequests::CloseService.new(project, current_user, {}).execute(merge_request)
- when 'task_check'
- merge_request.update_nth_task(params[:task_num].to_i, true)
- when 'task_uncheck'
- merge_request.update_nth_task(params[:task_num].to_i, false)
end
params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
@@ -30,9 +24,7 @@ module MergeRequests
filter_params
old_labels = merge_request.labels.to_a
- if params.present? && merge_request.update_attributes(
- params.except(:state_event, :task_num)
- )
+ if params.present? && merge_request.update_attributes(params)
merge_request.reset_events_cache
if merge_request.labels != old_labels
@@ -67,7 +59,7 @@ module MergeRequests
merge_request.mark_as_unchecked
end
- merge_request.notice_added_references(merge_request.project, current_user)
+ merge_request.create_new_cross_references!(merge_request.project, current_user)
execute_hooks(merge_request, 'update')
end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 489e03bd5ef..f43c0ef70e9 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -11,19 +11,16 @@ module Projects
include Gitlab::ShellAdapter
class TransferError < StandardError; end
- def execute
- namespace_id = params[:new_namespace_id]
- namespace = Namespace.find_by(id: namespace_id)
-
- if allowed_transfer?(current_user, project, namespace)
- transfer(project, namespace)
+ def execute(new_namespace)
+ if allowed_transfer?(current_user, project, new_namespace)
+ transfer(project, new_namespace)
else
- project.errors.add(:namespace, 'is invalid')
+ project.errors.add(:new_namespace, 'is invalid')
false
end
rescue Projects::TransferService::TransferError => ex
project.reload
- project.errors.add(:namespace_id, ex.message)
+ project.errors.add(:new_namespace, ex.message)
false
end
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index 8c6b8e851c4..33730ff05df 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -43,6 +43,7 @@
%strong{class: @user.two_factor_enabled? ? 'cgreen' : 'cred'}
- if @user.two_factor_enabled?
Enabled
+ = link_to 'Disable', disable_two_factor_admin_user_path(@user), data: {confirm: 'Are you sure?'}, method: :patch, class: 'btn btn-xs btn-remove pull-right', title: 'Disable Two-factor Authentication'
- else
Disabled
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index b2e5d11279b..b8409f64665 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -5,6 +5,7 @@
- if event.created_project?
= cache [event, current_user] do
+ = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:''
= render "events/event/created_project", event: event
- else
= cache event do
diff --git a/app/views/explore/projects/_project.html.haml b/app/views/explore/projects/_project.html.haml
index d65fb529373..d769c91545d 100644
--- a/app/views/explore/projects/_project.html.haml
+++ b/app/views/explore/projects/_project.html.haml
@@ -14,7 +14,7 @@
.repo-info
- unless project.empty_repo?
- = link_to pluralize(project.repository.round_commit_count, 'commit'), namespace_project_commits_path(project.namespace, project, project.default_branch)
+ = link_to pluralize(round_commit_count(project), 'commit'), namespace_project_commits_path(project.namespace, project, project.default_branch)
&middot;
= link_to pluralize(project.repository.branch_names.count, 'branch'), namespace_project_branches_path(project.namespace, project)
&middot;
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 825acb0ae3e..e809d99ba71 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -80,6 +80,12 @@
.key g
.key p
%td
+ Go to the project's home page
+ %tr
+ %td.shortcut
+ .key g
+ .key e
+ %td
Go to the project's activity feed
%tr
%td.shortcut
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index dbc68c39bf1..54cddc30b74 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -7,14 +7,29 @@
%title= page_title
= favicon_link_tag 'favicon.ico'
- = stylesheet_link_tag "application", :media => "all"
- = stylesheet_link_tag "print", :media => "print"
+
+ = stylesheet_link_tag "application", media: "all"
+ = stylesheet_link_tag "print", media: "print"
+
= javascript_include_tag "application"
+
= csrf_meta_tags
+
= include_gon
+
%meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'}
%meta{name: 'theme-color', content: '#474D57'}
+ -# Apple Safari/iOS home screen icons
+ = favicon_link_tag 'touch-icon-iphone.png', rel: 'apple-touch-icon'
+ = favicon_link_tag 'touch-icon-ipad.png', rel: 'apple-touch-icon', sizes: '76x76'
+ = favicon_link_tag 'touch-icon-iphone-retina.png', rel: 'apple-touch-icon', sizes: '120x120'
+ = favicon_link_tag 'touch-icon-ipad-retina.png', rel: 'apple-touch-icon', sizes: '152x152'
+
+ -# Windows 8 pinned site tile
+ %meta{name: 'msapplication-TileImage', content: image_url('msapplication-tile.png')}
+ %meta{name: 'msapplication-TileColor', content: '#30353E'}
+
= yield :meta_tags
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index f17f6fdd91c..96e15783a36 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,6 +1,6 @@
.page-with-sidebar{ class: nav_sidebar_class }
= render "layouts/broadcast"
- .sidebar-wrapper
+ .sidebar-wrapper.nicescroll
- if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}"
- elsif current_user
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 9d216be151a..695ce68a201 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,9 +1,17 @@
%ul.nav.nav-sidebar
+ = nav_link do
+ = link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do
+ = icon('caret-square-o-left fw')
+ %span
+ Back to Dashboard
+
+ %li.separate-item
+
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home', data: {placement: 'right'} do
= icon('dashboard fw')
%span
- Activity
+ Group
- if current_user
= nav_link(controller: [:group, :milestones]) do
= link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} do
diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml
index 72ada771ca4..8075fe32fbc 100644
--- a/app/views/layouts/nav/_group_settings.html.haml
+++ b/app/views/layouts/nav/_group_settings.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
= nav_link do
- = link_to group_path(@group), title: 'Back to group', data: {placement: 'right'} do
+ = link_to group_path(@group), title: 'Back to group', data: {placement: 'right'}, class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Back to group
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index de5544268a1..33fd5fcef6c 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,4 +1,12 @@
%ul.nav.nav-sidebar
+ = nav_link do
+ = link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do
+ = icon('caret-square-o-left fw')
+ %span
+ Back to Dashboard
+
+ %li.separate-item
+
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile', data: {placement: 'right'} do
= icon('user fw')
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index 6de97302dc1..d17d1c5fbd4 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -1,9 +1,29 @@
%ul.nav.nav-sidebar
+ - if @project.group
+ = nav_link do
+ = link_to group_path(@project.group), title: 'Back to group', data: {placement: 'right'}, class: 'back-link' do
+ = icon('caret-square-o-left fw')
+ %span
+ Back to Group
+ - else
+ = nav_link do
+ = link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do
+ = icon('caret-square-o-left fw')
+ %span
+ Back to Dashboard
+
+ %li.separate-item
+
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project', data: {placement: 'right'} do
- = icon('dashboard fw')
+ = icon('home fw')
%span
Project
+ = nav_link(path: 'projects#activity') do
+ = link_to activity_project_path(@project), title: 'Project Activity', class: 'shortcuts-project-activity', data: {placement: 'right'} do
+ = icon('dashboard fw')
+ %span
+ Activity
- if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
= link_to namespace_project_tree_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree', 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 9c86d3c09b2..857fb199957 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -1,6 +1,6 @@
%ul.nav.nav-sidebar
= nav_link do
- = link_to project_path(@project), title: 'Back to project', data: {placement: 'right'} do
+ = link_to project_path(@project), title: 'Back to project', data: {placement: 'right'}, class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Back to project
diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml
index d2fad31eca2..3a3e6e1b1c4 100644
--- a/app/views/profiles/applications.html.haml
+++ b/app/views/profiles/applications.html.haml
@@ -66,4 +66,4 @@
%td= token.scopes
%td= render 'doorkeeper/authorized_applications/delete_form', token: token
- else
- %p.light You dont have any authorized applications
+ %p.light You don't have any authorized applications
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index aa99280fde6..1134317ee06 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -38,5 +38,13 @@
= link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank')
.col-sm-10
= f.select :dashboard, dashboard_choices, {}, class: 'form-control'
+ .form-group
+ = f.label :project_view, class: 'control-label' do
+ Project view
+ = link_to('(?)', help_page_path('profile', 'preferences') + '#default-project-view', target: '_blank')
+ .col-sm-10
+ = f.select :project_view, project_view_choices, {}, class: 'form-control'
+ .help-block
+ Choose what content you want to see when visit project page
.panel-footer
= f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
new file mode 100644
index 00000000000..ee02b7f6a6c
--- /dev/null
+++ b/app/views/projects/_activity.html.haml
@@ -0,0 +1,15 @@
+= render 'projects/last_push'
+.hidden-xs
+ - if current_user
+ %ul.nav.nav-pills.event_filter.pull-right
+ %li
+ = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'rss-btn' do
+ %i.fa.fa-rss
+
+ = render 'shared/event_filter'
+ %hr
+.content_list{:"data-href" => activity_project_path(@project)}
+= spinner
+
+:coffeescript
+ new Activities()
diff --git a/app/views/projects/_aside.html.haml b/app/views/projects/_aside.html.haml
deleted file mode 100644
index 72aea8814f5..00000000000
--- a/app/views/projects/_aside.html.haml
+++ /dev/null
@@ -1,106 +0,0 @@
-.clearfix
- - unless @project.empty_repo?
- .panel.panel-default
- .panel-heading
- = visibility_level_icon(@project.visibility_level)
- = "#{visibility_level_label(@project.visibility_level).capitalize} project"
-
- .panel-body
- - if @repository.changelog || @repository.license || @repository.contribution_guide
- %ul.nav.nav-pills
- - if @repository.changelog
- %li.hidden-xs
- = link_to changelog_url(@project) do
- Changelog
- - if @repository.license
- %li
- = link_to license_url(@project) do
- License
- - if @repository.contribution_guide
- %li
- = link_to contribution_guide_url(@project) do
- Contribution guide
-
- .actions
- - if can? current_user, :create_issue, @project
- = link_to url_for_new_issue(@project, only_path: true), title: "New Issue", class: 'btn btn-sm append-right-10' do
- New Issue
-
- - if can? current_user, :create_merge_request, @project
- = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-sm", title: "New Merge Request" do
- New Merge Request
-
- - if forked_from_project = @project.forked_from_project
- .panel-footer
- = icon("code-fork fw")
- Forked from
- .pull-right
- = link_to forked_from_project.namespace.try(:name), project_path(forked_from_project)
-
-
- - @project.ci_services.each do |ci_service|
- - if ci_service.active? && ci_service.respond_to?(:builds_path)
- .panel-footer
- = icon("check fw")
- = ci_service.title
- .pull-right
- - if ci_service.respond_to?(:status_img_path)
- = link_to ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' do
- = image_tag ci_service.status_img_path, alt: "build status", class: 'ci-status-image'
- - else
- = link_to 'view builds', ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink'
-
-
- - unless @project.empty_repo?
- .panel.panel-default
- .panel-heading
- = icon("folder-o fw")
- Repository
- .panel-body
- %ul.nav.nav-pills
- %li
- = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do
- = pluralize(number_with_delimiter(@repository.commit_count), 'commit')
- %li
- = link_to namespace_project_branches_path(@project.namespace, @project) do
- = pluralize(number_with_delimiter(@repository.branch_names.count), 'branch')
- %li
- = link_to namespace_project_tags_path(@project.namespace, @project) do
- = pluralize(number_with_delimiter(@repository.tag_names.count), 'tag')
-
- .actions
- = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-sm append-right-10' do
- %i.fa.fa-exchange
- Compare code
-
- - if can?(current_user, :download_code, @project)
- = render 'projects/repositories/download_archive', split_button: true, btn_class: 'btn-group-sm'
- - if version = @repository.version
- .panel-footer
- = icon("clock-o fw")
- Version
- .pull-right
- = link_to version_url(@project) do
- = @repository.blob_by_oid(version.id).data
-
- = render "shared/clone_panel"
-
- - if @project.archived?
- %br
- .alert.alert-warning
- %h4
- = icon("exclamation-triangle fw")
- Archived project!
- %p Repository is read-only
-
- - if current_user
- - access = user_max_access_in_project(current_user, @project)
- - if access
- .light-well.light.prepend-top-20
- %small
- You have #{access} access to this project.
- - if @project.project_member_by_id(current_user)
- %br
- = link_to leave_namespace_project_project_members_path(@project.namespace, @project),
- data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project' do
- Leave this project
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 076afb11a9d..7b6b4b35c8d 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,37 +1,28 @@
- empty_repo = @project.empty_repo?
.project-home-panel.clearfix{:class => ("empty-project" if empty_repo)}
.project-identicon-holder
- = project_icon(@project, alt: '', class: 'avatar project-avatar')
- .project-home-row.project-home-row-top
- .project-home-desc
- - if @project.description.present?
- = markdown(@project.description, pipeline: :description)
- - if can?(current_user, :admin_project, @project)
- &ndash;
- = link_to 'Edit', edit_namespace_project_path
- - elsif !empty_repo && @repository.readme
- - readme = @repository.readme
- &ndash;
- = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do
- = readme.name
- .project-repo-buttons
- .inline.star.js-toggler-container{class: @show_star ? 'on' : ''}
- - if current_user
- = link_to_toggle_star('Star this project.', false)
- = link_to_toggle_star('Unstar this project.', true)
- - else
- = link_to new_user_session_path, class: 'btn star-btn has_tooltip', title: 'You must sign in to star a project' do
- %span
- = icon('star')
- Star
- %span.count
- = @project.star_count
- - unless empty_repo
- - if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace
- .inline.fork-buttons.prepend-left-10
- - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
- = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-sm btn-default' do
- = link_to_toggle_fork
- - else
- = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-sm btn-default' do
- = link_to_toggle_fork
+ = project_icon(@project, alt: '', class: 'project-avatar avatar s90')
+ .project-home-desc.lead
+ - if @project.description.present?
+ = markdown(@project.description, pipeline: :description)
+
+
+ .project-repo-buttons
+ = render 'projects/buttons/star'
+
+ - unless empty_repo
+ = render 'projects/buttons/fork'
+
+ - if forked_from_project = @project.forked_from_project
+ = link_to project_path(forked_from_project), class: 'btn' do
+ = icon("code-fork fw")
+ Forked from
+ = forked_from_project.namespace.try(:name)
+
+ - if can? current_user, :download_code, @project
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
+ %i.fa.fa-download
+
+ = render 'projects/buttons/dropdown'
+
+ = render "shared/clone_panel"
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
new file mode 100644
index 00000000000..30622d8a910
--- /dev/null
+++ b/app/views/projects/_last_push.html.haml
@@ -0,0 +1,14 @@
+- if event = last_push_event
+ - if show_last_push_widget?(event)
+ .hidden-xs.center
+ .slead
+ %span You pushed to
+ = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do
+ %strong= event.ref_name
+ branch
+ #{time_ago_with_tooltip(event.created_at)}
+
+ %div
+ = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do
+ Create Merge Request
+ %hr
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
new file mode 100644
index 00000000000..5038edb95ed
--- /dev/null
+++ b/app/views/projects/_readme.html.haml
@@ -0,0 +1,24 @@
+- if readme = @repository.readme
+ %article.readme-holder#README
+ .clearfix
+ .pull-right
+ &nbsp;
+ - if can?(current_user, :push_code, @project)
+ = link_to namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do
+ %i.fa.fa-pencil
+ .wiki
+ = cache(readme_cache_key) do
+ = render_readme(readme)
+- else
+ %h3.page-title
+ This project does not have README yet
+ - if can?(current_user, :push_code, @project)
+ %p.slead
+ A
+ %code README
+ file contains information about other files in a repository and is commonly
+ distributed with computer software, forming part of its documentation.
+ %br
+ We recommend you to
+ = link_to "add README", new_readme_path, class: 'underlined-link'
+ file to the repository and GitLab will render it here instead of this message.
diff --git a/app/views/projects/_section.html.haml b/app/views/projects/_section.html.haml
deleted file mode 100644
index d7b06197f67..00000000000
--- a/app/views/projects/_section.html.haml
+++ /dev/null
@@ -1,36 +0,0 @@
-%ul.nav.nav-tabs
- %li.active
- = link_to '#tab-activity', 'data-toggle' => 'tab' do
- = icon("tachometer")
- Activity
- - if @repository.readme
- %li
- = link_to '#tab-readme', 'data-toggle' => 'tab' do
- = icon("file-text-o")
- Readme
-.tab-content
- .tab-pane.active#tab-activity
- .hidden-xs
- = render "events/event_last_push", event: @last_push
-
- - if current_user
- %ul.nav.nav-pills.event_filter.pull-right
- %li
- = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'rss-btn' do
- %i.fa.fa-rss
-
- = render 'shared/event_filter'
- %hr
- .content_list
- = spinner
-
- - if readme = @repository.readme
- .tab-pane#tab-readme
- %article.readme-holder#README
- .clearfix
- %small.pull-right
- = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do
- %i.fa.fa-file
- = readme.name
- .wiki
- = render_readme(readme)
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index a4e41eeb363..6a41cdbc907 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -2,7 +2,7 @@
%input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' }
.zen-backdrop
- classes << ' js-gfm-input markdown-area'
- = f.text_area attr, class: classes, placeholder: random_markdown_tip
+ = f.text_area attr, class: classes, placeholder: ''
= link_to nil, class: 'zen-enter-link', tabindex: '-1' do
%i.fa.fa-expand
Edit in fullscreen
diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml
new file mode 100644
index 00000000000..65674913bb0
--- /dev/null
+++ b/app/views/projects/activity.html.haml
@@ -0,0 +1 @@
+= render 'projects/activity'
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index a1d464bac59..bd2fc43633c 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -1,4 +1,7 @@
- page_title @blob.path, @ref
+
+= render 'projects/last_push'
+
%div.tree-ref-holder
= render 'shared/ref_switcher', destination: 'blob', path: @path
diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
new file mode 100644
index 00000000000..99c2ed62545
--- /dev/null
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -0,0 +1,31 @@
+- if current_user
+ %span.dropdown
+ %a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-plus
+ %ul.dropdown-menu
+ - if @project.issues_enabled && can?(current_user, :create_issue, @project)
+ %li
+ = link_to url_for_new_issue, title: "New Issue" do
+ New issue
+ - if @project.merge_requests_enabled && can?(current_user, :create_merge_request, @project)
+ %li
+ = link_to new_namespace_project_merge_request_path(@project.namespace, @project), title: "New Merge Request" do
+ New merge request
+ - if @project.snippets_enabled && can?(current_user, :create_snippet, @project)
+ %li
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
+ New snippet
+ - if can?(current_user, :admin_project_member, @project)
+ %li
+ = link_to namespace_project_project_members_path(@project.namespace, @project), title: "New project member" do
+ New project member
+ - if can? current_user, :push_code, @project
+ %li.divider
+ %li
+ = link_to new_namespace_project_branch_path(@project.namespace, @project) do
+ New git branch
+ %li
+ = link_to new_namespace_project_tag_path(@project.namespace, @project) do
+ New git tag
+
+
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
new file mode 100644
index 00000000000..f0483c79edc
--- /dev/null
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -0,0 +1,13 @@
+- if current_user && can?(current_user, :fork_project, @project)
+ - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2
+ = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn' do
+ = icon('code-fork')
+ Fork
+ %span.count
+ = @project.forks_count
+ - else
+ = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn' do
+ = icon('code-fork')
+ Fork
+ %span.count
+ = @project.forks_count
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
new file mode 100644
index 00000000000..b5f14b43bfd
--- /dev/null
+++ b/app/views/projects/buttons/_star.html.haml
@@ -0,0 +1,22 @@
+- if current_user
+ = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star', method: :post, remote: true do
+ = icon('star')
+ - if current_user.starred?(@project)
+ Unstar
+ - else
+ Star
+ %span.count
+ = @project.star_count
+
+ :coffeescript
+ $('.project-home-panel .toggle-star').on 'ajax:success', (e, data, status, xhr) ->
+ $(@).replaceWith(data.html)
+ .on 'ajax:error', (e, xhr, status, error) ->
+ new Flash('Star toggle failed. Try again later.', 'alert')
+
+- else
+ = link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do
+ = icon('star')
+ Star
+ %span.count
+ = @project.star_count
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index bd0b7376ba7..da3d4be84ba 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -1,6 +1,6 @@
.alert.alert-warning
%h4
- Too many changes.
+ Too many changes to show.
.pull-right
- unless diff_hard_limit_enabled?
= link_to "Reload with full diff", url_for(params.merge(force_show_diff: true)), class: "btn btn-sm btn-warning"
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 3fecd25c324..e8e65d87f47 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -29,7 +29,7 @@
.col-sm-10= f.select(:default_branch, @repository.branch_names, {}, {class: 'select2 select-wide'})
- = render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can?(current_user, :change_visibility_level, @project), form_model: @project
+ = render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can_change_visibility_level?(@project, current_user), form_model: @project
.form-group
= f.label :tag_list, "Tags", class: 'control-label'
@@ -176,7 +176,7 @@
.form-group
= label_tag :new_namespace_id, nil, class: 'control-label' do
%span Namespace
- .col-sm-10
+ .col-sm-9
.form-group
= select_tag :new_namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace', class: 'select2' }
%ul
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 8080a904978..dfe45a3802d 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -4,30 +4,30 @@
= render "home_panel"
-.center.well
- %h3
+.center.light-well
+ %h3.page-title
The repository for this project is empty
- %h4
- You can
- = link_to namespace_project_new_blob_path(@project.namespace, @project, 'master'), class: 'btn btn-new btn-lg' do
- add a file
- &nbsp;or do a push via the command line.
+ %p
+ If you already have files you can push them using command line instructions below.
+ %br
+ Otherwise you can start with
+ = link_to "adding README", new_readme_path, class: 'underlined-link'
+ file to this project.
-.well
- = render "shared/clone_panel"
-%h4
- %strong Command line instructions
+.prepend-top-20
+%h3.page-title
+ Command line instructions
%div.git-empty
%fieldset
- %legend Git global setup
- %pre.dark
+ %h5 Git global setup
+ %pre.light-well
:preserve
git config --global user.name "#{git_user_name}"
git config --global user.email "#{git_user_email}"
%fieldset
- %legend Create a new repository
- %pre.dark
+ %h5 Create a new repository
+ %pre.light-well
:preserve
git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
cd #{@project.path}
@@ -37,8 +37,8 @@
git push -u origin master
%fieldset
- %legend Existing folder or Git repository
- %pre.dark
+ %h5 Existing folder or Git repository
+ %pre.light-well
:preserve
cd existing_folder
git init
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index 254a76e108b..141acbdcf72 100644
--- a/app/views/projects/graphs/commits.html.haml
+++ b/app/views/projects/graphs/commits.html.haml
@@ -1,9 +1,11 @@
- page_title "Commit statistics"
+.tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'graphs_commits'
= render 'head'
%p.lead
Commit statistics for
- %strong #{@repository.root_ref}
+ %strong #{@ref}
#{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')}
.row
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 3a8dc89f84c..ecdd0eaf52f 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -1,5 +1,8 @@
- page_title "Contributor statistics"
+.tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'graphs'
= render 'head'
+
.loading-graph
.center
%h3.page-title
@@ -11,7 +14,7 @@
.header.clearfix
%h3#date_header.page-title
%p.light
- Commits to #{@project.default_branch}, excluding merge commits. Limited by 6,000 commits
+ Commits to #{@ref}, excluding merge commits. Limited by 6,000 commits
%input#brush_change{:type => "hidden"}
.graphs
#contributors-master
@@ -35,4 +38,3 @@
$(".stat-graph").fadeIn();
$(".loading-graph").hide();
dataType: "json"
-
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index 7fa1ee53f76..c6ebfa281a1 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -6,5 +6,5 @@
= pluralize label.open_issues_count, 'open issue'
- if can? current_user, :admin_label, @project
- = link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn'
- = link_to 'Remove', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
+ = link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm'
+ = link_to 'Remove', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index b6d9b135c70..faaa85896cf 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -31,6 +31,16 @@
%li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
+ - if @merge_request.open? and @merge_request.source_branch_exists?
+ .append-bottom-20
+ .slead
+ %span
+ Fetch the branch with
+ %strong.label-branch<
+ git fetch
+ \ #{@merge_request.source_project.http_url_to_repo}
+ \ #{@merge_request.source_branch}
+
= render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/widget/show.html.haml"
diff --git a/app/views/projects/merge_requests/branch_from.js.haml b/app/views/projects/merge_requests/branch_from.js.haml
index 8372afa61b5..9210798f39c 100644
--- a/app/views/projects/merge_requests/branch_from.js.haml
+++ b/app/views/projects/merge_requests/branch_from.js.haml
@@ -1,2 +1,3 @@
:plain
$(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}");
+ $('.js-timeago').timeago()
diff --git a/app/views/projects/merge_requests/branch_to.js.haml b/app/views/projects/merge_requests/branch_to.js.haml
index f7ede0ded53..32fe2d535f3 100644
--- a/app/views/projects/merge_requests/branch_to.js.haml
+++ b/app/views/projects/merge_requests/branch_to.js.haml
@@ -1,2 +1,3 @@
:plain
$(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}");
+ $('.js-timeago').timeago()
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index e0bc1df97ee..72fbe2e27a7 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,4 +1,5 @@
- page_title "Merge Requests"
+= render 'projects/last_push'
.append-bottom-10
.pull-right
= render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 14a0580f966..2ce5358fa74 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -5,6 +5,10 @@
%i.fa.fa-pencil-square-o
Edit
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close"
+ = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-sm btn-remove" do
+ %i.fa.fa-trash-o
+ Remove
+
%h4
= link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- if milestone.expired? and not milestone.closed?
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 5947498e379..7b1681df336 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -19,6 +19,9 @@
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
- else
= link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
+ = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do
+ %i.fa.fa-trash-o
+ Remove
%hr
- if @milestone.issues.any? && @milestone.can_be_closed?
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index 5114e63c05f..d49eacb30dd 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -85,7 +85,7 @@
%li
The import will time out after 4 minutes. For big repositories, use a clone/push combination.
%li
- To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}.
+ To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}.
%hr.prepend-botton-10
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index a663950f031..7472b33bb53 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -5,8 +5,8 @@
= render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field'
.comment-hints.clearfix
- .pull-left Comments are parsed with #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'),{ target: '_blank', tabindex: -1 }}
- .pull-right Attach files by dragging &amp; dropping or #{link_to 'selecting them', '#', class: 'markdown-selector', tabindex: -1 }.
+ .pull-left #{link_to 'Markdown ', help_page_path('markdown', 'markdown'),{ target: '_blank', tabindex: -1 }}
+ .pull-right #{link_to 'Attach a file', '#', class: 'markdown-selector', tabindex: -1 }
.note-form-actions
.buttons
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 3fb044d736e..64f98741d45 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -12,8 +12,14 @@
classes: 'note_text js-note-text'
.comment-hints.clearfix
- .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }}
- .pull-right Attach files by dragging &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }.
+ .pull-left
+ = link_to "Markdown ", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }
+ tip:
+ = random_markdown_tip
+ .pull-right
+ = link_to '#', class: 'markdown-selector', tabindex: -1 do
+ Attach a file
+ = icon('paperclip')
.error-alert
.note-form-actions
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 5478a887f91..c8d705687da 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -56,7 +56,7 @@
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
- = cache [note, 'markdown'] do
+ = cache [note, 'markdown', user_color_scheme_class] do
.note-text
= preserve do
= markdown(note.note, {no_header_anchors: true})
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 2259dea0865..769dd68f089 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -6,11 +6,56 @@
= render 'shared/no_ssh'
= render 'shared/no_password'
+- if prefer_readme?
+ = render 'projects/last_push'
+
= render "home_panel"
-= render 'shared/show_aside'
-.row
- %section.col-md-8
- = render 'section'
- %aside.col-md-4.project-side
- = render 'aside'
+.project-stats
+ %ul.nav.nav-pills
+ %li
+ = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do
+ = pluralize(number_with_delimiter(@project.commit_count), 'commit')
+ %li
+ = link_to namespace_project_branches_path(@project.namespace, @project) do
+ = pluralize(number_with_delimiter(@repository.branch_names.count), 'branch')
+ %li
+ = link_to namespace_project_tags_path(@project.namespace, @project) do
+ = pluralize(number_with_delimiter(@repository.tag_names.count), 'tag')
+ - if @repository.changelog
+ %li
+ = link_to changelog_url(@project) do
+ Changelog
+ - if @repository.license
+ %li
+ = link_to license_url(@project) do
+ License
+ - if @repository.contribution_guide
+ %li
+ = link_to contribution_guide_url(@project) do
+ Contribution guide
+
+- if @project.archived?
+ .text-warning.center.prepend-top-20
+ %p
+ = icon("exclamation-triangle fw")
+ Archived project! Repository is read-only
+
+%hr
+%section
+ - if prefer_readme?
+ = render 'projects/readme'
+ - else
+ = render 'projects/activity'
+
+
+- if current_user
+ - access = user_max_access_in_project(current_user, @project)
+ - if access
+ %hr
+ %p.light
+ You have #{access} access to this project.
+ - if @project.project_member_by_id(current_user)
+ = link_to leave_namespace_project_project_members_path(@project.namespace, @project),
+ data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project', class: 'cred' do
+ Leave this project
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 04590f65b27..c9e59428e78 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -2,7 +2,9 @@
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits")
-
+
+= render 'projects/last_push'
+
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index 84e9be82c44..58f58eff54d 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,3 +1,4 @@
+- blob = @project.repository.parse_search_result(blob)
.blob-result
.file-holder
.file-title
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index f9c5810e3d0..c03438eb952 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -1,3 +1,4 @@
+- wiki_blob = @project.repository.parse_search_result(wiki_blob)
.blob-result
.file-holder
.file-title
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 6de2aed29ed..07672359dba 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -20,7 +20,7 @@
:"data-container" => "body"}
= gitlab_config.protocol.upcase
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control input-sm", readonly: true
- - if project.kind_of?(Project) && project.empty_repo?
+ - if project.kind_of?(Project)
.input-group-addon
.visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(project.visibility_level)} project" }
= visibility_level_icon(project.visibility_level)
diff --git a/app/views/shared/_field.html.haml b/app/views/shared/_field.html.haml
index 30d37dceb30..45ec49280d2 100644
--- a/app/views/shared/_field.html.haml
+++ b/app/views/shared/_field.html.haml
@@ -1,6 +1,6 @@
- name = field[:name]
- title = field[:title] || name.humanize
-- value = service_field_value(field[:type], @service.send(name))
+- value = @service.send(name)
- type = field[:type]
- placeholder = field[:placeholder]
- choices = field[:choices]
@@ -19,6 +19,6 @@
- elsif type == 'select'
= form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
- elsif type == 'password'
- = form.password_field name, placeholder: value, class: 'form-control'
+ = form.password_field name, value: value, class: 'form-control'
- if help
%span.help-block= help
diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml
index 02416125a72..ebe2eb0433d 100644
--- a/app/views/shared/_visibility_radios.html.haml
+++ b/app/views/shared/_visibility_radios.html.haml
@@ -1,4 +1,5 @@
- Gitlab::VisibilityLevel.values.each do |level|
+ - next if skip_level?(form_model, level)
.radio
- restricted = restricted_visibility_levels.include?(level)
= form.label "#{model_method}_#{level}" do
diff --git a/app/views/shared/issuable/_context.html.haml b/app/views/shared/issuable/_context.html.haml
index 46990895d33..d1bd5ef968d 100644
--- a/app/views/shared/issuable/_context.html.haml
+++ b/app/views/shared/issuable/_context.html.haml
@@ -8,7 +8,7 @@
- else
none
.issuable-context-selectbox
- - if can?(current_user, :admin_issue, @project)
+ - if can?(current_user, :"admin_#{issuable.class.to_s.underscore}", @project)
= users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true)
%div.prepend-top-20.clearfix
@@ -24,7 +24,7 @@
- else
none
.issuable-context-selectbox
- - if can?(current_user, :admin_issue, @project)
+ - if can?(current_user, :"admin_#{issuable.class.to_s.underscore}", @project)
= f.select(:milestone_id, milestone_options(issuable), { include_blank: 'Select milestone' }, {class: 'select2 select2-compact js-select2 js-milestone'})
= hidden_field_tag :issuable_context
= f.submit class: 'btn hide'
diff --git a/app/views/shared/issuable/_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index a829782fc4f..0e8da8de723 100644
--- a/app/views/shared/issuable/_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -43,11 +43,15 @@
placeholder: 'Author', class: 'trigger-submit', any_user: true, first_user: true)
.filter-item.inline.milestone-filter
- = select_tag('milestone_title', projects_milestones_options, class: "select2 trigger-submit", prompt: 'Milestone')
+ = select_tag('milestone_title', projects_milestones_options,
+ class: 'select2 trigger-submit', include_blank: true,
+ data: {placeholder: 'Milestone'})
- if @project
.filter-item.inline.labels-filter
- = select_tag('label_name', project_labels_options(@project), class: "select2 trigger-submit", prompt: 'Label')
+ = select_tag('label_name', project_labels_options(@project),
+ class: 'select2 trigger-submit', include_blank: true,
+ data: {placeholder: 'Label'})
.pull-right
= render 'shared/sort_dropdown'
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
new file mode 100644
index 00000000000..55cb6af232e
--- /dev/null
+++ b/app/workers/project_cache_worker.rb
@@ -0,0 +1,15 @@
+class ProjectCacheWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :default
+
+ def perform(project_id)
+ project = Project.find(project_id)
+ project.update_repository_size
+ project.update_commit_count
+
+ if project.repository.root_ref
+ project.repository.build_cache
+ end
+ end
+end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index e6a50afedb1..94832872d13 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -28,7 +28,7 @@ class RepositoryImportWorker
project.import_finish
project.save
project.satellite.create unless project.satellite.exists?
- project.update_repository_size
+ ProjectCacheWorker.perform_async(project.id)
Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
end
end
diff --git a/config/application.rb b/config/application.rb
index 7e899cc3b5b..a96e22211e6 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -96,6 +96,7 @@ module Gitlab
end
redis_config_hash[:namespace] = 'cache:gitlab'
+ redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
config.cache_store = :redis_store, redis_config_hash
# This is needed for gitlab-shell
diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb
index df73ec1304a..7f73546ac89 100644
--- a/config/initializers/7_omniauth.rb
+++ b/config/initializers/7_omniauth.rb
@@ -11,6 +11,7 @@ if Gitlab::LDAP::Config.enabled?
end
end
+OmniAuth.config.full_host = Settings.gitlab['url']
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?
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index d422acb31d6..6139ddbe6cd 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -6,7 +6,8 @@ Doorkeeper.configure do
# This block will be called to check whether the resource owner is authenticated or not.
resource_owner_authenticator do
# Put your resource owner authentication logic here.
- # Example implementation:
+ # Ensure user is redirected to redirect_uri after login
+ session[:user_return_to] = request.fullpath
current_user || redirect_to(new_user_session_url)
end
diff --git a/config/routes.rb b/config/routes.rb
index 8617839a256..2e16c3ecb39 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -159,6 +159,7 @@ Gitlab::Application.routes.draw do
put :block
put :unblock
put :unlock
+ patch :disable_two_factor
delete 'remove/:email_id', action: 'remove_email', as: 'remove_email'
end
end
@@ -314,6 +315,7 @@ Gitlab::Application.routes.draw do
post :toggle_star
post :markdown_preview
get :autocomplete_sources
+ get :activity
end
scope module: :projects do
@@ -479,7 +481,7 @@ Gitlab::Application.routes.draw do
end
end
- resources :milestones, except: [:destroy], constraints: { id: /\d+/ } do
+ resources :milestones, constraints: { id: /\d+/ } do
member do
put :sort_issues
put :sort_merge_requests
diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb
index b25d0dfc701..bba2fc4b186 100644
--- a/db/fixtures/development/01_admin.rb
+++ b/db/fixtures/development/01_admin.rb
@@ -5,7 +5,7 @@ Gitlab::Seeder.quiet do
s.email = 'admin@example.com'
s.notification_email = 'admin@example.com'
s.username = 'root'
- s.password = 'password'
+ s.password = '5iveL!fe'
s.admin = true
s.projects_limit = 100
s.confirmed_at = DateTime.now
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index 87839770924..8f71198e47f 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -11,9 +11,42 @@ Sidekiq::Testing.inline! do
'https://github.com/twitter/flight.git',
'https://github.com/twitter/typeahead.js.git',
'https://github.com/h5bp/html5-boilerplate.git',
+ 'https://github.com/google/material-design-lite.git',
+ 'https://github.com/jlevy/the-art-of-command-line.git',
+ 'https://github.com/FreeCodeCamp/freecodecamp.git',
+ 'https://github.com/google/deepdream.git',
+ 'https://github.com/jtleek/datasharing.git',
+ 'https://github.com/WebAssembly/design.git',
+ 'https://github.com/airbnb/javascript.git',
+ 'https://github.com/tessalt/echo-chamber-js.git',
+ 'https://github.com/atom/atom.git',
+ 'https://github.com/ipselon/react-ui-builder.git',
+ 'https://github.com/mattermost/platform.git',
+ 'https://github.com/purifycss/purifycss.git',
+ 'https://github.com/facebook/nuclide.git',
+ 'https://github.com/wbkd/awesome-d3.git',
+ 'https://github.com/kilimchoi/engineering-blogs.git',
+ 'https://github.com/gilbarbara/logos.git',
+ 'https://github.com/gaearon/redux.git',
+ 'https://github.com/awslabs/s2n.git',
+ 'https://github.com/arkency/reactjs_koans.git',
+ 'https://github.com/twbs/bootstrap.git',
+ 'https://github.com/chjj/ttystudio.git',
+ 'https://github.com/DrBoolean/mostly-adequate-guide.git',
+ 'https://github.com/octocat/Spoon-Knife.git',
+ 'https://github.com/opencontainers/runc.git',
+ 'https://github.com/googlesamples/android-topeka.git'
]
- project_urls.each_with_index do |url, i|
+ # You can specify how many projects you need during seed execution
+ size = if ENV['SIZE'].present?
+ ENV['SIZE'].to_i
+ else
+ 8
+ end
+
+
+ project_urls.first(size).each_with_index do |url, i|
group_path, project_path = url.split('/')[-2..-1]
group = Group.find_by(path: group_path)
diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb
index 1af8dfc0ef0..1c8740f6ba9 100644
--- a/db/fixtures/production/001_admin.rb
+++ b/db/fixtures/production/001_admin.rb
@@ -1,5 +1,5 @@
if ENV['GITLAB_ROOT_PASSWORD'].blank?
- password = 'password'
+ password = '5iveL!fe'
expire_time = Time.now
else
password = ENV['GITLAB_ROOT_PASSWORD']
diff --git a/db/migrate/20150713160110_add_project_view_to_users.rb b/db/migrate/20150713160110_add_project_view_to_users.rb
new file mode 100644
index 00000000000..fe3d206df89
--- /dev/null
+++ b/db/migrate/20150713160110_add_project_view_to_users.rb
@@ -0,0 +1,5 @@
+class AddProjectViewToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :project_view, :integer, default: 0
+ end
+end
diff --git a/db/migrate/20150717130904_add_commits_count_to_project.rb b/db/migrate/20150717130904_add_commits_count_to_project.rb
new file mode 100644
index 00000000000..9b46daa5933
--- /dev/null
+++ b/db/migrate/20150717130904_add_commits_count_to_project.rb
@@ -0,0 +1,5 @@
+class AddCommitsCountToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :commit_count, :integer, default: 0
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 8736d1e0df5..a63c2d05821 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: 20150620233230) do
+ActiveRecord::Schema.define(version: 20150717130904) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -28,11 +28,11 @@ ActiveRecord::Schema.define(version: 20150620233230) do
t.integer "default_branch_protection", default: 2
t.boolean "twitter_sharing_enabled", default: true
t.text "restricted_visibility_levels"
+ t.boolean "version_check_enabled", default: true
t.integer "max_attachment_size", default: 10, null: false
t.integer "default_project_visibility"
t.integer "default_snippet_visibility"
t.text "restricted_signup_domains"
- t.boolean "version_check_enabled", default: true
t.boolean "user_oauth_applications", default: true
t.string "after_sign_out_path"
t.integer "session_expire_delay", default: 10080, null: false
@@ -374,6 +374,7 @@ ActiveRecord::Schema.define(version: 20150620233230) do
t.integer "star_count", default: 0, null: false
t.string "import_type"
t.string "import_source"
+ t.integer "commit_count", default: 0
end
add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree
@@ -510,13 +511,14 @@ ActiveRecord::Schema.define(version: 20150620233230) do
t.string "bitbucket_access_token"
t.string "bitbucket_access_token_secret"
t.string "location"
- t.string "public_email", default: "", null: false
t.string "encrypted_otp_secret"
t.string "encrypted_otp_secret_iv"
t.string "encrypted_otp_secret_salt"
t.boolean "otp_required_for_login", default: false, null: false
t.text "otp_backup_codes"
+ t.string "public_email", default: "", null: false
t.integer "dashboard", default: 0
+ t.integer "project_view", default: 0
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
diff --git a/doc/api/README.md b/doc/api/README.md
index ca58c184543..f369c3fd978 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -4,7 +4,7 @@
- [Users](users.md)
- [Session](session.md)
-- [Projects](projects.md)
+- [Projects](projects.md) including setting Webhooks
- [Project Snippets](project_snippets.md)
- [Services](services.md)
- [Repositories](repositories.md)
@@ -20,6 +20,7 @@
- [System Hooks](system_hooks.md)
- [Groups](groups.md)
- [Namespaces](namespaces.md)
+- [Settings](settings.md)
## Clients
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 7b0873a9111..bb551fc67f7 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -49,7 +49,8 @@ Parameters:
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
- "description":"fixed login page css paddings"
+ "description":"fixed login page css paddings",
+ "work_in_progress": false
}
]
```
@@ -94,7 +95,8 @@ Parameters:
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
- "description":"fixed login page css paddings"
+ "description":"fixed login page css paddings",
+ "work_in_progress": false
}
```
@@ -118,6 +120,7 @@ Parameters:
"project_id": 4,
"title": "Blanditiis beatae suscipit hic assumenda et molestias nisi asperiores repellat et.",
"description": "Qui voluptatibus placeat ipsa alias quasi. Deleniti rem ut sint. Optio velit qui distinctio.",
+ "work_in_progress": false,
"state": "reopened",
"created_at": "2015-02-02T19:49:39.159Z",
"updated_at": "2015-02-02T20:08:49.959Z",
@@ -336,14 +339,6 @@ Parameters:
```json
{
- "author": {
- "id": 1,
- "username": "admin",
- "email": "admin@example.com",
- "name": "Administrator",
- "blocked": false,
- "created_at": "2012-04-29T08:46:00Z"
- },
"note": "text1"
}
```
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 17c014019ea..10533c73a31 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -4,16 +4,16 @@
### Project visibility level
Project in GitLab has be either private, internal or public.
-You can determine it by `visibility_level` field in project.
+You can determine it by `visibility_level` field in project.
Constants for project visibility levels are next:
-* Private. `visibility_level` is `0`.
+* Private. `visibility_level` is `0`.
Project access must be granted explicitly for each user.
* Internal. `visibility_level` is `10`.
The project can be cloned by any logged in user.
-
+
* Public. `visibility_level` is `20`.
The project can be cloned without any authentication.
@@ -362,7 +362,7 @@ Parameters:
- `public` (optional) - if `true` same as setting visibility_level = 20
- `visibility_level` (optional)
-On success, method returns 200 with the updated project. If parameters are
+On success, method returns 200 with the updated project. If parameters are
invalid, 400 is returned.
### Fork project
@@ -479,6 +479,9 @@ rely on the returned JSON structure.
## Hooks
+Also called Project Hooks and Webhooks.
+These are different for [System Hooks](system_hooks.md) that are system wide.
+
### List project hooks
Get a list of project hooks.
diff --git a/doc/api/settings.md b/doc/api/settings.md
new file mode 100644
index 00000000000..d1b93a09c02
--- /dev/null
+++ b/doc/api/settings.md
@@ -0,0 +1,88 @@
+# Application settings
+
+This API allows you to read and modify GitLab instance application settings.
+
+
+## Get current application settings:
+
+```
+GET /application/settings
+```
+
+```json
+{
+ "id": 1,
+ "default_projects_limit": 10,
+ "signup_enabled": true,
+ "signin_enabled": true,
+ "gravatar_enabled": true,
+ "sign_in_text": "",
+ "created_at": "2015-06-12T15:51:55.432Z",
+ "updated_at": "2015-06-30T13:22:42.210Z",
+ "home_page_url": "",
+ "default_branch_protection": 2,
+ "twitter_sharing_enabled": true,
+ "restricted_visibility_levels": [],
+ "max_attachment_size": 10,
+ "session_expire_delay": 10080,
+ "default_project_visibility": 0,
+ "default_snippet_visibility": 0,
+ "restricted_signup_domains": [],
+ "user_oauth_applications": true,
+ "after_sign_out_path": ""
+}
+```
+
+## Change application settings:
+
+
+
+```
+PUT /application/settings
+```
+
+Parameters:
+
+- `default_projects_limit` - project limit per user
+- `signup_enabled` - enable registration
+- `signin_enabled` - enable login via GitLab account
+- `gravatar_enabled` - enable gravatar
+- `sign_in_text` - text on login page
+- `home_page_url` - redirect to this URL when not logged in
+- `default_branch_protection` - determine if developers can push to master
+- `twitter_sharing_enabled` - allow users to share project creation in twitter
+- `restricted_visibility_levels` - restrict certain visibility levels
+- `max_attachment_size` - limit attachment size
+- `session_expire_delay` - session lifetime
+- `default_project_visibility` - what visibility level new project receives
+- `default_snippet_visibility` - what visibility level new snippet receives
+- `restricted_signup_domains` - force people to use only corporate emails for signup
+- `user_oauth_applications` - allow users to create oauth applicaitons
+- `after_sign_out_path` - where redirect user after logout
+
+All parameters are optional. You can send only one that you want to change.
+
+
+```json
+{
+ "id": 1,
+ "default_projects_limit": 10,
+ "signup_enabled": true,
+ "signin_enabled": true,
+ "gravatar_enabled": true,
+ "sign_in_text": "",
+ "created_at": "2015-06-12T15:51:55.432Z",
+ "updated_at": "2015-06-30T13:22:42.210Z",
+ "home_page_url": "",
+ "default_branch_protection": 2,
+ "twitter_sharing_enabled": true,
+ "restricted_visibility_levels": [],
+ "max_attachment_size": 10,
+ "session_expire_delay": 10080,
+ "default_project_visibility": 0,
+ "default_snippet_visibility": 0,
+ "restricted_signup_domains": [],
+ "user_oauth_applications": true,
+ "after_sign_out_path": ""
+}
+```
diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md
index fd55c44a2c4..d6272cd5912 100644
--- a/doc/gitlab-basics/README.md
+++ b/doc/gitlab-basics/README.md
@@ -11,3 +11,9 @@ Step-by-step guides on the basics of working with Git and GitLab.
* [Basic Git commands](basic-git-commands.md)
* [Create a project](create-project.md)
+
+* [Create a group](create-group.md)
+
+* [Create a branch](create-branch.md)
+
+* [Fork a project](fork-project.md)
diff --git a/doc/gitlab-basics/basic-git-commands.md b/doc/gitlab-basics/basic-git-commands.md
index ed210ba5420..2b5767dd2d3 100644
--- a/doc/gitlab-basics/basic-git-commands.md
+++ b/doc/gitlab-basics/basic-git-commands.md
@@ -1,58 +1,58 @@
# Basic Git commands
-* Go to the master branch to pull the latest changes from there
+### Go to the master branch to pull the latest changes from there
```
git checkout master
```
-* Download the latest changes in the project, so that you work on an up-to-date copy (this is important to do every time you work on a project), while you setup tracking branches
+### Download the latest changes in the project
+This is for you to work on an up-to-date copy (it is important to do every time you work on a project), while you setup tracking branches.
```
git pull REMOTE NAME-OF-BRANCH -u
```
(REMOTE: origin) (NAME-OF-BRANCH: could be "master" or an existing branch)
-* Create a branch (remember that spaces won't be recognized, you need to use a hyphen or underscore)
+### Create a branch
+Spaces won't be recognized, so you need to use a hyphen or underscore.
```
git checkout -b NAME-OF-BRANCH
```
-* Work on a branch that has already been created
+### Work on a branch that has already been created
```
git checkout NAME-OF-BRANCH
```
-* To see the changes you've made (it's important to be aware of what's happening and what's the status of your changes)
+### View the changes you've made
+It's important to be aware of what's happening and what's the status of your changes.
```
git status
```
-* Add changes to commit (you'll be able to see your changes in red when you type "git status")
+### Add changes to commit
+You'll see your changes in red when you type "git status".
```
git add CHANGES IN RED
git commit -m "DESCRIBE THE INTENTION OF THE COMMIT"
```
-* Send changes to gitlab.com
+### Send changes to gitlab.com
```
-git push origin NAME-OF-BRANCH
+git push REMOTE NAME-OF-BRANCH
```
-* Throw away all changes in the Git repository, but leave unstaged things
+### Delete all changes in the Git repository, but leave unstaged things
```
git checkout .
```
-* Delete all changes in the Git repository, including untracked files
+### Delete all changes in the Git repository, including untracked files
```
git clean -f
```
-* Remove all the changes that you don't want to send to gitlab.com
-```
-git add NAME-OF-FILE -all
-```
-
-* Merge created branch with master branch. You need to be in the created branch
+### Merge created branch with master branch
+You need to be in the created branch.
```
git checkout NAME-OF-BRANCH
git merge master
diff --git a/doc/gitlab-basics/basicsimages/click-on-new-group.png b/doc/gitlab-basics/basicsimages/click-on-new-group.png
new file mode 100644
index 00000000000..94b6d5756d3
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/click-on-new-group.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/select-group.png b/doc/gitlab-basics/basicsimages/select-group.png
new file mode 100644
index 00000000000..d02c2255ff2
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/select-group.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/select-group2.png b/doc/gitlab-basics/basicsimages/select-group2.png
new file mode 100644
index 00000000000..fd40bce499b
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/select-group2.png
Binary files differ
diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md
index 30f78d1e6a5..a8223a9b161 100644
--- a/doc/gitlab-basics/command-line-commands.md
+++ b/doc/gitlab-basics/command-line-commands.md
@@ -2,46 +2,47 @@
## Start working on your project
-* In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, start by signing in at GitLab.com.. To do it, go to your [GitLab.com](https://gitlab.com) account
+In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to [GitLab.com](https://gitlab.com).
-* When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen
+When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen.
![Select a project](basicsimages/select_project.png)
-* To work in the project, you can copy a link to the Git repository through a SSH or a HTTPS protocol. SSH is easier to use after it's been [setup](create-your-ssh-keys.md). When you're in the project, click on the HTTPS or SSH button at the right side of your screen. Then copy the link (you'll have to paste it on your shell in the next step)
+To work in the project, you can copy a link to the Git repository through a SSH or a HTTPS protocol. SSH is easier to use after it's been [setup](create-your-ssh-keys.md). When you're in the project, click on the HTTPS or SSH button at the right side of your screen. Then copy the link (you'll have to paste it on your shell in the next step).
![Copy the HTTPS or SSH](basicsimages/https.png)
## On the command line
-* To clone your project, go to your computer's shell and type the following command
+### Clone your project
+Go to your computer's shell and type the following command:
```
git clone PASTE HTTPS OR SSH HERE
```
-* A clone of the project will be created in your computer
+A clone of the project will be created in your computer.
-* Go into a project, directory or file to work in it
+### Go into a project, directory or file to work in it
```
cd NAME-OF-PROJECT-OR-FILE
```
-* Go back one directory or file
+### Go back one directory or file
```
cd ../
```
-* To see what’s in the directory that you are in
+### View what’s in the directory that you are in
```
ls
```
-* Create a directory
+### Create a directory
```
mkdir NAME-OF-YOUR-DIRECTORY
```
-* Create a README.md or file in directory
+### Create a README.md or file in directory
```
touch README.md
nano README.md
@@ -51,22 +52,23 @@ nano README.md
#### Press: enter
```
-* Remove a file
+### Remove a file
```
rm NAME-OF-FILE
```
-* Remove a directory and all of its contents
+### Remove a directory and all of its contents
```
rm -rf NAME-OF-DIRECTORY
```
-* View history in the command line
+### View history in the command line
```
history
```
-* Carry out commands for which the account you are using lacks authority. (You will be asked for an administrator’s password)
+### Carry out commands for which the account you are using lacks authority
+You will be asked for an administrator’s password.
```
sudo
```
diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md
new file mode 100644
index 00000000000..7556b0f663e
--- /dev/null
+++ b/doc/gitlab-basics/create-branch.md
@@ -0,0 +1,39 @@
+# How to create a branch
+
+A branch is an independent line of development.
+
+New commits are recorded in the history for the current branch, which results in taking the source from someone’s repository (the place where the history of your work is stored) at certain point in time, and apply your own changes to it in the history of the project.
+
+To add changes to your GitLab project, you should create a branch. You can do it in your [shell](basic-git-commands.md) or in GitLab.
+
+To create a new branch in GitLab, sign in and then select a project on the right side of your screen:
+
+![Select a project](basicsimages/select_project.png)
+
+Click on "commits" on the menu on the left side of your screen:
+
+![Commits](basicsimages/commits.png)
+
+Click on the "branches" tab:
+
+![Branches](basicsimages/branches.png)
+
+Click on the "new branch" button on the right side of the screen:
+
+![New branch](basicsimages/newbranch.png)
+
+Fill out the information required:
+
+1. Add a name for your new branch (you can't add spaces, so you can use hyphens or underscores)
+
+1. On the "create from" space, add the the name of the branch you want to branch off from
+
+1. Click on the button "create branch"
+
+![Branch info](basicsimages/branch_info.png)
+
+### Note:
+
+You will be able to find and select the name of your branch in the white box next to a project's name:
+
+![Branch name](basicsimages/branch_name.png)
diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md
new file mode 100644
index 00000000000..8e168395ff7
--- /dev/null
+++ b/doc/gitlab-basics/create-group.md
@@ -0,0 +1,43 @@
+# How to create a group in GitLab
+
+## Create a group
+
+Your projects in [GitLab.com](https://gitlab.com) can be organized in 2 different ways:
+under your own namespace for single projects, such as ´your-name/project-1'; or under groups.
+If you organize your projects under a group, it works like a folder. You can manage your group members' permissions and access to the projects.
+
+To create a group, follow the instructions below:
+
+Sign in to [GitLab.com](https://gitlab.com).
+
+When you are on your Dashboard, click on "Groups" on the left menu of your screen:
+
+![Go to groups](basicsimages/select-group2.png)
+
+Click on "New group" on the top right side of your screen:
+
+![New group](basicsimages/click-on-new-group.png)
+
+Fill out the information required:
+
+1. Add a group path or group name (you can't add spaces, so you can use hyphens or underscores)
+
+1. Add details or a group description
+
+1. You can choose a group avatar if you'd like
+
+1. Click on "create group"
+
+![Group information](basicsimages/group_info.png)
+
+## Add a project to a group
+
+There are 2 different ways to add a new project to a group:
+
+* Select a group and then click on "New project" on the right side of your screen. Then you can [create a project](create-project.md)
+
+![New project](basicsimages/new_project.png)
+
+* When you are [creating a project](create-project.md), click on "create a group" on the bottom right side of your screen
+
+![Create a group](basicsimages/create_group.png)
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
index e3963f66010..90d40cb6c51 100644
--- a/doc/gitlab-basics/create-project.md
+++ b/doc/gitlab-basics/create-project.md
@@ -1,14 +1,12 @@
# How to create a project in GitLab
-## Create a project
+To create a new project, sign in to [GitLab.com](https://gitlab.com).
-* Sign in to [GitLab.com](https://gitlab.com)
-
-* Go to your Dashboard and click on "new project" on the right side of your screen
+Go to your Dashboard and click on "new project" on the right side of your screen.
![Create a project](basicsimages/new_project.png)
-* Fill out the required information
+Fill out the required information:
1. Project path or the name of your project (you can't add spaces, so you can use hyphens or underscores)
diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md
index e1acc2bd65b..dcd3e6ffb31 100644
--- a/doc/gitlab-basics/create-your-ssh-keys.md
+++ b/doc/gitlab-basics/create-your-ssh-keys.md
@@ -4,34 +4,34 @@ You need to connect your computer to your GitLab account through SSH Keys. They
## Generate your SSH Key
-* Create an account on GitLab. Sign up and check your email for your confirmation link
+Create an account on GitLab. Sign up and check your email for your confirmation link.
-* After you confirm, go to [GitLab.com](https://about.gitlab.com/) and sign in to your account
+After you confirm, go to [GitLab.com](https://about.gitlab.com/) and sign in to your account.
## Add your SSH Key
-* At the top right corner, click on "profile settings"
+At the top right corner, click on "profile settings":
![profile settings](basicsimages/profile_settings.png)
-* On the left side menu click on "SSH Keys"
+On the left side menu click on "SSH Keys":
![SSH Keys](basicsimages/shh_keys.png)
-* Then click on the green button "Add SSH Key"
+Then click on the green button "Add SSH Key":
![Add SSH Key](basicsimages/add_sshkey.png)
-* There, you should paste the SSH Key that your commandline will generate for you. Below you'll find the steps to generate it
+There, you should paste the SSH Key that your command line will generate for you. Below you'll find the steps to generate it:
![Paste SSH Key](basicsimages/paste_sshkey.png)
-## To generate an SSH Key on your commandline
+## To generate an SSH Key on your command line
-* Go to your [commandline](start-using-git.md) and follow the [instructions](https://gitlab.com/help/ssh/README) to generate it
+Go to your [command line](start-using-git.md) and follow the [instructions](../ssh/README.md) to generate it.
-* Copy the SSH Key that your commandline created and paste it on the "Key" box on the GitLab page. The title will be added automatically
+Copy the SSH Key that your command line created and paste it on the "Key" box on the GitLab page. The title will be added automatically.
![Paste SSH Key](basicsimages/key.png)
-* Now, you'll be able to use Git over SSH, instead of Git over HTTP.
+Now, you'll be able to use Git over SSH, instead of Git over HTTP.
diff --git a/doc/gitlab-basics/fork-project.md b/doc/gitlab-basics/fork-project.md
new file mode 100644
index 00000000000..5173aae2c0f
--- /dev/null
+++ b/doc/gitlab-basics/fork-project.md
@@ -0,0 +1,19 @@
+# How to fork a project
+
+A fork is a copy of an original repository that you can put somewhere else
+or where you can experiment and apply changes that you can later decide if
+publishing or not, without affecting your original project.
+
+It takes just a few steps to fork a project in GitLab.
+
+Sign in to [gitlab.com](https://gitlab.com).
+
+Select a project on the right side of your screen:
+
+![Select a project](basicsimages/select_project.png)
+
+Click on the "fork" button on the right side of your screen:
+
+![Fork](basicsimages/fork.png)
+
+Click on the user or group to where you'd like to add the forked project.
diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md
index 21d93ed2e4d..5b1c6c1cd46 100644
--- a/doc/gitlab-basics/start-using-git.md
+++ b/doc/gitlab-basics/start-using-git.md
@@ -1,10 +1,10 @@
-# Start using Git on the commandline
+# Start using Git on the command line
-If you want to start using a Git and GitLab, make sure that you have created an account on [GitLab.com](https://about.gitlab.com/)
+If you want to start using a Git and GitLab, make sure that you have created an account on [GitLab.com](https://about.gitlab.com/).
## Open a shell
-* Depending on your operating system, find the shell of your preference. Here are some suggestions
+Depending on your operating system, find the shell of your preference. Here are some suggestions.
- [Terminal](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line) on Mac OSX
@@ -14,54 +14,48 @@ If you want to start using a Git and GitLab, make sure that you have created an
## Check if Git has already been installed
-* Git is usually preinstalled on Mac and Linux
-
-* Type the following command and then press enter
+Git is usually preinstalled on Mac and Linux.
+Type the following command and then press enter:
```
git --version
```
-* You should receive a message that will tell you which Git version you have in your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
+You should receive a message that will tell you which Git version you have in your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
-* If Git doesn't automatically download, there's an option on the website to [download manually](https://git-scm.com/downloads). Then follow the steps on the installation window
+If Git doesn't automatically download, there's an option on the website to [download manually](https://git-scm.com/downloads). Then follow the steps on the installation window.
-* After you finished installing, open a new shell and type "git --version" again to verify that it was correctly installed
+After you finished installing, open a new shell and type "git --version" again to verify that it was correctly installed.
## Add your Git username and set your email
-* It is important because every Git commit that you create will use this information
-
-* On your shell, type the following command to add your username
+It is important because every Git commit that you create will use this information.
+On your shell, type the following command to add your username:
```
git config --global user.name ADD YOUR USERNAME
```
-* Then verify that you have the correct username
-
+Then verify that you have the correct username:
```
git config --global user.name
```
-* To set your email address, type the following command
-
+To set your email address, type the following command:
```
git config --global user.email ADD YOUR EMAIL
```
-* To verify that you entered your email correctly, type
-
+To verify that you entered your email correctly, type:
```
git config --global user.email
```
-* You'll need to do this only once because you are using the "--global" option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the "--global" option when you’re in that project
+You'll need to do this only once because you are using the "--global" option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the "--global" option when you’re in that project.
## Check your information
-* To view the information that you entered, type
-
+To view the information that you entered, type:
```
git config --global --list
```
diff --git a/doc/install/installation.md b/doc/install/installation.md
index cf58abea4eb..8b918cba133 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -404,7 +404,7 @@ NOTE: Supply `SANITIZE=true` environment variable to `gitlab:check` to omit proj
Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created a default admin account for you. You can use it to log in:
root
- password
+ 5iveL!fe
**Important Note:** On login you'll be prompted to change the password.
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 7a3216dd2d2..1efc1f7bddf 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -57,6 +57,7 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim
- 16 cores supports up to 10,000 users
- 32 cores supports up to 20,000 users
- 64 cores supports up to 40,000 users
+- More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/)
### Memory
@@ -64,15 +65,17 @@ You need at least 2GB of addressable memory (RAM + swap) to install and use GitL
With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage.
- 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advise.
-- 1GB RAM + 1GB swap supports up to 100 users
-- **2GB RAM** is the **recommended** memory size and supports up to 500 users
-- 4GB RAM supports up to 2,000 users
-- 8GB RAM supports up to 5,000 users
-- 16GB RAM supports up to 10,000 users
-- 32GB RAM supports up to 20,000 users
-- 64GB RAM supports up to 40,000 users
-
-Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application.
+- 1GB RAM + 1GB swap supports up to 100 users but it will be slow
+- **2GB RAM** is the **recommended** memory size and supports up to 100 users
+- 4GB RAM supports up to 1,000 users
+- 8GB RAM supports up to 2,000 users
+- 16GB RAM supports up to 4,000 users
+- 32GB RAM supports up to 8,000 users
+- 64GB RAM supports up to 16,000 users
+- 128GB RAM supports up to 32,000 users
+- More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/)
+
+Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about many you need of those.
## Unicorn Workers
@@ -106,4 +109,8 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/))
- Safari 7+ (known problem: required fields in html5 do not work)
- Opera (Latest released version)
-- IE 10+ \ No newline at end of file
+- IE 10+
+
+### Common UI problems with IE
+
+If you experience UI issues with Internet Explorer, please make sure that you have the `Compatibility View` mode disabled. \ No newline at end of file
diff --git a/doc/profile/preferences.md b/doc/profile/preferences.md
index ce5f1936782..f17bbe8f2aa 100644
--- a/doc/profile/preferences.md
+++ b/doc/profile/preferences.md
@@ -30,3 +30,9 @@ will be. Setting it to **Starred Projects** will make that Dashboard view the
default when signing in or clicking the application logo in the upper left.
The default is **Your Projects**.
+
+### Default Project view
+
+It allows user to choose what content he or she want to see on project page.
+
+The default is **Readme**.
diff --git a/doc/profile/two_factor_authentication.md b/doc/profile/two_factor_authentication.md
index fb215c8b269..f60ce35d3e2 100644
--- a/doc/profile/two_factor_authentication.md
+++ b/doc/profile/two_factor_authentication.md
@@ -63,5 +63,10 @@ your phone's application or a recovery code to log in.
1. Go to **Account**.
1. Click **Disable Two-factor Authentication**.
+## Note to GitLab administrators
+
+You need to take special care to that 2FA keeps working after
+[restoring a GitLab backup](../raketasks/backup_restore.md).
+
[Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
[FreeOTP]: https://fedorahosted.org/freeotp/
diff --git a/doc/project_services/irker.md b/doc/project_services/irker.md
index 1dbca20baf9..25c0c3ad2a6 100644
--- a/doc/project_services/irker.md
+++ b/doc/project_services/irker.md
@@ -34,7 +34,7 @@ need to follow the firsts steps of the next section.
in the `Server host` field on the Web page
1. Enter the server port of `irkerd` (e.g. defaults to 6659) in the
`Server port` field on the Web page.
-1. Optional: if `Default irc uri` is set, it has to be in the format
+1. Optional: if `Default IRC URI` is set, it has to be in the format
`irc[s]://domain.name` and will be prepend to each and every channel provided
by the user which is not a full URI.
1. Specify the recipients (e.g. #channel1, user1, etc.)
diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index 4a2e2df357a..05324b33022 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -9,6 +9,13 @@ This archive will be saved in backup_path (see `config/gitlab.yml`).
The filename will be `[TIMESTAMP]_gitlab_backup.tar`. This timestamp can be used to restore an specific backup.
You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1.
+You need to keep a separate copy of `/etc/gitlab/gitlab-secrets.json`
+(for omnibus packages) or `/home/git/gitlab/.secret` (for installations
+from source). This file contains the database encryption key used
+for two-factor authentication. If you restore a GitLab backup without
+restoring the database encryption key, users who have two-factor
+authentication enabled will loose access to your GitLab server.
+
If you are interested in GitLab CI backup please follow to the [CI backup documentation](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/raketasks/backup_restore.md)*
```
@@ -160,15 +167,39 @@ gitlab_rails['backup_archive_permissions'] = 0644 # Makes the backup archives wo
## Storing configuration files
-Please be informed that a backup does not store your configuration files.
+Please be informed that a backup does not store your configuration
+files. One reason for this is that your database contains encrypted
+information for two-factor authentication. Storing encrypted
+information along with its key in the same place defeats the purpose
+of using encryption in the first place!
+
If you use an Omnibus package please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration).
If you have a cookbook installation there should be a copy of your configuration in Chef.
-If you have an installation from source, please consider backing up your `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
+If you have an installation from source, please consider backing up your `.secret` file, `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079).
+
+At the very **minimum** you should backup `/etc/gitlab/gitlab-secrets.json`
+(Omnibus) or `/home/git/gitlab/.secret` (source) to preserve your
+database encryption key.
## Restore a previously created backup
You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1.
+### Prerequisites
+
+You need to have a working GitLab installation before you can perform
+a restore. This is mainly because the system user performing the
+restore actions ('git') is usually not allowed to create or delete
+the SQL database it needs to import data into ('gitlabhq_production').
+All existing data will be either erased (SQL) or moved to a separate
+directory (repositories, uploads).
+
+If some or all of your GitLab users are using two-factor authentication
+(2FA) then you must also make sure to restore
+`/etc/gitlab/gitlab-secrets.json` (Omnibus) or `/home/git/gitlab/.secret`
+(installations from source). Note that you need to run `gitlab-ctl
+reconfigure` after changing `gitlab-secrets.json`.
+
### Installation from source
```
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index 3bc92187218..ca9696e957e 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -6,6 +6,7 @@ It starts 7 working days before the release.
The release manager doesn't have to perform all the work but must ensure someone is assigned.
The current release manager must schedule the appointment of the next release manager.
The new release manager should create overall issue to track the progress.
+The release manager should be the only person pushing/merging commits to the x-y-stable branches.
## Release Manager
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 5f44f9351dd..7cdcd11c04c 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -105,3 +105,6 @@ IdentityFile ~/my-ssh-key-directory/company-com-private-key-filename
Note in the gitlab.com example above a username was specified to override the default chosen by OpenSSH (your local username). This is only required if your local and remote usernames differ.
Due to the wide variety of SSH clients and their very large number of configuration options, further explanation of these topics is beyond the scope of this document.
+
+Public SSH keys need to be unique, as they will bind to your account. Your SSH key is the only identifier you'll
+have when pushing code via SSH. That's why it needs to uniquely map to a single user.
diff --git a/doc/update/6.x-or-7.x-to-7.12.md b/doc/update/6.x-or-7.x-to-7.13.md
index 5705fb360db..3e3ceb1194c 100644
--- a/doc/update/6.x-or-7.x-to-7.12.md
+++ b/doc/update/6.x-or-7.x-to-7.13.md
@@ -1,7 +1,7 @@
-# From 6.x or 7.x to 7.12
-*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.x-or-7.x-to-7.12.md) for the most up to date instructions.*
+# From 6.x or 7.x to 7.13
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.x-or-7.x-to-7.13.md) for the most up to date instructions.*
-This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.12.
+This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.13.
## Global issue numbers
@@ -71,7 +71,7 @@ sudo -u git -H git checkout -- db/schema.rb # local changes will be restored aut
For GitLab Community Edition:
```bash
-sudo -u git -H git checkout 7-12-stable
+sudo -u git -H git checkout 7-13-stable
```
OR
@@ -79,7 +79,7 @@ OR
For GitLab Enterprise Edition:
```bash
-sudo -u git -H git checkout 7-12-stable-ee
+sudo -u git -H git checkout 7-13-stable-ee
```
## 4. Install additional packages
@@ -162,11 +162,11 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
TIP: to see what changed in `gitlab.yml.example` in this release use next command:
```
-git diff 6-0-stable:config/gitlab.yml.example 7-12-stable:config/gitlab.yml.example
+git diff 6-0-stable:config/gitlab.yml.example 7-13-stable:config/gitlab.yml.example
```
-* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-12-stable/config/gitlab.yml.example but with your settings.
-* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-12-stable/config/unicorn.rb.example but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-13-stable/config/gitlab.yml.example but with your settings.
+* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-13-stable/config/unicorn.rb.example but with your settings.
* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.6.0/config.yml.example but with your settings.
* Copy rack attack middleware config
@@ -182,14 +182,14 @@ sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
### Change Nginx settings
-* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-12-stable/lib/support/nginx/gitlab but with your settings.
-* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-12-stable/lib/support/nginx/gitlab-ssl but with your settings.
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-13-stable/lib/support/nginx/gitlab but with your settings.
+* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-13-stable/lib/support/nginx/gitlab-ssl but with your settings.
* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section.
### Check the version of /usr/local/bin/git
If you installed Git from source into /usr/local/bin/git then please [check
-your version](7.11-to-7.12.md).
+your version](7.12-to-7.13.md).
## 9. Start application
diff --git a/doc/update/7.12-to-7.13.md b/doc/update/7.12-to-7.13.md
new file mode 100644
index 00000000000..57ebe3261b6
--- /dev/null
+++ b/doc/update/7.12-to-7.13.md
@@ -0,0 +1,129 @@
+# From 7.12 to 7.13
+
+### 0. Double-check your Git version
+
+**This notice applies only to /usr/local/bin/git**
+
+If you compiled Git from source on your GitLab server then please double-check
+that you are using a version that protects against CVE-2014-9390. For six
+months after this vulnerability became known the GitLab installation guide
+still contained instructions that would install the outdated, 'vulnerable' Git
+version 2.1.2.
+
+Run the following command to get your current Git version.
+
+```
+/usr/local/bin/git --version
+```
+
+If you see 'No such file or directory' then you did not install Git according
+to the outdated instructions from the GitLab installation guide and you can go
+to the next step 'Stop server' below.
+
+If you see a version string then it should be v1.8.5.6, v1.9.5, v2.0.5, v2.1.4,
+v2.2.1 or newer. You can use the [instructions in the GitLab source
+installation
+guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
+to install a newer version of Git.
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-13-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-13-stable-ee
+```
+
+### 4. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.6.3
+```
+
+### 5. Install libs, migrations, etc.
+
+```bash
+cd /home/git/gitlab
+
+# MySQL installations (note: the line below states '--without ... postgres')
+sudo -u git -H bundle install --without development test postgres --deployment
+
+# PostgreSQL installations (note: the line below states '--without ... mysql')
+sudo -u git -H bundle install --without development test mysql --deployment
+
+# Run database migrations
+sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production
+
+# Clean up assets and cache
+sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production
+
+# Update init.d script
+sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
+```
+
+### 6. Update config files
+
+#### New configuration options for `gitlab.yml`
+
+There are new configuration options available for [`gitlab.yml`](config/gitlab.yml.example). View them with the command below and apply them to your current `gitlab.yml`.
+
+```
+git diff origin/7-12-stable:config/gitlab.yml.example origin/7-13-stable:config/gitlab.yml.example
+``````
+
+### 7. Start application
+
+ sudo service gitlab start
+ sudo service nginx restart
+
+### 8. Check application status
+
+Check if GitLab and its environment are configured correctly:
+
+ sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production
+
+To make sure you didn't miss anything run a more thorough check with:
+
+ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production
+
+If all items are green, then congratulations, the upgrade is complete!
+
+## Things went south? Revert to previous version (7.12)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.11 to 7.12](7.11-to-7.12.md), except for the database migration
+(The backup is already migrated to the previous version)
+
+### 2. Restore from the backup:
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production
+```
+If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above.
diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md
index 2c43cf59c1f..26605c7c3a3 100644
--- a/doc/update/mysql_to_postgresql.md
+++ b/doc/update/mysql_to_postgresql.md
@@ -16,6 +16,7 @@ git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab
cd mysql-postgresql-converter
mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production -p
python db_converter.py gitlabhq_production.mysql gitlabhq_production.psql
+ed -s gitlabhq_production.psql < move_drop_indexes.ed
# Import the database dump as the application database user
sudo -u git psql -f gitlabhq_production.psql -d gitlabhq_production
@@ -56,13 +57,17 @@ sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.
# Convert gitlabhq_production.mysql
sudo -u git -H mkdir db
sudo -u git -H python mysql-postgresql-converter/db_converter.py gitlabhq_production.mysql db/database.sql
+sudo -u git -H ed -s db/database.sql < mysql-postgresql-converter/move_drop_indexes.ed
+
+# Compress database backup
+sudo -u git -H gzip db/database.sql
# Replace the MySQL dump in TIMESTAMP_gitlab_backup.tar.
# Warning: if you forget to replace TIMESTAMP below, tar will create a new file
# 'TIMESTAMP_gitlab_backup.tar' without giving an error.
-sudo -u git -H tar rf TIMESTAMP_gitlab_backup.tar db/database.sql
+sudo -u git -H tar rf TIMESTAMP_gitlab_backup.tar db/database.sql.gz
# Done! TIMESTAMP_gitlab_backup.tar can now be restored into a Postgres GitLab
# installation. Remember to recreate the indexes after the import.
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index f1959d30139..1f39d02bdf3 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -7,7 +7,7 @@
- [Groups](groups.md)
- [Keyboard shortcuts](shortcuts.md)
- [Labels](labels.md)
-- [Notifications](notifications.md)
+- [Notification emails](notifications.md)
- [Project Features](project_features.md)
- [Project forking workflow](forking_workflow.md)
- [Protected branches](protected_branches.md)
diff --git a/doc/workflow/labels.md b/doc/workflow/labels.md
index 085b7baf5ce..6e4840ca5ae 100644
--- a/doc/workflow/labels.md
+++ b/doc/workflow/labels.md
@@ -1,6 +1,6 @@
# Labels
-In GitLab, you can easily tag issues and merge requests. If you have permission level `Developer` or higher, you can manage labels. To create, edit or delete a label, go to a project and then to `Issues` and then `Labels`.
+In GitLab, you can easily tag issues and Merge Requests. If you have permission level `Developer` or higher, you can manage labels. To create, edit or delete a label, go to a project and then to `Issues` and then `Labels`.
Here you can create a new label.
@@ -14,3 +14,5 @@ If you want to change an existing label, press edit next to the listed label.
You will be presented with the same form as when creating a new label.
![edit label](labels/label3.png)
+
+You can add labels to Merge Requests when you create or edit them.
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index 17215de677e..2b5f06dd1fa 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -1,6 +1,6 @@
-# GitLab Notifications
+# GitLab Notification Emails
-GitLab has notifications system in place to notify a user of events important for the workflow.
+GitLab has a notification system in place to notify a user of events that are important for the workflow.
## Notification settings
@@ -67,5 +67,3 @@ Below is the table of events users can be notified of:
| Reopen merge request | Project members [1] | [1] higher than participating |
| Merge merge request | MR author [1], MR assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
| New comment | Mentioned users [1], users participating [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
-
-
diff --git a/docker/single/Dockerfile b/docker/Dockerfile
index a6cbf131237..05521af6963 100644
--- a/docker/single/Dockerfile
+++ b/docker/Dockerfile
@@ -7,7 +7,9 @@ RUN apt-get update -q \
ca-certificates \
openssh-server \
wget \
- apt-transport-https
+ apt-transport-https \
+ vim \
+ nano
# Download & Install GitLab
# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
@@ -23,12 +25,23 @@ RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
&& ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \
&& mkdir -p /var/run/sshd
+# Prepare default configuration
+RUN ( \
+ echo "" && \
+ echo "# Docker options" && \
+ echo "# Prevent Postgres from trying to allocate 25% of total memory" && \
+ echo "postgresql['shared_buffers'] = '1MB'" ) >> /etc/gitlab/gitlab.rb && \
+ mkdir -p /assets/ && \
+ cp /etc/gitlab/gitlab.rb /assets/gitlab.rb
+
# Expose web & ssh
-EXPOSE 80 22
+EXPOSE 443 80 22
+
+# Define data volumes
+VOLUME ["/etc/gitlab", "/var/opt/gitlab", "/var/log/gitlab"]
# Copy assets
COPY assets/wrapper /usr/local/bin/
-COPY assets/gitlab.rb /etc/gitlab/
# Wrapper to handle signal, trigger runit and reconfigure GitLab
CMD ["/usr/local/bin/wrapper"]
diff --git a/docker/README.md b/docker/README.md
index 9507aa6a63c..e4d56cdb336 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -1,9 +1,6 @@
# GitLab Docker images
-## What is GitLab?
-
-GitLab offers git repository management, code reviews, issue tracking, activity feeds, wikis. It has LDAP/AD integration, handles 25,000 users on a single server but can also run on a highly available active/active cluster.
-Learn more on [https://about.gitlab.com](https://about.gitlab.com)
+The GitLab docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ce/).
## After starting a container
@@ -11,152 +8,162 @@ After starting a container you can go to [http://localhost:8080/](http://localho
It might take a while before the docker container is responding to queries.
-You can check the status with something like `sudo docker logs -f 7c10172d7705`.
+You can check the status with something like `sudo docker logs -f gitlab`.
-You can login to the web interface with username `root` and password `password`.
+You can login to the web interface with username `root` and password `5iveL!fe`.
Next time, you can just use docker start and stop to run the container.
-## How to build the docker images
+## Run the image
-This guide will also let you know how to build docker images yourself.
-Please run all the commands from the GitLab repo root directory.
-People using boot2docker should run all the commands without sudo.
+Run the image:
+```bash
+sudo docker run --detach \
+ --publish 8443:443 --publish 8080:80 --publish 2222:22 \
+ --name gitlab \
+ --restart always \
+ --volume /srv/gitlab/config:/etc/gitlab \
+ --volume /srv/gitlab/logs:/var/log/gitlab \
+ --volume /srv/gitlab/data:/var/opt/gitlab \
+ gitlab/gitlab-ce:latest
+```
-## Choosing between the single and the app and data images
+This will download and start GitLab CE container and publish ports needed to access SSH, HTTP and HTTPS.
+All GitLab data will be stored as subdirectories of `/srv/gitlab/`.
+The container will automatically `restart` after system reboot.
-Normally docker uses a single image for one applications.
-But GitLab stores repositories and uploads in the filesystem.
-This means that upgrades of a single image are hard.
-That is why we recommend using separate app and data images.
-We'll first describe how to use a single image.
-After that we'll describe how to use the app and data images.
+After this you can login to the web interface as explained above in 'After starting a container'.
-## Single image
+## Where is the data stored?
-Get a published image from Dockerhub:
+The GitLab container uses host mounted volumes to store persistent data:
+- `/srv/gitlab/data` mounted as `/var/opt/gitlab` in the container is used for storing *application data*
+- `/srv/gitlab/logs` mounted as `/var/log/gitlab` in the container is used for storing *logs*
+- `/srv/gitlab/config` mounted as `/etc/gitlab` in the container is used for storing *configuration*
-```bash
-sudo docker pull sytse/gitlab-ce:7.10.1
-```
+You can fine tune these directories to meet your requirements.
-Run the image:
+### Configure GitLab
+
+This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`.
+To access GitLab configuration, you can start an bash in a new the context of running container, you will be able to browse all directories and use your favorite text editor:
```bash
-sudo docker run --detach --publish 8080:80 --publish 2222:22 sytse/gitlab-ce:7.10.1
+sudo docker exec -it gitlab /bin/bash
```
-After this you can login to the web interface as explained above in 'After starting a container'.
-
-Build the image:
-
+You can also edit just `/etc/gitlab/gitlab.rb`:
```bash
-sudo docker build --tag sytse/gitlab-ce:7.10.1 docker/single/
+sudo docker exec -it gitlab vi /etc/gitlab/gitlab.rb
```
-Publish the image to Dockerhub:
+**You should set the `external_url` to point to a valid URL.**
-```bash
-sudo docker push sytse/gitlab-ce
-```
+**You may also be interesting in [Enabling HTTPS](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/nginx.md#enable-https).**
+
+**To receive e-mails from GitLab you have to configure the [SMTP settings](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/smtp.md),
+because Docker image doesn't have a SMTP server.**
-Diagnosing commands:
+**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab:
```bash
-sudo docker run -i -t sytse/gitlab-ce:7.10.1
-sudo docker run -ti -e TERM=linux --name gitlab-ce-troubleshoot --publish 8080:80 --publish 2222:22 sytse/gitlab-ce:7.10.1 bash /usr/local/bin/wrapper
+sudo docker restart gitlab
```
-## App and data images
+For more options for configuring the container please check [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration).
-### Get published images from Dockerhub
+## Diagnose potential problems
+Read container logs:
```bash
-sudo docker pull sytse/gitlab-data
-sudo docker pull sytse/gitlab-app:7.10.1
+sudo docker logs gitlab
```
-### Run the images
-
+Enter running container:
```bash
-sudo docker run --name gitlab-data sytse/gitlab-data /bin/true
-sudo docker run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data sytse/gitlab-app:7.10.1
+sudo docker exec -it gitlab /bin/bash
```
-After this you can login to the web interface as explained above in 'After starting a container'.
-
-### Build images
+From within container you can administrer GitLab container as you would normally administer Omnibus installation: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md.
-Build your own based on the Omnibus packages with the following commands.
+### Upgrade GitLab to newer version
+To upgrade GitLab to new version you have to do:
+1. pull new image,
```bash
-sudo docker build --tag gitlab-data docker/data/
-sudo docker build --tag gitlab-app:7.10.1 docker/app/
+sudo docker stop gitlab
```
-After this run the images:
-
+1. stop running container,
```bash
-sudo docker run --name gitlab-data gitlab-data /bin/true
-sudo docker run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data gitlab-app:7.10.1
+sudo docker rm gitlab
```
-We assume using a data volume container, this will simplify migrations and backups.
-This empty container will exist to persist as volumes the 3 directories used by GitLab, so remember not to delete it.
-
-The directories on data container are:
-
-- `/var/opt/gitlab` for application data
-- `/var/log/gitlab` for logs
-- `/etc/gitlab` for configuration
-
-### Configure GitLab
-
-This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`.
-
-To access GitLab configuration, you can start an interactive command line in a new container using the shared data volume container, you will be able to browse the 3 directories and use your favorite text editor:
+1. remove existing container,
+```bash
+sudo docker pull gitlab/gitlab-ce:latest
+```
+1. create the container once again with previously specified options.
```bash
-sudo docker run -ti -e TERM=linux --rm --volumes-from gitlab-data ubuntu
-vi /etc/gitlab/gitlab.rb
+sudo docker run --detach \
+ --publish 8443:443 --publish 8080:80 --publish 2222:22 \
+ --name gitlab \
+ --restart always \
+ --volume /srv/gitlab/config:/etc/gitlab \
+ --volume /srv/gitlab/logs:/var/log/gitlab \
+ --volume /srv/gitlab/data:/var/opt/gitlab \
+ gitlab/gitlab-ce:latest
```
-**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab.
+On the first run GitLab will reconfigure and update itself.
-You can find all available options in [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration).
+### Run GitLab CE on public IP address
-### Upgrade GitLab with app and data images
+You can make Docker to use your IP address and forward all traffic to the GitLab CE container.
+You can do that by modifying the `--publish` ([Binding container ports to the host](https://docs.docker.com/articles/networking/#binding-ports)):
-To upgrade GitLab to new versions, stop running container, create new docker image and container from that image.
+> --publish=[] : Publish a container᾿s port or a range of ports to the host format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
-It Assumes that you're upgrading from 7.8.1 to 7.10.1 and you're in the updated GitLab repo root directory:
+To expose GitLab CE on IP 1.1.1.1:
```bash
-sudo docker stop gitlab-app
-sudo docker rm gitlab-app
-sudo docker build --tag gitlab-app:7.10.1 docker/app/
-sudo docker run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data gitlab-app:7.10.1
+sudo docker run --detach \
+ --publish 1.1.1.1:443:443 --publish 1.1.1.1:80:80 --publish 1.1.1.1:22:22 \
+ --name gitlab \
+ --restart always \
+ --volume /srv/gitlab/config:/etc/gitlab \
+ --volume /srv/gitlab/logs:/var/log/gitlab \
+ --volume /srv/gitlab/data:/var/opt/gitlab \
+ gitlab/gitlab-ce:latest
```
-On the first run GitLab will reconfigure and update itself. If everything runs OK don't forget to cleanup the app image:
+You can then access GitLab instance at http://1.1.1.1/ and https://1.1.1.1/.
+
+### Build the image
+
+This guide will also let you know how to build docker image yourself.
+Please run the command from the GitLab repo root directory.
+People using boot2docker should run all the commands without sudo.
```bash
-sudo docker rmi gitlab-app:7.8.1
+sudo docker build --tag gitlab/gitlab-ce:latest docker/
```
-### Publish images to Dockerhub
+### Publish the image to Dockerhub
- Ensure the containers are running
- Login to Dockerhub with `sudo docker login`
-- Run the following (replace '7.10.1' with the version you're using and 'Sytse Sijbrandij' with your name):
```bash
-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
+sudo docker login
+sudo docker push gitlab/gitlab-ce:latest
```
## Troubleshooting
Please see the [troubleshooting](troubleshooting.md) file in this directory.
+
+Note: We use `fig.yml` to have compatibility with fig and because docker-compose also supports it.
+
+Our docker image runs chef at every start to generate GitLab configuration.
diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile
deleted file mode 100644
index fe3f7f0bcd2..00000000000
--- a/docker/app/Dockerfile
+++ /dev/null
@@ -1,32 +0,0 @@
-FROM ubuntu:14.04
-
-# Install required packages
-RUN apt-get update -q \
- && DEBIAN_FRONTEND=noninteractive apt-get install -qy --no-install-recommends \
- ca-certificates \
- openssh-server \
- wget \
- apt-transport-https
-
-# Download & Install GitLab
-# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
-RUN echo "deb https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.list.d/gitlab_gitlab-ce.list
-RUN wget -q -O - https://packages.gitlab.com/gpg.key | apt-key add -
-RUN apt-get update && apt-get install -yq --no-install-recommends gitlab-ce
-
-# Manage SSHD through runit
-RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
- && mkfifo /opt/gitlab/sv/sshd/supervise/ok \
- && printf "#!/bin/sh\nexec 2>&1\numask 077\nexec /usr/sbin/sshd -D" > /opt/gitlab/sv/sshd/run \
- && chmod a+x /opt/gitlab/sv/sshd/run \
- && ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \
- && mkdir -p /var/run/sshd
-
-# Expose web & ssh
-EXPOSE 80 22
-
-# Copy assets
-COPY assets/wrapper /usr/local/bin/
-
-# Wrapper to handle signal, trigger runit and reconfigure GitLab
-CMD ["/usr/local/bin/wrapper"] \ No newline at end of file
diff --git a/docker/app/assets/wrapper b/docker/app/assets/wrapper
deleted file mode 100755
index 9e6e7a05903..00000000000
--- a/docker/app/assets/wrapper
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/bin/bash
-
-function sigterm_handler() {
- echo "SIGTERM signal received, try to gracefully shutdown all services..."
- gitlab-ctl stop
-}
-
-trap "sigterm_handler; exit" TERM
-
-function entrypoint() {
- # Default is to run runit and reconfigure GitLab
- gitlab-ctl reconfigure &
- /opt/gitlab/embedded/bin/runsvdir-start &
- wait
-}
-
-entrypoint
diff --git a/docker/single/assets/wrapper b/docker/assets/wrapper
index 966b2cab4a1..8bc8370fbc9 100755
--- a/docker/single/assets/wrapper
+++ b/docker/assets/wrapper
@@ -13,4 +13,9 @@ function entrypoint() {
gitlab-ctl tail # tail all logs
}
+if [[ ! -e /etc/gitlab/gitlab.rb ]]; then
+ cp /assets/gitlab.rb /etc/gitlab/gitlab.rb
+ chmod 0600 /etc/gitlab/gitlab.rb
+fi
+
entrypoint
diff --git a/docker/data/Dockerfile b/docker/data/Dockerfile
deleted file mode 100644
index ea0175c4aa2..00000000000
--- a/docker/data/Dockerfile
+++ /dev/null
@@ -1,8 +0,0 @@
-FROM busybox
-
-# Declare volumes
-VOLUME ["/var/opt/gitlab", "/var/log/gitlab", "/etc/gitlab"]
-# Copy assets
-COPY assets/gitlab.rb /etc/gitlab/
-
-CMD /bin/sh
diff --git a/docker/data/assets/gitlab.rb b/docker/data/assets/gitlab.rb
deleted file mode 100644
index 7fddf309c01..00000000000
--- a/docker/data/assets/gitlab.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# External URL should be your Docker instance.
-# By default, this example is the "standard" boot2docker IP.
-# Always use port 80 here to force the internal nginx to bind port 80,
-# even if you intend to use another port in Docker.
-external_url "http://192.168.59.103/"
-
-# Prevent Postgres from trying to allocate 25% of total memory
-postgresql['shared_buffers'] = '1MB'
-
-# Configure GitLab to redirect PostgreSQL logs to the data volume
-postgresql['log_directory'] = '/var/log/gitlab/postgresql'
-
-# Some configuration of GitLab
-# You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration
-gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
-gitlab_rails['gitlab_support_email'] = 'support@example.com'
-gitlab_rails['time_zone'] = 'Europe/Paris'
-
-# SMTP settings
-# You must use an external server, the Docker container does not install an SMTP server
-gitlab_rails['smtp_enable'] = true
-gitlab_rails['smtp_address'] = "smtp.example.com"
-gitlab_rails['smtp_port'] = 587
-gitlab_rails['smtp_user_name'] = "user"
-gitlab_rails['smtp_password'] = "password"
-gitlab_rails['smtp_domain'] = "example.com"
-gitlab_rails['smtp_authentication'] = "plain"
-gitlab_rails['smtp_enable_starttls_auto'] = true
-
-# Enable LDAP authentication
-# gitlab_rails['ldap_enabled'] = true
-# gitlab_rails['ldap_host'] = 'ldap.example.com'
-# gitlab_rails['ldap_port'] = 389
-# gitlab_rails['ldap_method'] = 'plain' # 'ssl' or 'plain'
-# gitlab_rails['ldap_allow_username_or_email_login'] = false
-# gitlab_rails['ldap_uid'] = 'uid'
-# gitlab_rails['ldap_base'] = 'ou=users,dc=example,dc=com'
diff --git a/docker/fig.yml b/docker/fig.yml
new file mode 100644
index 00000000000..989551cbfe2
--- /dev/null
+++ b/docker/fig.yml
@@ -0,0 +1,2 @@
+app:
+ build: .
diff --git a/docker/marathon.json b/docker/marathon.json
new file mode 100644
index 00000000000..9b2091a8c22
--- /dev/null
+++ b/docker/marathon.json
@@ -0,0 +1,31 @@
+{
+ "id": "/gitlab",
+ "ports": [0,0],
+ "cpus": 2,
+ "mem": 2048.0,
+ "disk": 10240.0,
+ "container": {
+ "type": "DOCKER",
+ "docker": {
+ "network": "HOST",
+ "image": "gitlab/gitlab-ce:latest"
+ },
+ "volumes": [
+ {
+ "containerPath": "/etc/gitlab",
+ "hostPath": "/var/data/etc/gitlab",
+ "mode": "RW"
+ },
+ {
+ "containerPath": "/var/opt/gitlab",
+ "hostPath": "/var/data/opt/gitlab",
+ "mode": "RW"
+ },
+ {
+ "containerPath": "/var/log/gitlab",
+ "hostPath": "/var/data/log/gitlab",
+ "mode": "RW"
+ }
+ ]
+ }
+} \ No newline at end of file
diff --git a/docker/single/assets/gitlab.rb b/docker/single/assets/gitlab.rb
deleted file mode 100644
index ef84e7832d6..00000000000
--- a/docker/single/assets/gitlab.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# External URL should be your Docker instance.
-# By default, GitLab will use the Docker container hostname.
-# Always use port 80 here to force the internal nginx to bind port 80,
-# even if you intend to use another port in Docker.
-# external_url "http://192.168.59.103/"
-
-# Prevent Postgres from trying to allocate 25% of total memory
-postgresql['shared_buffers'] = '1MB'
-
-# Configure GitLab to redirect PostgreSQL logs to the data volume
-postgresql['log_directory'] = '/var/log/gitlab/postgresql'
-
-# Some configuration of GitLab
-# You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration
-gitlab_rails['gitlab_email_from'] = 'gitlab@example.com'
-gitlab_rails['gitlab_support_email'] = 'support@example.com'
-gitlab_rails['time_zone'] = 'Europe/Paris'
-
-# SMTP settings
-# You must use an external server, the Docker container does not install an SMTP server
-gitlab_rails['smtp_enable'] = true
-gitlab_rails['smtp_address'] = "smtp.example.com"
-gitlab_rails['smtp_port'] = 587
-gitlab_rails['smtp_user_name'] = "user"
-gitlab_rails['smtp_password'] = "password"
-gitlab_rails['smtp_domain'] = "example.com"
-gitlab_rails['smtp_authentication'] = "plain"
-gitlab_rails['smtp_enable_starttls_auto'] = true
-
-# Enable LDAP authentication
-# gitlab_rails['ldap_enabled'] = true
-# gitlab_rails['ldap_host'] = 'ldap.example.com'
-# gitlab_rails['ldap_port'] = 389
-# gitlab_rails['ldap_method'] = 'plain' # 'ssl' or 'plain'
-# gitlab_rails['ldap_allow_username_or_email_login'] = false
-# gitlab_rails['ldap_uid'] = 'uid'
-# gitlab_rails['ldap_base'] = 'ou=users,dc=example,dc=com'
diff --git a/docker/single/marathon.json b/docker/single/marathon.json
deleted file mode 100644
index d23c2b84e0e..00000000000
--- a/docker/single/marathon.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "id": "/gitlab",
- "ports": [0,0],
- "cpus": 2,
- "mem": 2048.0,
- "disk": 10240.0,
- "container": {
- "type": "DOCKER",
- "docker": {
- "network": "HOST",
- "image": "sytse/gitlab-ce:7.10.1"
- }
- }
-} \ No newline at end of file
diff --git a/docker/troubleshooting.md b/docker/troubleshooting.md
index 5827f2185db..63482547daa 100644
--- a/docker/troubleshooting.md
+++ b/docker/troubleshooting.md
@@ -9,24 +9,19 @@ postgresql['log_directory'] = '/var/log/gitlab/postgresql'
# Commands
```bash
-sudo docker build --tag gitlab_image docker/
+sudo docker build --tag gitlab/gitlab-ce:latest docker/
-sudo docker rm -f gitlab_app
-sudo docker rm -f gitlab_data
+sudo docker rm -f gitlab
-sudo docker run --name gitlab_data gitlab_image /bin/true
+sudo docker exec -it gitlab vim /etc/gitlab/gitlab.rb
-sudo docker run -ti --rm --volumes-from gitlab_data ubuntu apt-get update && sudo apt-get install -y vim && sudo vim /etc/gitlab/gitlab.rb
+sudo docker exec gitlab tail -f /var/log/gitlab/reconfigure.log
-sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image
+sudo docker exec gitlab tail -f /var/log/gitlab/postgresql/current
-sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log
+sudo docker exec gitlab cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
-sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/postgresql/current
-
-sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
-
-sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab.rb
+sudo docker exec gitlab cat /etc/gitlab/gitlab.rb
```
# Interactively
@@ -37,7 +32,16 @@ sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab
# - we run interactively (-t -i)
# - we define TERM=linux because it allows to use arrow keys in vi (!!!)
# - we choose another startup command (bash)
-sudo docker run -ti -e TERM=linux --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image bash
+sudo docker run --ti \
+ -e TERM=linux
+ --publish 80443:443 --publish 8080:80 --publish 2222:22 \
+ --name gitlab \
+ --restart always \
+ --volume /srv/gitlab/config:/etc/gitlab \
+ --volume /srv/gitlab/logs:/var/log/gitlab \
+ --volume /srv/gitlab/data:/var/opt/gitlab \
+ gitlab/gitlab-ce:latest \
+ bash
# Configure GitLab to redirect PostgreSQL logs
echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb
@@ -64,10 +68,17 @@ free -m
# Cleanup
-Remove ALL docker containers and images (also non GitLab ones):
+Remove ALL docker containers and images (also non GitLab ones).
+**Be careful, because the `-v` also removes volumes attached to the images.**
-```
-docker rm $(docker ps -a -q)
+```bash
+# Remove all containers with attached volumes
+docker rm -v $(docker ps -a -q)
+
+# Remove all images
docker rmi $(docker images -q)
+
+# Remove GitLab persistent data
+rm -rf /srv/gitlab
```
diff --git a/features/groups.feature b/features/groups.feature
index 415e43d6ae7..299e846edb0 100644
--- a/features/groups.feature
+++ b/features/groups.feature
@@ -4,6 +4,10 @@ Feature: Groups
And "John Doe" is owner of group "Owned"
And "John Doe" is guest of group "Guest"
+ Scenario: I should have back to group button
+ When I visit group "Owned" page
+ Then I should see back to dashboard button
+
@javascript
Scenario: I should see group "Owned" dashboard list
When I visit group "Owned" page
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index a15298fc452..28cc43ef710 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -189,6 +189,7 @@ Feature: Project Issues
Given I logout
Given public project "Community"
When I visit project "Community" page
+ And I visit project "Community" issues page
And I click link "New Issue"
And I should not see assignee field
And I should not see milestone field
diff --git a/features/project/issues/milestones.feature b/features/project/issues/milestones.feature
index 9ac65b1257c..bfbaaec5a35 100644
--- a/features/project/issues/milestones.feature
+++ b/features/project/issues/milestones.feature
@@ -17,6 +17,10 @@ Feature: Project Issues Milestones
And I submit new milestone "v2.3"
Then I should see milestone "v2.3"
+ Scenario: I delete new milestone
+ Given I click link to remove milestone "v2.2"
+ And I should see no milestones
+
@javascript
Scenario: Listing closed issues
Given the milestone has open and closed issues
diff --git a/features/project/project.feature b/features/project/project.feature
index 56ae5c78d01..089ffcba14a 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -18,9 +18,22 @@ Feature: Project
Then I should see the default project avatar
And I should not see the "Remove avatar" button
+ Scenario: I should have back to group button
+ And project "Shop" belongs to group
+ And I visit project "Shop" page
+ Then I should see back to group button
+
+ Scenario: I should have back to group button
+ And I visit project "Shop" page
+ Then I should see back to dashboard button
+
+ Scenario: I should have readme on page
+ And I visit project "Shop" page
+ Then I should see project "Shop" README
+
@javascript
Scenario: I should see project activity
- When I visit project "Shop" page
+ When I visit project "Shop" activity page
Then I should see project "Shop" activity feed
Scenario: I visit edit project
@@ -38,24 +51,12 @@ Feature: Project
And change project path settings
Then I should see project with new path settings
- Scenario: I should see project readme and version
- When I visit project "Shop" page
- And I should see project "Shop" version
-
Scenario: I should change project default branch
When I visit edit project "Shop" page
And change project default branch
And I save project
Then I should see project default branch changed
- @javascript
- Scenario: I should have default tab per my preference
- And I own project "Forum"
- When I select project "Forum" README tab
- Then I should see project "Forum" README
- And I visit project "Shop" page
- Then I should see project "Shop" README
-
Scenario: I tag a project
When I visit edit project "Shop" page
Then I should see project settings
diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature
index cfb68bf1f50..0f71c32380b 100644
--- a/features/project/shortcuts.feature
+++ b/features/project/shortcuts.feature
@@ -3,7 +3,7 @@ Feature: Project Shortcuts
Background:
Given I sign in as a user
And I own a project
- And I visit my project's home page
+ And I visit my project's commits page
@javascript
Scenario: Navigate to files tab
@@ -12,6 +12,7 @@ Feature: Project Shortcuts
@javascript
Scenario: Navigate to commits tab
+ Given I visit my project's files page
Given I press "g" and "c"
Then the active main tab should be Commits
@@ -46,7 +47,11 @@ Feature: Project Shortcuts
Then the active main tab should be Wiki
@javascript
- Scenario: Navigate to project feed
- Given I visit my project's files page
+ Scenario: Navigate to project home
Given I press "g" and "p"
Then the active main tab should be Home
+
+ @javascript
+ Scenario: Navigate to project feed
+ Given I press "g" and "e"
+ Then the active main tab should be Activity
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index 2812c5473e9..46e1f4d0990 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -5,6 +5,10 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
include SharedUser
include Select2Helper
+ step 'I should see back to dashboard button' do
+ expect(page).to have_content 'Back to Dashboard'
+ end
+
step 'gitlab user "Mike"' do
create(:user, name: "Mike")
end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 9ace6436b15..239392eab96 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -194,6 +194,11 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
end
+ When "I visit project \"Community\" issues page" do
+ project = Project.find_by(name: 'Community')
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
When "I visit empty project's issues page" do
project = Project.find_by(name: 'Empty Project')
visit namespace_project_issues_path(project.namespace, project)
diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb
index 708c5243947..61e62c2adbd 100644
--- a/features/steps/project/issues/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -56,4 +56,12 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
step 'I should see 3 issues' do
expect(page).to have_selector('#tab-issues li.issue-row', count: 4)
end
+
+ step 'I click link to remove milestone "v2.2"' do
+ click_link 'Remove'
+ end
+
+ step 'I should see no milestones' do
+ expect(page).to have_content('No milestones to show')
+ end
end
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index b4a0ba1e27f..0404fd5e594 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -86,13 +86,15 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see project "Forum" README' do
- expect(page).to have_link 'README.md'
- expect(page).to have_content 'Sample repo for testing gitlab features'
+ page.within('#README') do
+ expect(page).to have_content 'Sample repo for testing gitlab features'
+ end
end
step 'I should see project "Shop" README' do
- expect(page).to have_link 'README.md'
- expect(page).to have_content 'testme'
+ page.within('#README') do
+ expect(page).to have_content 'testme'
+ end
end
step 'I add project tags' do
@@ -114,4 +116,18 @@ class Spinach::Features::Project < Spinach::FeatureSteps
step 'I should not see "Snippets" button' do
expect(page).not_to have_link 'Snippets'
end
+
+ step 'project "Shop" belongs to group' do
+ group = create(:group)
+ @project.namespace = group
+ @project.save!
+ end
+
+ step 'I should see back to dashboard button' do
+ expect(page).to have_content 'Back to Dashboard'
+ end
+
+ step 'I should see back to group button' do
+ expect(page).to have_content 'Back to Group'
+ end
end
diff --git a/features/steps/project/project_shortcuts.rb b/features/steps/project/project_shortcuts.rb
index a10e7bf78ee..49e9c5520bb 100644
--- a/features/steps/project/project_shortcuts.rb
+++ b/features/steps/project/project_shortcuts.rb
@@ -33,4 +33,9 @@ class Spinach::Features::ProjectShortcuts < Spinach::FeatureSteps
find('body').native.send_key('g')
find('body').native.send_key('w')
end
+
+ step 'I press "g" and "e"' do
+ find('body').native.send_key('g')
+ find('body').native.send_key('e')
+ end
end
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 398c9bf5756..95879b9544d 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -187,7 +187,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I click on "add a file" link' do
- click_link 'add a file'
+ click_link 'adding README'
# Remove pre-receive hook so we can push without auth
FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive'))
diff --git a/features/steps/project/star.rb b/features/steps/project/star.rb
index 8b50bfcef04..bd2e0619cdd 100644
--- a/features/steps/project/star.rb
+++ b/features/steps/project/star.rb
@@ -5,7 +5,7 @@ class Spinach::Features::ProjectStar < Spinach::FeatureSteps
include SharedUser
step "The project has no stars" do
- expect(page).not_to have_content '.star-buttons'
+ expect(page).not_to have_content '.toggle-star'
end
step "The project has 0 stars" do
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index fe651e81dac..88a98a37807 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -251,6 +251,10 @@ module SharedPaths
visit namespace_project_path(project.namespace, project)
end
+ step 'I visit project "Shop" activity page' do
+ visit activity_namespace_project_path(project.namespace, project)
+ end
+
step 'I visit project "Forked Shop" merge requests page' do
visit namespace_project_merge_requests_path(@forked_project.namespace, @forked_project)
end
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index 3b94b7d8621..c67e5e4a06a 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -49,4 +49,8 @@ module SharedProjectTab
expect(page).to have_content('Back to project')
end
end
+
+ step 'the active main tab should be Activity' do
+ ensure_active_main_tab('Activity')
+ end
end
diff --git a/lib/api/api.rb b/lib/api/api.rb
index d2a35c78fc1..eebd44ea5b6 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -49,5 +49,6 @@ module API
mount Namespaces
mount Branches
mount Labels
+ mount Settings
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 14a8f929d76..ecf1412dee5 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -171,6 +171,7 @@ module API
expose :source_project_id, :target_project_id
expose :label_names, as: :labels
expose :description
+ expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone
end
@@ -277,5 +278,27 @@ module API
class BroadcastMessage < Grape::Entity
expose :message, :starts_at, :ends_at, :color, :font
end
+
+ class ApplicationSetting < Grape::Entity
+ expose :id
+ expose :default_projects_limit
+ expose :signup_enabled
+ expose :signin_enabled
+ expose :gravatar_enabled
+ expose :sign_in_text
+ expose :created_at
+ expose :updated_at
+ expose :home_page_url
+ expose :default_branch_protection
+ expose :twitter_sharing_enabled
+ expose :restricted_visibility_levels
+ expose :max_attachment_size
+ expose :session_expire_delay
+ expose :default_project_visibility
+ expose :default_snippet_visibility
+ expose :restricted_signup_domains
+ expose :user_oauth_applications
+ expose :after_sign_out_path
+ end
end
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index e88b6e31775..024aeec2e14 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -74,9 +74,9 @@ module API
# POST /groups/:id/projects/:project_id
post ":id/projects/:project_id" do
authenticated_as_admin!
- group = Group.find(params[:id])
+ group = Group.find_by(id: params[:id])
project = Project.find(params[:project_id])
- result = ::Projects::TransferService.new(project, current_user, namespace_id: group.id).execute
+ result = ::Projects::TransferService.new(project, current_user).execute(group)
if result
present group
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
new file mode 100644
index 00000000000..c885fcd7ea3
--- /dev/null
+++ b/lib/api/settings.rb
@@ -0,0 +1,35 @@
+module API
+ class Settings < Grape::API
+ before { authenticated_as_admin! }
+
+ helpers do
+ def current_settings
+ @current_setting ||=
+ (ApplicationSetting.current || ApplicationSetting.create_from_defaults)
+ end
+ end
+
+ # Get current applicaiton settings
+ #
+ # Example Request:
+ # GET /application/settings
+ get "application/settings" do
+ present current_settings, with: Entities::ApplicationSetting
+ end
+
+ # Modify applicaiton settings
+ #
+ # Example Request:
+ # PUT /application/settings
+ put "application/settings" do
+ attributes = current_settings.attributes.keys - ["id"]
+ attrs = attributes_for_keys(attributes)
+
+ if current_settings.update_attributes(attrs)
+ present current_settings, with: Entities::ApplicationSetting
+ else
+ render_validation_error!(current_settings)
+ end
+ end
+ end
+end
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index 9ab6aca276d..c5a5396cbbf 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -18,23 +18,31 @@ module Backup
when "postgresql" then
$progress.print "Dumping PostgreSQL database #{config['database']} ... "
pg_env
- system('pg_dump', config['database'], out: db_file_name)
+ # Pass '--clean' to include 'DROP TABLE' statements in the DB dump.
+ system('pg_dump', '--clean', config['database'], out: db_file_name)
end
report_success(success)
abort 'Backup failed' unless success
+
+ $progress.print 'Compressing database ... '
+ FileUtils.rm_f db_file_name_gz
+ success = system('gzip', db_file_name)
+ report_success(success)
+ abort 'Backup failed: compress error' unless success
end
def restore
+ $progress.print 'Decompressing database ... '
+ success = system('gzip', '-d', db_file_name_gz)
+ report_success(success)
+ abort 'Restore failed: decompress error' unless success
+
success = case config["adapter"]
when /^mysql/ then
$progress.print "Restoring MySQL database #{config['database']} ... "
system('mysql', *mysql_args, config['database'], in: db_file_name)
when "postgresql" then
$progress.print "Restoring PostgreSQL database #{config['database']} ... "
- # Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE
- # statements like MySQL.
- Rake::Task["gitlab:db:drop_all_tables"].invoke
- Rake::Task["gitlab:db:drop_all_postgres_sequences"].invoke
pg_env
system('psql', config['database'], '-f', db_file_name)
end
@@ -48,6 +56,10 @@ module Backup
File.join(db_dir, 'database.sql')
end
+ def db_file_name_gz
+ File.join(db_dir, 'database.sql.gz')
+ end
+
def mysql_args
args = {
'host' => '--host',
diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb
index 70bfe059776..03c410726a5 100644
--- a/lib/gitlab/google_code_import/importer.rb
+++ b/lib/gitlab/google_code_import/importer.rb
@@ -327,7 +327,7 @@ module Gitlab
link = "https://storage.googleapis.com/google-code-attachments/#{@repo.name}/issue-#{issue_id}/comment-#{comment_id}/#{filename}"
text = "[#{filename}](#{link})"
- text = "!#{text}" if filename =~ /\.(png|jpg|jpeg|gif|bmp|tiff)\z/
+ text = "!#{text}" if filename =~ /\.(png|jpg|jpeg|gif|bmp|tiff)\z/i
text
end.compact
end
diff --git a/lib/gitlab/markup_helper.rb b/lib/gitlab/markup_helper.rb
index f99be969d3e..b1991e2e285 100644
--- a/lib/gitlab/markup_helper.rb
+++ b/lib/gitlab/markup_helper.rb
@@ -33,6 +33,16 @@ module Gitlab
filename.downcase.end_with?(*%w(.adoc .ad .asciidoc))
end
+ # Public: Determines if the given filename is plain text.
+ #
+ # filename - Filename string to check
+ #
+ # Returns boolean
+ def plain?(filename)
+ filename.downcase.end_with?('.txt') ||
+ filename.downcase == 'readme'
+ end
+
def previewable?(filename)
markup?(filename)
end
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 582fc759efd..335dc44be19 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -47,6 +47,10 @@ module Gitlab
def valid_level?(level)
options.has_value?(level)
end
+
+ def allowed_fork_levels(origin_level)
+ [PRIVATE, INTERNAL, PUBLIC].select{ |level| level <= origin_level }
+ end
end
def private?
diff --git a/lib/repository_cache.rb b/lib/repository_cache.rb
index fa016a170cd..8ddc3511293 100644
--- a/lib/repository_cache.rb
+++ b/lib/repository_cache.rb
@@ -18,4 +18,12 @@ class RepositoryCache
def fetch(key, &block)
backend.fetch(cache_key(key), &block)
end
+
+ def exist?(key)
+ backend.exist?(cache_key(key))
+ end
+
+ def read(key)
+ backend.read(cache_key(key))
+ end
end
diff --git a/lib/support/init.d/gitlab b/lib/support/init.d/gitlab
index 946902e2f6d..a3455728a94 100755
--- a/lib/support/init.d/gitlab
+++ b/lib/support/init.d/gitlab
@@ -41,7 +41,7 @@ shell_path="/bin/bash"
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
+if [ `whoami` != "$app_user" ]; then
eval su - "$app_user" -s $shell_path -c $(echo \")$0 "$@"$(echo \"); exit;
fi
diff --git a/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake b/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake
deleted file mode 100644
index e9cf0a9b5e8..00000000000
--- a/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace :gitlab do
- namespace :db do
- task drop_all_postgres_sequences: :environment do
- connection = ActiveRecord::Base.connection
- connection.execute("SELECT c.relname FROM pg_class c WHERE c.relkind = 'S';").each do |sequence|
- connection.execute("DROP SEQUENCE #{sequence['relname']}")
- end
- end
- end
-end
diff --git a/lib/tasks/gitlab/db/drop_all_tables.rake b/lib/tasks/gitlab/db/drop_all_tables.rake
deleted file mode 100644
index a66030ab93a..00000000000
--- a/lib/tasks/gitlab/db/drop_all_tables.rake
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace :gitlab do
- namespace :db do
- task drop_all_tables: :environment do
- connection = ActiveRecord::Base.connection
- connection.tables.each do |table|
- connection.drop_table(table)
- end
- end
- end
-end
diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake
index cbb3d61af04..4d4e746503a 100644
--- a/lib/tasks/gitlab/test.rake
+++ b/lib/tasks/gitlab/test.rake
@@ -6,7 +6,7 @@ namespace :gitlab do
%W(rake rubocop),
%W(rake spinach),
%W(rake spec),
- %W(rake jasmine:ci)
+ %W(rake teaspoon)
]
cmds.each do |cmd|
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 550a91a79e2..6f4c8987637 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -36,4 +36,32 @@ describe Admin::UsersController do
expect(user.access_locked?).to be_falsey
end
end
+
+ describe 'PATCH disable_two_factor' do
+ let(:user) { create(:user) }
+
+ it 'disables 2FA for the user' do
+ expect(user).to receive(:disable_two_factor!)
+ allow(subject).to receive(:user).and_return(user)
+
+ go
+ end
+
+ it 'redirects back' do
+ go
+
+ expect(response).to redirect_to(admin_user_path(user))
+ end
+
+ it 'displays an alert' do
+ go
+
+ expect(flash[:notice]).
+ to eq 'Two-factor Authentication has been disabled for this user'
+ end
+
+ def go
+ patch :disable_two_factor, id: user.to_param
+ end
+ end
end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 9ad9cb41cc1..1230017c270 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -9,15 +9,27 @@ describe AutocompleteController do
before do
sign_in(user)
project.team << [user, :master]
-
- get(:users, project_id: project.id)
end
let(:body) { JSON.parse(response.body) }
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq 1 }
- it { expect(body.first["username"]).to eq user.username }
+ describe 'GET #users with project ID' do
+ before do
+ get(:users, project_id: project.id)
+ end
+
+ it { expect(body).to be_kind_of(Array) }
+ it { expect(body.size).to eq 1 }
+ it { expect(body.first["username"]).to eq user.username }
+ end
+
+ describe 'GET #users with unknown project' do
+ before do
+ get(:users, project_id: 'unknown')
+ end
+
+ it { expect(response.status).to eq(404) }
+ end
end
context 'group members' do
@@ -26,15 +38,27 @@ describe AutocompleteController do
before do
sign_in(user)
group.add_owner(user)
-
- get(:users, group_id: group.id)
end
let(:body) { JSON.parse(response.body) }
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq 1 }
- it { expect(body.first["username"]).to eq user.username }
+ describe 'GET #users with group ID' do
+ before do
+ get(:users, group_id: group.id)
+ end
+
+ it { expect(body).to be_kind_of(Array) }
+ it { expect(body.size).to eq 1 }
+ it { expect(body.first["username"]).to eq user.username }
+ end
+
+ describe 'GET #users with unknown group ID' do
+ before do
+ get(:users, group_id: 'unknown')
+ end
+
+ it { expect(response.status).to eq(404) }
+ end
end
context 'all users' do
@@ -48,4 +72,52 @@ describe AutocompleteController do
it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq User.count }
end
+
+ context 'unauthenticated user' do
+ let(:public_project) { create(:project, :public) }
+ let(:body) { JSON.parse(response.body) }
+
+ describe 'GET #users with public project' do
+ before do
+ public_project.team << [user, :guest]
+ get(:users, project_id: public_project.id)
+ end
+
+ it { expect(body).to be_kind_of(Array) }
+ it { expect(body.size).to eq 1 }
+ end
+
+ describe 'GET #users with project' do
+ before do
+ get(:users, project_id: project.id)
+ end
+
+ it { expect(response.status).to eq(302) }
+ end
+
+ describe 'GET #users with unknown project' do
+ before do
+ get(:users, project_id: 'unknown')
+ end
+
+ it { expect(response.status).to eq(302) }
+ end
+
+ describe 'GET #users with inaccessible group' do
+ before do
+ project.team << [user, :guest]
+ get(:users, group_id: user.namespace.id)
+ end
+
+ it { expect(response.status).to eq(302) }
+ end
+
+ describe 'GET #users with no project' do
+ before do
+ get(:users)
+ end
+
+ it { expect(response.status).to eq(302) }
+ end
+ end
end
diff --git a/spec/controllers/branches_controller_spec.rb b/spec/controllers/branches_controller_spec.rb
index db3b64babcd..bd4c946b64b 100644
--- a/spec/controllers/branches_controller_spec.rb
+++ b/spec/controllers/branches_controller_spec.rb
@@ -55,4 +55,30 @@ describe Projects::BranchesController do
it { is_expected.to render_template('new') }
end
end
+
+ describe "POST destroy" do
+ render_views
+
+ before do
+ post :destroy,
+ format: :js,
+ id: branch,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ end
+
+ context "valid branch name, valid source" do
+ let(:branch) { "feature" }
+
+ it { expect(response.status).to eq(200) }
+ it { expect(subject).to render_template('destroy') }
+ end
+
+ context "invalid branch name, valid ref" do
+ let(:branch) { "no-branch" }
+
+ it { expect(response.status).to eq(404) }
+ it { expect(subject).to render_template('destroy') }
+ end
+ end
end
diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
index aa09f1a758d..f54706e3aa3 100644
--- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb
+++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
@@ -105,19 +105,12 @@ describe Profiles::TwoFactorAuthsController do
end
describe 'DELETE destroy' do
- let(:user) { create(:user, :two_factor) }
- let!(:codes) { user.generate_otp_backup_codes! }
+ let(:user) { create(:user, :two_factor) }
- it 'clears all 2FA-related fields' do
- expect(user).to be_two_factor_enabled
- expect(user.otp_backup_codes).not_to be_nil
- expect(user.encrypted_otp_secret).not_to be_nil
+ it 'disables two factor' do
+ expect(user).to receive(:disable_two_factor!)
delete :destroy
-
- expect(user).not_to be_two_factor_enabled
- expect(user.otp_backup_codes).to be_nil
- expect(user.encrypted_otp_secret).to be_nil
end
it 'redirects to profile_account_path' do
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
new file mode 100644
index 00000000000..d3868c13202
--- /dev/null
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Projects::MilestonesController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:issue) { create(:issue, project: project, milestone: milestone) }
+
+ before do
+ sign_in(user)
+ project.team << [user, :master]
+ controller.instance_variable_set(:@project, project)
+ end
+
+ describe "#destroy" do
+ it "should remove milestone" do
+ expect(issue.milestone_id).to eq(milestone.id)
+ delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.id, format: :js
+ expect(response).to be_success
+ expect { Milestone.find(milestone.id) }.to raise_exception(ActiveRecord::RecordNotFound)
+ issue.reload
+ expect(issue.milestone_id).to eq(nil)
+ # Check system note left for milestone removal
+ last_note = project.issues.find(issue.id).notes[-1].note
+ expect(last_note).to eq('Milestone removed')
+ end
+ end
+end
diff --git a/spec/controllers/tree_controller_spec.rb b/spec/controllers/projects/tree_controller_spec.rb
index e09caf5df13..53915856357 100644
--- a/spec/controllers/tree_controller_spec.rb
+++ b/spec/controllers/projects/tree_controller_spec.rb
@@ -8,9 +8,6 @@ describe Projects::TreeController do
sign_in(user)
project.team << [user, :master]
-
- allow(project).to receive(:branches).and_return(['master', 'foo/bar/baz'])
- allow(project).to receive(:tags).and_return(['v1.0.0', 'v2.0.0'])
controller.instance_variable_set(:@project, project)
end
@@ -44,6 +41,32 @@ describe Projects::TreeController do
let(:id) { 'invalid-branch/encoding/' }
it { is_expected.to respond_with(:not_found) }
end
+
+ context "valid empty branch, invalid path" do
+ let(:id) { 'empty-branch/invalid-path/' }
+ it { is_expected.to respond_with(:not_found) }
+ end
+
+ context "valid empty branch" do
+ let(:id) { 'empty-branch' }
+ it { is_expected.to respond_with(:success) }
+ end
+
+ context "invalid SHA commit ID" do
+ let(:id) { 'ff39438/.gitignore' }
+ it { is_expected.to respond_with(:not_found) }
+ end
+
+ context "valid SHA commit ID" do
+ let(:id) { '6d39438' }
+ it { is_expected.to respond_with(:success) }
+ end
+
+ context "valid SHA commit ID with path" do
+ let(:id) { '6d39438/.gitignore' }
+ it { expect(response.status).to eq(302) }
+ end
+
end
describe 'GET show with blob path' do
diff --git a/spec/factories.rb b/spec/factories.rb
index 578a2e4dc69..05e3211d551 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -32,6 +32,7 @@ FactoryGirl.define do
before(:create) do |user|
user.two_factor_enabled = true
user.otp_secret = User.generate_otp_secret(32)
+ user.generate_otp_backup_codes!
end
end
diff --git a/spec/features/admin/admin_disables_two_factor_spec.rb b/spec/features/admin/admin_disables_two_factor_spec.rb
new file mode 100644
index 00000000000..71be66303d2
--- /dev/null
+++ b/spec/features/admin/admin_disables_two_factor_spec.rb
@@ -0,0 +1,33 @@
+require 'rails_helper'
+
+feature 'Admin disables 2FA for a user', feature: true do
+ scenario 'successfully', js: true do
+ login_as(:admin)
+ user = create(:user, :two_factor)
+
+ edit_user(user)
+ page.within('.two-factor-status') do
+ click_link 'Disable'
+ end
+
+ page.within('.two-factor-status') do
+ expect(page).to have_content 'Disabled'
+ expect(page).not_to have_button 'Disable'
+ end
+ end
+
+ scenario 'for a user without 2FA enabled' do
+ login_as(:admin)
+ user = create(:user)
+
+ edit_user(user)
+
+ page.within('.two-factor-status') do
+ expect(page).not_to have_button 'Disable'
+ end
+ end
+
+ def edit_user(user)
+ visit admin_user_path(user)
+ end
+end
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index edc1c63a0aa..891df65216d 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Group' do
+feature 'Group', feature: true do
describe 'description' do
let(:group) { create(:group) }
let(:path) { group_path(group) }
diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb
new file mode 100644
index 00000000000..f600f8684ac
--- /dev/null
+++ b/spec/features/issues/filter_by_milestone_spec.rb
@@ -0,0 +1,36 @@
+require 'rails_helper'
+
+feature 'Issue filtering by Milestone', feature: true do
+ include Select2Helper
+
+ let(:project) { create(:project, :public) }
+ let(:milestone) { create(:milestone, project: project) }
+
+ scenario 'filters by no Milestone', js: true do
+ create(:issue, project: project)
+ create(:issue, project: project, milestone: milestone)
+
+ visit_issues(project)
+ filter_by_milestone(Milestone::None.title)
+
+ expect(page).to have_css('.issue-title', count: 1)
+ end
+
+ scenario 'filters by a specific Milestone', js: true do
+ create(:issue, project: project, milestone: milestone)
+ create(:issue, project: project)
+
+ visit_issues(project)
+ filter_by_milestone(milestone.title)
+
+ expect(page).to have_css('.issue-title', count: 1)
+ end
+
+ def visit_issues(project)
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ def filter_by_milestone(title)
+ select2(title, from: '#milestone_title')
+ end
+end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index 808a6eeb958..32fd4065bb4 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -92,22 +92,6 @@ describe 'Issues', feature: true do
let(:issue) { @issue }
- it 'should allow filtering by issues with no specified milestone' do
- visit namespace_project_issues_path(project.namespace, project, milestone_title: IssuableFinder::NONE)
-
- expect(page).not_to have_content 'foobar'
- expect(page).to have_content 'barbaz'
- expect(page).to have_content 'gitlab'
- end
-
- it 'should allow filtering by a specified milestone' do
- visit namespace_project_issues_path(project.namespace, project, milestone_title: issue.milestone.title)
-
- expect(page).to have_content 'foobar'
- expect(page).not_to have_content 'barbaz'
- expect(page).not_to have_content 'gitlab'
- end
-
it 'should allow filtering by issues with no specified assignee' do
visit namespace_project_issues_path(project.namespace, project, assignee_id: IssuableFinder::NONE)
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 046a9f6191d..cef432e512b 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Login' do
+feature 'Login', feature: true do
describe 'with two-factor authentication' do
context 'with valid username/password' do
let(:user) { create(:user, :two_factor) }
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 902968cebcb..b8199aa5e61 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -32,7 +32,7 @@ require 'erb'
#
# See the MarkdownFeature class for setup details.
-describe 'GitLab Markdown' do
+describe 'GitLab Markdown', feature: true do
include ActionView::Helpers::TagHelper
include ActionView::Helpers::UrlHelper
include Capybara::Node::Matchers
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
new file mode 100644
index 00000000000..f70214e1122
--- /dev/null
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -0,0 +1,36 @@
+require 'rails_helper'
+
+feature 'Merge Request filtering by Milestone', feature: true do
+ include Select2Helper
+
+ let(:project) { create(:project, :public) }
+ let(:milestone) { create(:milestone, project: project) }
+
+ scenario 'filters by no Milestone', js: true do
+ create(:merge_request, :with_diffs, source_project: project)
+ create(:merge_request, :simple, source_project: project, milestone: milestone)
+
+ visit_merge_requests(project)
+ filter_by_milestone(Milestone::None.title)
+
+ expect(page).to have_css('.merge-request-title', count: 1)
+ end
+
+ scenario 'filters by a specific Milestone', js: true do
+ create(:merge_request, :with_diffs, source_project: project, milestone: milestone)
+ create(:merge_request, :simple, source_project: project)
+
+ visit_merge_requests(project)
+ filter_by_milestone(milestone.title)
+
+ expect(page).to have_css('.merge-request-title', count: 1)
+ end
+
+ def visit_merge_requests(project)
+ visit namespace_project_merge_requests_path(project.namespace, project)
+ end
+
+ def filter_by_milestone(title)
+ select2(title, from: '#milestone_title')
+ end
+end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index ad37b589b84..d7cb3b2e86e 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Comments' do
+describe 'Comments', feature: true do
include RepoHelpers
describe 'On a merge request', js: true, feature: true do
diff --git a/spec/features/password_reset_spec.rb b/spec/features/password_reset_spec.rb
index a34efce09ef..2b6311e4fd7 100644
--- a/spec/features/password_reset_spec.rb
+++ b/spec/features/password_reset_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Password reset' do
+feature 'Password reset', feature: true do
def forgot_password
click_on 'Forgot your password?'
fill_in 'Email', with: user.email
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index 9fe2e610555..c80253fead8 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -9,8 +9,7 @@ describe 'Profile account page', feature: true do
describe 'when signup is enabled' do
before do
- allow_any_instance_of(ApplicationSetting).
- to receive(:signup_enabled?).and_return(true)
+ stub_application_setting(signup_enabled: true)
visit profile_account_path
end
@@ -24,8 +23,7 @@ describe 'Profile account page', feature: true do
describe 'when signup is disabled' do
before do
- allow_any_instance_of(ApplicationSetting).
- to receive(:signup_enabled?).and_return(false)
+ stub_application_setting(signup_enabled: false)
visit profile_account_path
end
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
index 03e78c533db..9bc6145dda4 100644
--- a/spec/features/profiles/preferences_spec.rb
+++ b/spec/features/profiles/preferences_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Profile > Preferences' do
+describe 'Profile > Preferences', feature: true do
let(:user) { create(:user) }
before do
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index f8eea70ec4a..c8d44efdc8b 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Project' do
+feature 'Project', feature: true do
describe 'description' do
let(:project) { create(:project) }
let(:path) { namespace_project_path(project.namespace, project) }
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index a4c3dfe9205..efcb8a31abe 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Users' do
+feature 'Users', feature: true do
scenario 'GET /users/sign_in creates a new user account' do
visit new_user_session_path
fill_in 'user_name', with: 'Name Surname'
diff --git a/spec/fixtures/GoogleCodeProjectHosting.json b/spec/fixtures/GoogleCodeProjectHosting.json
index d05e77271ae..67bb3bae5b7 100644
--- a/spec/fixtures/GoogleCodeProjectHosting.json
+++ b/spec/fixtures/GoogleCodeProjectHosting.json
@@ -382,6 +382,11 @@
"fileName" : "screenshot.png",
"fileSize" : 0,
"mimetype" : "image/png"
+ }, {
+ "attachmentId" : "001",
+ "fileName" : "screenshot1.PNG",
+ "fileSize" : 0,
+ "mimetype" : "image/x-png"
} ]
}, {
"id" : 1,
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index 14c8c29d008..a42ccb9b501 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -137,7 +137,7 @@ describe GitlabMarkdownHelper do
describe 'random_markdown_tip' do
it 'returns a random Markdown tip' do
stub_const("#{described_class}::MARKDOWN_TIPS", ['Random tip'])
- expect(random_markdown_tip).to eq 'Tip: Random tip'
+ expect(random_markdown_tip).to eq 'Random tip'
end
end
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 0f78725e3d9..beb9b4e438e 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -8,4 +8,48 @@ describe ProjectsHelper do
expect(project_status_css_class("finished")).to eq("success")
end
end
+
+ describe "can_change_visibility_level?" do
+ let(:project) { create(:project) }
+
+ let(:fork_project) do
+ fork_project = create(:forked_project_with_submodules)
+ fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
+ fork_project.save
+
+ fork_project
+ end
+
+ let(:user) { create(:user) }
+
+ it "returns false if there are no approipriate permissions" do
+ allow(helper).to receive(:can?) { false }
+
+ expect(helper.can_change_visibility_level?(project, user)).to be_falsey
+ end
+
+ it "returns true if there are permissions and it is not fork" do
+ allow(helper).to receive(:can?) { true }
+
+ expect(helper.can_change_visibility_level?(project, user)).to be_truthy
+ end
+
+ context "forks" do
+ it "returns false if there are permissions and origin project is PRIVATE" do
+ allow(helper).to receive(:can?) { true }
+
+ project.update visibility_level: Gitlab::VisibilityLevel::PRIVATE
+
+ expect(helper.can_change_visibility_level?(fork_project, user)).to be_falsey
+ end
+
+ it "returns true if there are permissions and origin project is INTERNAL" do
+ allow(helper).to receive(:can?) { true }
+
+ project.update visibility_level: Gitlab::VisibilityLevel::INTERNAL
+
+ expect(helper.can_change_visibility_level?(fork_project, user)).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index 3840e64981f..c4f7693329c 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -72,4 +72,43 @@ describe VisibilityLevelHelper do
end
end
end
+
+ describe "skip_level?" do
+ describe "forks" do
+ let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+ let(:fork_project) { create(:forked_project_with_submodules) }
+
+ before do
+ fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
+ fork_project.save
+ end
+
+ it "skips levels" do
+ expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
+ expect(skip_level?(fork_project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
+ expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
+ end
+ end
+
+ describe "non-forked project" do
+ let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+
+ it "skips levels" do
+ expect(skip_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
+ expect(skip_level?(project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
+ expect(skip_level?(project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
+ end
+ end
+
+ describe "Snippet" do
+ let(:snippet) { create(:snippet, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+
+ it "skips levels" do
+ expect(skip_level?(snippet, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
+ expect(skip_level?(snippet, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
+ expect(skip_level?(snippet, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
+ end
+ end
+
+ end
end
diff --git a/spec/javascripts/fixtures/line_highlighter.html.haml b/spec/javascripts/fixtures/line_highlighter.html.haml
index 15ad1d8968f..da1ebcdb23c 100644
--- a/spec/javascripts/fixtures/line_highlighter.html.haml
+++ b/spec/javascripts/fixtures/line_highlighter.html.haml
@@ -2,7 +2,9 @@
.file-content
.line-numbers
- 1.upto(25) do |i|
- %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}= i
+ %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
+ %i.fa.fa-link
+ = i
%pre.code.highlight
%code
- 1.upto(25) do |i|
diff --git a/spec/javascripts/line_highlighter_spec.js.coffee b/spec/javascripts/line_highlighter_spec.js.coffee
index 14fa487ff7f..57453c716a5 100644
--- a/spec/javascripts/line_highlighter_spec.js.coffee
+++ b/spec/javascripts/line_highlighter_spec.js.coffee
@@ -48,6 +48,14 @@ describe 'LineHighlighter', ->
clickLine(13)
expect(spy).toHaveBeenPrevented()
+ it 'handles clicking on a child icon element', ->
+ spy = spyOn(@class, 'setHash').and.callThrough()
+
+ $('#L13 i').mousedown().click()
+
+ expect(spy).toHaveBeenCalledWith(13)
+ expect($('#LC13')).toHaveClass(@css)
+
describe 'without shiftKey', ->
it 'highlights one line when clicked', ->
clickLine(13)
diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb
index c53ddeb87b5..f49cbb7f532 100644
--- a/spec/lib/gitlab/google_code_import/importer_spec.rb
+++ b/spec/lib/gitlab/google_code_import/importer_spec.rb
@@ -65,6 +65,7 @@ describe Gitlab::GoogleCodeImport::Importer do
expect(issue.description).to include('all the best!')
expect(issue.description).to include('[tint2_task_scrolling.diff](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/tint2_task_scrolling.diff)')
expect(issue.description).to include('![screenshot.png](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/screenshot.png)')
+ expect(issue.description).to include('![screenshot1.PNG](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/screenshot1.PNG)')
end
it "imports issue comments" do
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index f7f66987b5f..2d6fe003215 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -28,4 +28,53 @@ describe Issue, "Mentionable" do
issue.create_cross_references!(project, author, [commit2])
end
end
+
+ describe '#create_new_cross_references!' do
+ let(:project) { create(:project) }
+ let(:issues) { create_list(:issue, 2, project: project) }
+
+ context 'before changes are persisted' do
+ it 'ignores pre-existing references' do
+ issue = create_issue(description: issues[0].to_reference)
+
+ expect(SystemNoteService).not_to receive(:cross_reference)
+
+ issue.description = 'New description'
+ issue.create_new_cross_references!
+ end
+
+ it 'notifies new references' do
+ issue = create_issue(description: issues[0].to_reference)
+
+ expect(SystemNoteService).to receive(:cross_reference).with(issues[1], any_args)
+
+ issue.description = issues[1].to_reference
+ issue.create_new_cross_references!
+ end
+ end
+
+ context 'after changes are persisted' do
+ it 'ignores pre-existing references' do
+ issue = create_issue(description: issues[0].to_reference)
+
+ expect(SystemNoteService).not_to receive(:cross_reference)
+
+ issue.update_attributes(description: 'New description')
+ issue.create_new_cross_references!
+ end
+
+ it 'notifies new references' do
+ issue = create_issue(description: issues[0].to_reference)
+
+ expect(SystemNoteService).to receive(:cross_reference).with(issues[1], any_args)
+
+ issue.update_attributes(description: issues[1].to_reference)
+ issue.create_new_cross_references!
+ end
+ end
+
+ def create_issue(description:)
+ create(:issue, project: project, description: description)
+ end
+ end
end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index fbb9e162952..456bf221d62 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -63,7 +63,7 @@ describe Key do
key = build(:key)
# Not always the middle, but close enough
- key.key = key.key[0..100] + ' ' + key.key[100..-1]
+ key.key = key.key[0..100] + ' ' + key.key[101..-1]
expect(key).not_to be_valid
end
@@ -71,6 +71,12 @@ describe Key do
it 'rejects the unfingerprintable key (not a key)' do
expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid
end
+
+ it 'rejects the multiple line key' do
+ key = build(:key)
+ key.key.gsub!(' ', "\n")
+ expect(key).not_to be_valid
+ end
end
context 'callbacks' do
diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb
index fedc37c9b94..a14384c87b4 100644
--- a/spec/models/project_services/gitlab_ci_service_spec.rb
+++ b/spec/models/project_services/gitlab_ci_service_spec.rb
@@ -26,6 +26,33 @@ describe GitlabCiService do
it { is_expected.to have_one(:service_hook) }
end
+ describe 'validations' do
+ context 'active' do
+ before { allow(subject).to receive(:activated?).and_return(true) }
+
+ it { is_expected.to validate_presence_of(:token) }
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
+ it { is_expected.to allow_value('http://ci.example.com/project/1').for(:project_url) }
+ it { is_expected.not_to allow_value('token with spaces').for(:token) }
+ it { is_expected.not_to allow_value('token/with%spaces').for(:token) }
+ it { is_expected.not_to allow_value('this is not url').for(:project_url) }
+ it { is_expected.not_to allow_value('http//noturl').for(:project_url) }
+ it { is_expected.not_to allow_value('ftp://ci.example.com/projects/3').for(:project_url) }
+ end
+
+ context 'inactive' do
+ before { allow(subject).to receive(:activated?).and_return(false) }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
+ it { is_expected.to allow_value('http://ci.example.com/project/1').for(:project_url) }
+ it { is_expected.to allow_value('token with spaces').for(:token) }
+ it { is_expected.to allow_value('ftp://ci.example.com/projects/3').for(:project_url) }
+ end
+ end
+
describe 'commits methods' do
before do
@service = GitlabCiService.new
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index a083dcb1274..d25351b0f0e 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -47,4 +47,28 @@ describe Repository do
it { is_expected.to be_falsey }
end
end
+
+ describe "search_files" do
+ let(:results) { repository.search_files('feature', 'master') }
+ subject { results }
+
+ it { is_expected.to be_an Array }
+
+ describe 'result' do
+ subject { results.first }
+
+ it { is_expected.to be_an String }
+ it { expect(subject.lines[2]).to eq("master:CHANGELOG:188: - Feature: Replace teams with group membership\n") }
+ end
+
+ describe 'parsing result' do
+ subject { repository.parse_search_result(results.first) }
+
+ it { is_expected.to be_an OpenStruct }
+ it { expect(subject.filename).to eq('CHANGELOG') }
+ it { expect(subject.ref).to eq('master') }
+ it { expect(subject.startline).to eq(186) }
+ it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") }
+ end
+ end
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index 6d2423ae27a..16902317f10 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -217,6 +217,24 @@ describe User do
end
end
+ describe '#disable_two_factor!' do
+ it 'clears all 2FA-related fields' do
+ user = create(:user, :two_factor)
+
+ expect(user).to be_two_factor_enabled
+ expect(user.encrypted_otp_secret).not_to be_nil
+ expect(user.otp_backup_codes).not_to be_nil
+
+ user.disable_two_factor!
+
+ expect(user).not_to be_two_factor_enabled
+ expect(user.encrypted_otp_secret).to be_nil
+ expect(user.encrypted_otp_secret_iv).to be_nil
+ expect(user.encrypted_otp_secret_salt).to be_nil
+ expect(user.otp_backup_codes).to be_nil
+ end
+ end
+
describe 'projects' do
before do
@user = create :user
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index cb6e5e89625..5c1b58535cc 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -14,10 +14,13 @@ describe API::API, api: true do
describe "GET /projects/:id/repository/branches" do
it "should return an array of project branches" do
+ project.repository.expire_cache
+
get api("/projects/#{project.id}/repository/branches", user)
expect(response.status).to eq(200)
expect(json_response).to be_an Array
- expect(json_response.first['name']).to eq(project.repository.branch_names.first)
+ branch_names = json_response.map { |x| x['name'] }
+ expect(branch_names).to match_array(project.repository.branch_names)
end
end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index e9ff832603f..5bd8206b890 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -89,7 +89,7 @@ describe API::API, api: true do
it 'returns projects in the correct order when ci_enabled_first parameter is passed' do
[project, project2, project3].each{ |project| project.build_missing_services }
- project2.gitlab_ci_service.update(active: true, token: "token", project_url: "url")
+ project2.gitlab_ci_service.update(active: true, token: "token", project_url: "http://ci.example.com/projects/1")
get api('/projects', user), { ci_enabled_first: 'true' }
expect(response.status).to eq(200)
expect(json_response).to be_an Array
@@ -220,9 +220,7 @@ describe API::API, api: true do
context 'when a visibility level is restricted' do
before do
@project = attributes_for(:project, { public: true })
- allow_any_instance_of(ApplicationSetting).to(
- receive(:restricted_visibility_levels).and_return([20])
- )
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
it 'should not allow a non-admin to use a restricted visibility level' do
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 51c543578df..6d29a28580a 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -7,7 +7,7 @@ describe API::API, api: true do
describe "POST /projects/:id/services/gitlab-ci" do
it "should update gitlab-ci settings" do
- put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'secret-token', project_url: "http://ci.example.com/projects/1"
+ put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'secrettoken', project_url: "http://ci.example.com/projects/1"
expect(response.status).to eq(200)
end
@@ -17,6 +17,18 @@ describe API::API, api: true do
expect(response.status).to eq(400)
end
+
+ it "should return if the format of token is invalid" do
+ put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'token-with dashes and spaces%', project_url: "http://ci.example.com/projects/1", active: true
+
+ expect(response.status).to eq(404)
+ end
+
+ it "should return if the format of token is invalid" do
+ put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'token-with dashes and spaces%', project_url: "ftp://ci.example/projects/1", active: true
+
+ expect(response.status).to eq(404)
+ end
end
describe "DELETE /projects/:id/services/gitlab-ci" do
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
new file mode 100644
index 00000000000..c815a8e1d73
--- /dev/null
+++ b/spec/requests/api/settings_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe API::API, 'Settings', api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
+
+
+ describe "GET /application/settings" do
+ it "should return application settings" do
+ get api("/application/settings", admin)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Hash
+ expect(json_response['default_projects_limit']).to eq(42)
+ expect(json_response['signin_enabled']).to be_truthy
+ end
+ end
+
+ describe "PUT /application/settings" do
+ it "should update application settings" do
+ put api("/application/settings", admin),
+ default_projects_limit: 3, signin_enabled: false
+ expect(response.status).to eq(200)
+ expect(json_response['default_projects_limit']).to eq(3)
+ expect(json_response['signin_enabled']).to be_falsey
+ end
+ end
+end
diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb
index 08689c15ca8..8edabe9450b 100644
--- a/spec/services/create_snippet_service_spec.rb
+++ b/spec/services/create_snippet_service_spec.rb
@@ -14,11 +14,7 @@ describe CreateSnippetService do
context 'When public visibility is restricted' do
before do
- allow_any_instance_of(ApplicationSetting).to(
- receive(:restricted_visibility_levels).and_return(
- [Gitlab::VisibilityLevel::PUBLIC]
- )
- )
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
@opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index 3373b97bfd4..62cef9db534 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -124,9 +124,7 @@ describe GitPushService do
end
it "when pushing a branch for the first time with default branch protection disabled" do
- allow(ApplicationSetting.current_application_settings).
- to receive(:default_branch_protection).
- and_return(Gitlab::Access::PROTECTION_NONE)
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
@@ -135,9 +133,7 @@ describe GitPushService do
end
it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do
- allow(ApplicationSetting.current_application_settings).
- to receive(:default_branch_protection).
- and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 337dae592dd..97b206c9854 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -58,9 +58,7 @@ describe Projects::CreateService do
context 'restricted visibility level' do
before do
- allow_any_instance_of(ApplicationSetting).to(
- receive(:restricted_visibility_levels).and_return([20])
- )
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
@opts.merge!(
visibility_level: Gitlab::VisibilityLevel.options['Public']
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 79acba78bda..bb7da33b12e 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -8,7 +8,7 @@ describe Projects::TransferService do
context 'namespace -> namespace' do
before do
group.add_owner(user)
- @result = transfer_project(project, user, new_namespace_id: group.id)
+ @result = transfer_project(project, user, group)
end
it { expect(@result).to be_truthy }
@@ -17,7 +17,7 @@ describe Projects::TransferService do
context 'namespace -> no namespace' do
before do
- @result = transfer_project(project, user, new_namespace_id: nil)
+ @result = transfer_project(project, user, nil)
end
it { expect(@result).to eq false }
@@ -26,14 +26,14 @@ describe Projects::TransferService do
context 'namespace -> not allowed namespace' do
before do
- @result = transfer_project(project, user, new_namespace_id: group.id)
+ @result = transfer_project(project, user, group)
end
it { expect(@result).to eq false }
it { expect(project.namespace).to eq(user.namespace) }
end
- def transfer_project(project, user, params)
- Projects::TransferService.new(project, user, params).execute
+ def transfer_project(project, user, new_namespace)
+ Projects::TransferService.new(project, user).execute(new_namespace)
end
end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index 0dd6980a44f..b347fa15f87 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -47,9 +47,7 @@ describe Projects::UpdateService do
context 'respect configured visibility restrictions setting' do
before(:each) do
- allow_any_instance_of(ApplicationSetting).to(
- receive(:restricted_visibility_levels).and_return([20])
- )
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
context 'should be private when updated to private' do
diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb
index 841ef9bfed1..d7c516e3934 100644
--- a/spec/services/update_snippet_service_spec.rb
+++ b/spec/services/update_snippet_service_spec.rb
@@ -14,11 +14,7 @@ describe UpdateSnippetService do
context 'When public visibility is restricted' do
before do
- allow_any_instance_of(ApplicationSetting).to(
- receive(:restricted_visibility_levels).and_return(
- [Gitlab::VisibilityLevel::PUBLIC]
- )
- )
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
@snippet = create_snippet(@project, @user, @opts)
@opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 682a8863bad..d0f1873ee2d 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,4 +1,15 @@
+if ENV['SIMPLECOV']
+ require 'simplecov'
+ SimpleCov.start :rails
+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 'shoulda/matchers'
diff --git a/spec/support/coverage.rb b/spec/support/coverage.rb
deleted file mode 100644
index a54bf03380c..00000000000
--- a/spec/support/coverage.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-if ENV['SIMPLECOV']
- require 'simplecov'
-end
-
-if ENV['COVERALLS']
- require 'coveralls'
- Coveralls.wear_merged!
-end
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index a2a0b6905f9..f0717e61781 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -143,6 +143,6 @@ shared_examples 'an editable mentionable' do
end
set_mentionable_text.call(new_text)
- subject.notice_added_references(project, author)
+ subject.create_new_cross_references!(project, author)
end
end
diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb
index ad86abdbb41..e4004ec8f79 100644
--- a/spec/support/stub_configuration.rb
+++ b/spec/support/stub_configuration.rb
@@ -1,5 +1,10 @@
module StubConfiguration
def stub_application_setting(messages)
+ add_predicates(messages)
+
+ # Stubbing both of these because we're not yet consistent with how we access
+ # current application settings
+ allow_any_instance_of(ApplicationSetting).to receive_messages(messages)
allow(Gitlab::CurrentSettings.current_application_settings).
to receive_messages(messages)
end
@@ -11,4 +16,25 @@ module StubConfiguration
def stub_gravatar_setting(messages)
allow(Gitlab.config.gravatar).to receive_messages(messages)
end
+
+ private
+
+ # Modifies stubbed messages to also stub possible predicate versions
+ #
+ # Examples:
+ #
+ # add_predicates(foo: true)
+ # # => {foo: true, foo?: true}
+ #
+ # add_predicates(signup_enabled?: false)
+ # # => {signup_enabled? false}
+ def add_predicates(messages)
+ # Only modify keys that aren't already predicates
+ keys = messages.keys.map(&:to_s).reject { |k| k.end_with?('?') }
+
+ keys.each do |key|
+ predicate = key + '?'
+ messages[predicate.to_sym] = messages[key.to_sym]
+ end
+ end
end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 2bdb64ff314..ae0b95ea444 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -5,6 +5,7 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = {
+ 'empty-branch' => '7efb185',
'flatten-dir' => 'e56497b',
'feature' => '0b4bc9a',
'feature_conflict' => 'bb5206f',
@@ -14,9 +15,13 @@ module TestEnv
'master' => '5937ac0'
}
- FORKED_BRANCH_SHA = BRANCH_SHA.merge({
- 'add-submodule-version-bump' => '3f547c08'
- })
+ # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
+ # need to keep all the branches in sync.
+ # We currently only need a subset of the branches
+ FORKED_BRANCH_SHA = {
+ 'add-submodule-version-bump' => '3f547c08',
+ 'master' => '5937ac0'
+ }
# Test environment
#
diff --git a/vendor/assets/javascripts/jquery.nicescroll.min.js b/vendor/assets/javascripts/jquery.nicescroll.min.js
new file mode 100644
index 00000000000..5440b6a0da0
--- /dev/null
+++ b/vendor/assets/javascripts/jquery.nicescroll.min.js
@@ -0,0 +1,118 @@
+/* jquery.nicescroll 3.6.0 InuYaksa*2014 MIT http://nicescroll.areaaperta.com */(function(f){"function"===typeof define&&define.amd?define(["jquery"],f):f(jQuery)})(function(f){var y=!1,D=!1,N=0,O=2E3,x=0,H=["webkit","ms","moz","o"],s=window.requestAnimationFrame||!1,t=window.cancelAnimationFrame||!1;if(!s)for(var P in H){var E=H[P];s||(s=window[E+"RequestAnimationFrame"]);t||(t=window[E+"CancelAnimationFrame"]||window[E+"CancelRequestAnimationFrame"])}var v=window.MutationObserver||window.WebKitMutationObserver||!1,I={zindex:"auto",cursoropacitymin:0,cursoropacitymax:1,cursorcolor:"#424242",
+cursorwidth:"5px",cursorborder:"1px solid #fff",cursorborderradius:"5px",scrollspeed:60,mousescrollstep:24,touchbehavior:!1,hwacceleration:!0,usetransition:!0,boxzoom:!1,dblclickzoom:!0,gesturezoom:!0,grabcursorenabled:!0,autohidemode:!0,background:"",iframeautoresize:!0,cursorminheight:32,preservenativescrolling:!0,railoffset:!1,railhoffset:!1,bouncescroll:!0,spacebarenabled:!0,railpadding:{top:0,right:0,left:0,bottom:0},disableoutline:!0,horizrailenabled:!0,railalign:"right",railvalign:"bottom",
+enabletranslate3d:!0,enablemousewheel:!0,enablekeyboard:!0,smoothscroll:!0,sensitiverail:!0,enablemouselockapi:!0,cursorfixedheight:!1,directionlockdeadzone:6,hidecursordelay:400,nativeparentscrolling:!0,enablescrollonselection:!0,overflowx:!0,overflowy:!0,cursordragspeed:.3,rtlmode:"auto",cursordragontouch:!1,oneaxismousemode:"auto",scriptpath:function(){var f=document.getElementsByTagName("script"),f=f[f.length-1].src.split("?")[0];return 0<f.split("/").length?f.split("/").slice(0,-1).join("/")+
+"/":""}(),preventmultitouchscrolling:!0},F=!1,Q=function(){if(F)return F;var f=document.createElement("DIV"),c=f.style,h=navigator.userAgent,m=navigator.platform,d={haspointerlock:"pointerLockElement"in document||"webkitPointerLockElement"in document||"mozPointerLockElement"in document};d.isopera="opera"in window;d.isopera12=d.isopera&&"getUserMedia"in navigator;d.isoperamini="[object OperaMini]"===Object.prototype.toString.call(window.operamini);d.isie="all"in document&&"attachEvent"in f&&!d.isopera;
+d.isieold=d.isie&&!("msInterpolationMode"in c);d.isie7=d.isie&&!d.isieold&&(!("documentMode"in document)||7==document.documentMode);d.isie8=d.isie&&"documentMode"in document&&8==document.documentMode;d.isie9=d.isie&&"performance"in window&&9<=document.documentMode;d.isie10=d.isie&&"performance"in window&&10==document.documentMode;d.isie11="msRequestFullscreen"in f&&11<=document.documentMode;d.isie9mobile=/iemobile.9/i.test(h);d.isie9mobile&&(d.isie9=!1);d.isie7mobile=!d.isie9mobile&&d.isie7&&/iemobile/i.test(h);
+d.ismozilla="MozAppearance"in c;d.iswebkit="WebkitAppearance"in c;d.ischrome="chrome"in window;d.ischrome22=d.ischrome&&d.haspointerlock;d.ischrome26=d.ischrome&&"transition"in c;d.cantouch="ontouchstart"in document.documentElement||"ontouchstart"in window;d.hasmstouch=window.MSPointerEvent||!1;d.hasw3ctouch=window.PointerEvent||!1;d.ismac=/^mac$/i.test(m);d.isios=d.cantouch&&/iphone|ipad|ipod/i.test(m);d.isios4=d.isios&&!("seal"in Object);d.isios7=d.isios&&"webkitHidden"in document;d.isandroid=/android/i.test(h);
+d.haseventlistener="addEventListener"in f;d.trstyle=!1;d.hastransform=!1;d.hastranslate3d=!1;d.transitionstyle=!1;d.hastransition=!1;d.transitionend=!1;m=["transform","msTransform","webkitTransform","MozTransform","OTransform"];for(h=0;h<m.length;h++)if("undefined"!=typeof c[m[h]]){d.trstyle=m[h];break}d.hastransform=!!d.trstyle;d.hastransform&&(c[d.trstyle]="translate3d(1px,2px,3px)",d.hastranslate3d=/translate3d/.test(c[d.trstyle]));d.transitionstyle=!1;d.prefixstyle="";d.transitionend=!1;for(var m=
+"transition webkitTransition msTransition MozTransition OTransition OTransition KhtmlTransition".split(" "),n=" -webkit- -ms- -moz- -o- -o -khtml-".split(" "),p="transitionend webkitTransitionEnd msTransitionEnd transitionend otransitionend oTransitionEnd KhtmlTransitionEnd".split(" "),h=0;h<m.length;h++)if(m[h]in c){d.transitionstyle=m[h];d.prefixstyle=n[h];d.transitionend=p[h];break}d.ischrome26&&(d.prefixstyle=n[1]);d.hastransition=d.transitionstyle;a:{h=["-webkit-grab","-moz-grab","grab"];if(d.ischrome&&
+!d.ischrome22||d.isie)h=[];for(m=0;m<h.length;m++)if(n=h[m],c.cursor=n,c.cursor==n){c=n;break a}c="url(//mail.google.com/mail/images/2/openhand.cur),n-resize"}d.cursorgrabvalue=c;d.hasmousecapture="setCapture"in f;d.hasMutationObserver=!1!==v;return F=d},R=function(k,c){function h(){var b=a.doc.css(e.trstyle);return b&&"matrix"==b.substr(0,6)?b.replace(/^.*\((.*)\)$/g,"$1").replace(/px/g,"").split(/, +/):!1}function m(){var b=a.win;if("zIndex"in b)return b.zIndex();for(;0<b.length&&9!=b[0].nodeType;){var g=
+b.css("zIndex");if(!isNaN(g)&&0!=g)return parseInt(g);b=b.parent()}return!1}function d(b,g,q){g=b.css(g);b=parseFloat(g);return isNaN(b)?(b=w[g]||0,q=3==b?q?a.win.outerHeight()-a.win.innerHeight():a.win.outerWidth()-a.win.innerWidth():1,a.isie8&&b&&(b+=1),q?b:0):b}function n(b,g,q,c){a._bind(b,g,function(a){a=a?a:window.event;var c={original:a,target:a.target||a.srcElement,type:"wheel",deltaMode:"MozMousePixelScroll"==a.type?0:1,deltaX:0,deltaZ:0,preventDefault:function(){a.preventDefault?a.preventDefault():
+a.returnValue=!1;return!1},stopImmediatePropagation:function(){a.stopImmediatePropagation?a.stopImmediatePropagation():a.cancelBubble=!0}};"mousewheel"==g?(c.deltaY=-.025*a.wheelDelta,a.wheelDeltaX&&(c.deltaX=-.025*a.wheelDeltaX)):c.deltaY=a.detail;return q.call(b,c)},c)}function p(b,g,c){var d,e;0==b.deltaMode?(d=-Math.floor(a.opt.mousescrollstep/54*b.deltaX),e=-Math.floor(a.opt.mousescrollstep/54*b.deltaY)):1==b.deltaMode&&(d=-Math.floor(b.deltaX*a.opt.mousescrollstep),e=-Math.floor(b.deltaY*a.opt.mousescrollstep));
+g&&a.opt.oneaxismousemode&&0==d&&e&&(d=e,e=0,c&&(0>d?a.getScrollLeft()>=a.page.maxw:0>=a.getScrollLeft())&&(e=d,d=0));d&&(a.scrollmom&&a.scrollmom.stop(),a.lastdeltax+=d,a.debounced("mousewheelx",function(){var b=a.lastdeltax;a.lastdeltax=0;a.rail.drag||a.doScrollLeftBy(b)},15));if(e){if(a.opt.nativeparentscrolling&&c&&!a.ispage&&!a.zoomactive)if(0>e){if(a.getScrollTop()>=a.page.maxh)return!0}else if(0>=a.getScrollTop())return!0;a.scrollmom&&a.scrollmom.stop();a.lastdeltay+=e;a.debounced("mousewheely",
+function(){var b=a.lastdeltay;a.lastdeltay=0;a.rail.drag||a.doScrollBy(b)},15)}b.stopImmediatePropagation();return b.preventDefault()}var a=this;this.version="3.6.0";this.name="nicescroll";this.me=c;this.opt={doc:f("body"),win:!1};f.extend(this.opt,I);this.opt.snapbackspeed=80;if(k)for(var G in a.opt)"undefined"!=typeof k[G]&&(a.opt[G]=k[G]);this.iddoc=(this.doc=a.opt.doc)&&this.doc[0]?this.doc[0].id||"":"";this.ispage=/^BODY|HTML/.test(a.opt.win?a.opt.win[0].nodeName:this.doc[0].nodeName);this.haswrapper=
+!1!==a.opt.win;this.win=a.opt.win||(this.ispage?f(window):this.doc);this.docscroll=this.ispage&&!this.haswrapper?f(window):this.win;this.body=f("body");this.iframe=this.isfixed=this.viewport=!1;this.isiframe="IFRAME"==this.doc[0].nodeName&&"IFRAME"==this.win[0].nodeName;this.istextarea="TEXTAREA"==this.win[0].nodeName;this.forcescreen=!1;this.canshowonmouseevent="scroll"!=a.opt.autohidemode;this.page=this.view=this.onzoomout=this.onzoomin=this.onscrollcancel=this.onscrollend=this.onscrollstart=this.onclick=
+this.ongesturezoom=this.onkeypress=this.onmousewheel=this.onmousemove=this.onmouseup=this.onmousedown=!1;this.scroll={x:0,y:0};this.scrollratio={x:0,y:0};this.cursorheight=20;this.scrollvaluemax=0;this.isrtlmode="auto"==this.opt.rtlmode?"rtl"==(this.win[0]==window?this.body:this.win).css("direction"):!0===this.opt.rtlmode;this.observerbody=this.observerremover=this.observer=this.scrollmom=this.scrollrunning=!1;do this.id="ascrail"+O++;while(document.getElementById(this.id));this.hasmousefocus=this.hasfocus=
+this.zoomactive=this.zoom=this.selectiondrag=this.cursorfreezed=this.cursor=this.rail=!1;this.visibility=!0;this.hidden=this.locked=this.railslocked=!1;this.cursoractive=!0;this.wheelprevented=!1;this.overflowx=a.opt.overflowx;this.overflowy=a.opt.overflowy;this.nativescrollingarea=!1;this.checkarea=0;this.events=[];this.saved={};this.delaylist={};this.synclist={};this.lastdeltay=this.lastdeltax=0;this.detected=Q();var e=f.extend({},this.detected);this.ishwscroll=(this.canhwscroll=e.hastransform&&
+a.opt.hwacceleration)&&a.haswrapper;this.hasreversehr=this.isrtlmode&&!e.iswebkit;this.istouchcapable=!1;!e.cantouch||e.isios||e.isandroid||!e.iswebkit&&!e.ismozilla||(this.istouchcapable=!0,e.cantouch=!1);a.opt.enablemouselockapi||(e.hasmousecapture=!1,e.haspointerlock=!1);this.debounced=function(b,g,c){var d=a.delaylist[b];a.delaylist[b]=g;d||setTimeout(function(){var g=a.delaylist[b];a.delaylist[b]=!1;g.call(a)},c)};var r=!1;this.synched=function(b,g){a.synclist[b]=g;(function(){r||(s(function(){r=
+!1;for(var b in a.synclist){var g=a.synclist[b];g&&g.call(a);a.synclist[b]=!1}}),r=!0)})();return b};this.unsynched=function(b){a.synclist[b]&&(a.synclist[b]=!1)};this.css=function(b,g){for(var c in g)a.saved.css.push([b,c,b.css(c)]),b.css(c,g[c])};this.scrollTop=function(b){return"undefined"==typeof b?a.getScrollTop():a.setScrollTop(b)};this.scrollLeft=function(b){return"undefined"==typeof b?a.getScrollLeft():a.setScrollLeft(b)};var A=function(a,g,c,d,e,f,h){this.st=a;this.ed=g;this.spd=c;this.p1=
+d||0;this.p2=e||1;this.p3=f||0;this.p4=h||1;this.ts=(new Date).getTime();this.df=this.ed-this.st};A.prototype={B2:function(a){return 3*a*a*(1-a)},B3:function(a){return 3*a*(1-a)*(1-a)},B4:function(a){return(1-a)*(1-a)*(1-a)},getNow:function(){var a=1-((new Date).getTime()-this.ts)/this.spd,g=this.B2(a)+this.B3(a)+this.B4(a);return 0>a?this.ed:this.st+Math.round(this.df*g)},update:function(a,g){this.st=this.getNow();this.ed=a;this.spd=g;this.ts=(new Date).getTime();this.df=this.ed-this.st;return this}};
+if(this.ishwscroll){this.doc.translate={x:0,y:0,tx:"0px",ty:"0px"};e.hastranslate3d&&e.isios&&this.doc.css("-webkit-backface-visibility","hidden");this.getScrollTop=function(b){if(!b){if(b=h())return 16==b.length?-b[13]:-b[5];if(a.timerscroll&&a.timerscroll.bz)return a.timerscroll.bz.getNow()}return a.doc.translate.y};this.getScrollLeft=function(b){if(!b){if(b=h())return 16==b.length?-b[12]:-b[4];if(a.timerscroll&&a.timerscroll.bh)return a.timerscroll.bh.getNow()}return a.doc.translate.x};this.notifyScrollEvent=
+function(a){var g=document.createEvent("UIEvents");g.initUIEvent("scroll",!1,!0,window,1);g.niceevent=!0;a.dispatchEvent(g)};var K=this.isrtlmode?1:-1;e.hastranslate3d&&a.opt.enabletranslate3d?(this.setScrollTop=function(b,g){a.doc.translate.y=b;a.doc.translate.ty=-1*b+"px";a.doc.css(e.trstyle,"translate3d("+a.doc.translate.tx+","+a.doc.translate.ty+",0px)");g||a.notifyScrollEvent(a.win[0])},this.setScrollLeft=function(b,g){a.doc.translate.x=b;a.doc.translate.tx=b*K+"px";a.doc.css(e.trstyle,"translate3d("+
+a.doc.translate.tx+","+a.doc.translate.ty+",0px)");g||a.notifyScrollEvent(a.win[0])}):(this.setScrollTop=function(b,g){a.doc.translate.y=b;a.doc.translate.ty=-1*b+"px";a.doc.css(e.trstyle,"translate("+a.doc.translate.tx+","+a.doc.translate.ty+")");g||a.notifyScrollEvent(a.win[0])},this.setScrollLeft=function(b,g){a.doc.translate.x=b;a.doc.translate.tx=b*K+"px";a.doc.css(e.trstyle,"translate("+a.doc.translate.tx+","+a.doc.translate.ty+")");g||a.notifyScrollEvent(a.win[0])})}else this.getScrollTop=
+function(){return a.docscroll.scrollTop()},this.setScrollTop=function(b){return a.docscroll.scrollTop(b)},this.getScrollLeft=function(){return a.detected.ismozilla&&a.isrtlmode?Math.abs(a.docscroll.scrollLeft()):a.docscroll.scrollLeft()},this.setScrollLeft=function(b){return a.docscroll.scrollLeft(a.detected.ismozilla&&a.isrtlmode?-b:b)};this.getTarget=function(a){return a?a.target?a.target:a.srcElement?a.srcElement:!1:!1};this.hasParent=function(a,g){if(!a)return!1;for(var c=a.target||a.srcElement||
+a||!1;c&&c.id!=g;)c=c.parentNode||!1;return!1!==c};var w={thin:1,medium:3,thick:5};this.getDocumentScrollOffset=function(){return{top:window.pageYOffset||document.documentElement.scrollTop,left:window.pageXOffset||document.documentElement.scrollLeft}};this.getOffset=function(){if(a.isfixed){var b=a.win.offset(),g=a.getDocumentScrollOffset();b.top-=g.top;b.left-=g.left;return b}b=a.win.offset();if(!a.viewport)return b;g=a.viewport.offset();return{top:b.top-g.top,left:b.left-g.left}};this.updateScrollBar=
+function(b){if(a.ishwscroll)a.rail.css({height:a.win.innerHeight()-(a.opt.railpadding.top+a.opt.railpadding.bottom)}),a.railh&&a.railh.css({width:a.win.innerWidth()-(a.opt.railpadding.left+a.opt.railpadding.right)});else{var g=a.getOffset(),c=g.top,e=g.left-(a.opt.railpadding.left+a.opt.railpadding.right),c=c+d(a.win,"border-top-width",!0),e=e+(a.rail.align?a.win.outerWidth()-d(a.win,"border-right-width")-a.rail.width:d(a.win,"border-left-width")),f=a.opt.railoffset;f&&(f.top&&(c+=f.top),a.rail.align&&
+f.left&&(e+=f.left));a.railslocked||a.rail.css({top:c,left:e,height:(b?b.h:a.win.innerHeight())-(a.opt.railpadding.top+a.opt.railpadding.bottom)});a.zoom&&a.zoom.css({top:c+1,left:1==a.rail.align?e-20:e+a.rail.width+4});if(a.railh&&!a.railslocked){c=g.top;e=g.left;if(f=a.opt.railhoffset)f.top&&(c+=f.top),f.left&&(e+=f.left);b=a.railh.align?c+d(a.win,"border-top-width",!0)+a.win.innerHeight()-a.railh.height:c+d(a.win,"border-top-width",!0);e+=d(a.win,"border-left-width");a.railh.css({top:b-(a.opt.railpadding.top+
+a.opt.railpadding.bottom),left:e,width:a.railh.width})}}};this.doRailClick=function(b,g,c){var e;a.railslocked||(a.cancelEvent(b),g?(g=c?a.doScrollLeft:a.doScrollTop,e=c?(b.pageX-a.railh.offset().left-a.cursorwidth/2)*a.scrollratio.x:(b.pageY-a.rail.offset().top-a.cursorheight/2)*a.scrollratio.y,g(e)):(g=c?a.doScrollLeftBy:a.doScrollBy,e=c?a.scroll.x:a.scroll.y,b=c?b.pageX-a.railh.offset().left:b.pageY-a.rail.offset().top,c=c?a.view.w:a.view.h,g(e>=b?c:-c)))};a.hasanimationframe=s;a.hascancelanimationframe=
+t;a.hasanimationframe?a.hascancelanimationframe||(t=function(){a.cancelAnimationFrame=!0}):(s=function(a){return setTimeout(a,15-Math.floor(+new Date/1E3)%16)},t=clearInterval);this.init=function(){a.saved.css=[];if(e.isie7mobile||e.isoperamini)return!0;e.hasmstouch&&a.css(a.ispage?f("html"):a.win,{"-ms-touch-action":"none"});a.zindex="auto";a.zindex=a.ispage||"auto"!=a.opt.zindex?a.opt.zindex:m()||"auto";!a.ispage&&"auto"!=a.zindex&&a.zindex>x&&(x=a.zindex);a.isie&&0==a.zindex&&"auto"==a.opt.zindex&&
+(a.zindex="auto");if(!a.ispage||!e.cantouch&&!e.isieold&&!e.isie9mobile){var b=a.docscroll;a.ispage&&(b=a.haswrapper?a.win:a.doc);e.isie9mobile||a.css(b,{"overflow-y":"hidden"});a.ispage&&e.isie7&&("BODY"==a.doc[0].nodeName?a.css(f("html"),{"overflow-y":"hidden"}):"HTML"==a.doc[0].nodeName&&a.css(f("body"),{"overflow-y":"hidden"}));!e.isios||a.ispage||a.haswrapper||a.css(f("body"),{"-webkit-overflow-scrolling":"touch"});var g=f(document.createElement("div"));g.css({position:"relative",top:0,"float":"right",
+width:a.opt.cursorwidth,height:"0px","background-color":a.opt.cursorcolor,border:a.opt.cursorborder,"background-clip":"padding-box","-webkit-border-radius":a.opt.cursorborderradius,"-moz-border-radius":a.opt.cursorborderradius,"border-radius":a.opt.cursorborderradius});g.hborder=parseFloat(g.outerHeight()-g.innerHeight());g.addClass("nicescroll-cursors");a.cursor=g;var c=f(document.createElement("div"));c.attr("id",a.id);c.addClass("nicescroll-rails nicescroll-rails-vr");var d,h,k=["left","right",
+"top","bottom"],J;for(J in k)h=k[J],(d=a.opt.railpadding[h])?c.css("padding-"+h,d+"px"):a.opt.railpadding[h]=0;c.append(g);c.width=Math.max(parseFloat(a.opt.cursorwidth),g.outerWidth());c.css({width:c.width+"px",zIndex:a.zindex,background:a.opt.background,cursor:"default"});c.visibility=!0;c.scrollable=!0;c.align="left"==a.opt.railalign?0:1;a.rail=c;g=a.rail.drag=!1;!a.opt.boxzoom||a.ispage||e.isieold||(g=document.createElement("div"),a.bind(g,"click",a.doZoom),a.bind(g,"mouseenter",function(){a.zoom.css("opacity",
+a.opt.cursoropacitymax)}),a.bind(g,"mouseleave",function(){a.zoom.css("opacity",a.opt.cursoropacitymin)}),a.zoom=f(g),a.zoom.css({cursor:"pointer","z-index":a.zindex,backgroundImage:"url("+a.opt.scriptpath+"zoomico.png)",height:18,width:18,backgroundPosition:"0px 0px"}),a.opt.dblclickzoom&&a.bind(a.win,"dblclick",a.doZoom),e.cantouch&&a.opt.gesturezoom&&(a.ongesturezoom=function(b){1.5<b.scale&&a.doZoomIn(b);.8>b.scale&&a.doZoomOut(b);return a.cancelEvent(b)},a.bind(a.win,"gestureend",a.ongesturezoom)));
+a.railh=!1;var l;a.opt.horizrailenabled&&(a.css(b,{"overflow-x":"hidden"}),g=f(document.createElement("div")),g.css({position:"absolute",top:0,height:a.opt.cursorwidth,width:"0px","background-color":a.opt.cursorcolor,border:a.opt.cursorborder,"background-clip":"padding-box","-webkit-border-radius":a.opt.cursorborderradius,"-moz-border-radius":a.opt.cursorborderradius,"border-radius":a.opt.cursorborderradius}),e.isieold&&g.css({overflow:"hidden"}),g.wborder=parseFloat(g.outerWidth()-g.innerWidth()),
+g.addClass("nicescroll-cursors"),a.cursorh=g,l=f(document.createElement("div")),l.attr("id",a.id+"-hr"),l.addClass("nicescroll-rails nicescroll-rails-hr"),l.height=Math.max(parseFloat(a.opt.cursorwidth),g.outerHeight()),l.css({height:l.height+"px",zIndex:a.zindex,background:a.opt.background}),l.append(g),l.visibility=!0,l.scrollable=!0,l.align="top"==a.opt.railvalign?0:1,a.railh=l,a.railh.drag=!1);a.ispage?(c.css({position:"fixed",top:"0px",height:"100%"}),c.align?c.css({right:"0px"}):c.css({left:"0px"}),
+a.body.append(c),a.railh&&(l.css({position:"fixed",left:"0px",width:"100%"}),l.align?l.css({bottom:"0px"}):l.css({top:"0px"}),a.body.append(l))):(a.ishwscroll?("static"==a.win.css("position")&&a.css(a.win,{position:"relative"}),b="HTML"==a.win[0].nodeName?a.body:a.win,f(b).scrollTop(0).scrollLeft(0),a.zoom&&(a.zoom.css({position:"absolute",top:1,right:0,"margin-right":c.width+4}),b.append(a.zoom)),c.css({position:"absolute",top:0}),c.align?c.css({right:0}):c.css({left:0}),b.append(c),l&&(l.css({position:"absolute",
+left:0,bottom:0}),l.align?l.css({bottom:0}):l.css({top:0}),b.append(l))):(a.isfixed="fixed"==a.win.css("position"),b=a.isfixed?"fixed":"absolute",a.isfixed||(a.viewport=a.getViewport(a.win[0])),a.viewport&&(a.body=a.viewport,0==/fixed|absolute/.test(a.viewport.css("position"))&&a.css(a.viewport,{position:"relative"})),c.css({position:b}),a.zoom&&a.zoom.css({position:b}),a.updateScrollBar(),a.body.append(c),a.zoom&&a.body.append(a.zoom),a.railh&&(l.css({position:b}),a.body.append(l))),e.isios&&a.css(a.win,
+{"-webkit-tap-highlight-color":"rgba(0,0,0,0)","-webkit-touch-callout":"none"}),e.isie&&a.opt.disableoutline&&a.win.attr("hideFocus","true"),e.iswebkit&&a.opt.disableoutline&&a.win.css({outline:"none"}));!1===a.opt.autohidemode?(a.autohidedom=!1,a.rail.css({opacity:a.opt.cursoropacitymax}),a.railh&&a.railh.css({opacity:a.opt.cursoropacitymax})):!0===a.opt.autohidemode||"leave"===a.opt.autohidemode?(a.autohidedom=f().add(a.rail),e.isie8&&(a.autohidedom=a.autohidedom.add(a.cursor)),a.railh&&(a.autohidedom=
+a.autohidedom.add(a.railh)),a.railh&&e.isie8&&(a.autohidedom=a.autohidedom.add(a.cursorh))):"scroll"==a.opt.autohidemode?(a.autohidedom=f().add(a.rail),a.railh&&(a.autohidedom=a.autohidedom.add(a.railh))):"cursor"==a.opt.autohidemode?(a.autohidedom=f().add(a.cursor),a.railh&&(a.autohidedom=a.autohidedom.add(a.cursorh))):"hidden"==a.opt.autohidemode&&(a.autohidedom=!1,a.hide(),a.railslocked=!1);if(e.isie9mobile)a.scrollmom=new L(a),a.onmangotouch=function(){var b=a.getScrollTop(),c=a.getScrollLeft();
+if(b==a.scrollmom.lastscrolly&&c==a.scrollmom.lastscrollx)return!0;var g=b-a.mangotouch.sy,e=c-a.mangotouch.sx;if(0!=Math.round(Math.sqrt(Math.pow(e,2)+Math.pow(g,2)))){var d=0>g?-1:1,f=0>e?-1:1,q=+new Date;a.mangotouch.lazy&&clearTimeout(a.mangotouch.lazy);80<q-a.mangotouch.tm||a.mangotouch.dry!=d||a.mangotouch.drx!=f?(a.scrollmom.stop(),a.scrollmom.reset(c,b),a.mangotouch.sy=b,a.mangotouch.ly=b,a.mangotouch.sx=c,a.mangotouch.lx=c,a.mangotouch.dry=d,a.mangotouch.drx=f,a.mangotouch.tm=q):(a.scrollmom.stop(),
+a.scrollmom.update(a.mangotouch.sx-e,a.mangotouch.sy-g),a.mangotouch.tm=q,g=Math.max(Math.abs(a.mangotouch.ly-b),Math.abs(a.mangotouch.lx-c)),a.mangotouch.ly=b,a.mangotouch.lx=c,2<g&&(a.mangotouch.lazy=setTimeout(function(){a.mangotouch.lazy=!1;a.mangotouch.dry=0;a.mangotouch.drx=0;a.mangotouch.tm=0;a.scrollmom.doMomentum(30)},100)))}},c=a.getScrollTop(),l=a.getScrollLeft(),a.mangotouch={sy:c,ly:c,dry:0,sx:l,lx:l,drx:0,lazy:!1,tm:0},a.bind(a.docscroll,"scroll",a.onmangotouch);else{if(e.cantouch||
+a.istouchcapable||a.opt.touchbehavior||e.hasmstouch){a.scrollmom=new L(a);a.ontouchstart=function(b){if(b.pointerType&&2!=b.pointerType&&"touch"!=b.pointerType)return!1;a.hasmoving=!1;if(!a.railslocked){var c;if(e.hasmstouch)for(c=b.target?b.target:!1;c;){var g=f(c).getNiceScroll();if(0<g.length&&g[0].me==a.me)break;if(0<g.length)return!1;if("DIV"==c.nodeName&&c.id==a.id)break;c=c.parentNode?c.parentNode:!1}a.cancelScroll();if((c=a.getTarget(b))&&/INPUT/i.test(c.nodeName)&&/range/i.test(c.type))return a.stopPropagation(b);
+!("clientX"in b)&&"changedTouches"in b&&(b.clientX=b.changedTouches[0].clientX,b.clientY=b.changedTouches[0].clientY);a.forcescreen&&(g=b,b={original:b.original?b.original:b},b.clientX=g.screenX,b.clientY=g.screenY);a.rail.drag={x:b.clientX,y:b.clientY,sx:a.scroll.x,sy:a.scroll.y,st:a.getScrollTop(),sl:a.getScrollLeft(),pt:2,dl:!1};if(a.ispage||!a.opt.directionlockdeadzone)a.rail.drag.dl="f";else{var g=f(window).width(),d=f(window).height(),q=Math.max(document.body.scrollWidth,document.documentElement.scrollWidth),
+h=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight),d=Math.max(0,h-d),g=Math.max(0,q-g);a.rail.drag.ck=!a.rail.scrollable&&a.railh.scrollable?0<d?"v":!1:a.rail.scrollable&&!a.railh.scrollable?0<g?"h":!1:!1;a.rail.drag.ck||(a.rail.drag.dl="f")}a.opt.touchbehavior&&a.isiframe&&e.isie&&(g=a.win.position(),a.rail.drag.x+=g.left,a.rail.drag.y+=g.top);a.hasmoving=!1;a.lastmouseup=!1;a.scrollmom.reset(b.clientX,b.clientY);if(!e.cantouch&&!this.istouchcapable&&!b.pointerType){if(!c||
+!/INPUT|SELECT|TEXTAREA/i.test(c.nodeName))return!a.ispage&&e.hasmousecapture&&c.setCapture(),a.opt.touchbehavior?(c.onclick&&!c._onclick&&(c._onclick=c.onclick,c.onclick=function(b){if(a.hasmoving)return!1;c._onclick.call(this,b)}),a.cancelEvent(b)):a.stopPropagation(b);/SUBMIT|CANCEL|BUTTON/i.test(f(c).attr("type"))&&(pc={tg:c,click:!1},a.preventclick=pc)}}};a.ontouchend=function(b){if(!a.rail.drag)return!0;if(2==a.rail.drag.pt){if(b.pointerType&&2!=b.pointerType&&"touch"!=b.pointerType)return!1;
+a.scrollmom.doMomentum();a.rail.drag=!1;if(a.hasmoving&&(a.lastmouseup=!0,a.hideCursor(),e.hasmousecapture&&document.releaseCapture(),!e.cantouch))return a.cancelEvent(b)}else if(1==a.rail.drag.pt)return a.onmouseup(b)};var n=a.opt.touchbehavior&&a.isiframe&&!e.hasmousecapture;a.ontouchmove=function(b,c){if(!a.rail.drag||b.targetTouches&&a.opt.preventmultitouchscrolling&&1<b.targetTouches.length||b.pointerType&&2!=b.pointerType&&"touch"!=b.pointerType)return!1;if(2==a.rail.drag.pt){if(e.cantouch&&
+e.isios&&"undefined"==typeof b.original)return!0;a.hasmoving=!0;a.preventclick&&!a.preventclick.click&&(a.preventclick.click=a.preventclick.tg.onclick||!1,a.preventclick.tg.onclick=a.onpreventclick);b=f.extend({original:b},b);"changedTouches"in b&&(b.clientX=b.changedTouches[0].clientX,b.clientY=b.changedTouches[0].clientY);if(a.forcescreen){var g=b;b={original:b.original?b.original:b};b.clientX=g.screenX;b.clientY=g.screenY}var d,g=d=0;n&&!c&&(d=a.win.position(),g=-d.left,d=-d.top);var q=b.clientY+
+d;d=q-a.rail.drag.y;var h=b.clientX+g,u=h-a.rail.drag.x,k=a.rail.drag.st-d;a.ishwscroll&&a.opt.bouncescroll?0>k?k=Math.round(k/2):k>a.page.maxh&&(k=a.page.maxh+Math.round((k-a.page.maxh)/2)):(0>k&&(q=k=0),k>a.page.maxh&&(k=a.page.maxh,q=0));var l;a.railh&&a.railh.scrollable&&(l=a.isrtlmode?u-a.rail.drag.sl:a.rail.drag.sl-u,a.ishwscroll&&a.opt.bouncescroll?0>l?l=Math.round(l/2):l>a.page.maxw&&(l=a.page.maxw+Math.round((l-a.page.maxw)/2)):(0>l&&(h=l=0),l>a.page.maxw&&(l=a.page.maxw,h=0)));g=!1;if(a.rail.drag.dl)g=
+!0,"v"==a.rail.drag.dl?l=a.rail.drag.sl:"h"==a.rail.drag.dl&&(k=a.rail.drag.st);else{d=Math.abs(d);var u=Math.abs(u),z=a.opt.directionlockdeadzone;if("v"==a.rail.drag.ck){if(d>z&&u<=.3*d)return a.rail.drag=!1,!0;u>z&&(a.rail.drag.dl="f",f("body").scrollTop(f("body").scrollTop()))}else if("h"==a.rail.drag.ck){if(u>z&&d<=.3*u)return a.rail.drag=!1,!0;d>z&&(a.rail.drag.dl="f",f("body").scrollLeft(f("body").scrollLeft()))}}a.synched("touchmove",function(){a.rail.drag&&2==a.rail.drag.pt&&(a.prepareTransition&&
+a.prepareTransition(0),a.rail.scrollable&&a.setScrollTop(k),a.scrollmom.update(h,q),a.railh&&a.railh.scrollable?(a.setScrollLeft(l),a.showCursor(k,l)):a.showCursor(k),e.isie10&&document.selection.clear())});e.ischrome&&a.istouchcapable&&(g=!1);if(g)return a.cancelEvent(b)}else if(1==a.rail.drag.pt)return a.onmousemove(b)}}a.onmousedown=function(b,c){if(!a.rail.drag||1==a.rail.drag.pt){if(a.railslocked)return a.cancelEvent(b);a.cancelScroll();a.rail.drag={x:b.clientX,y:b.clientY,sx:a.scroll.x,sy:a.scroll.y,
+pt:1,hr:!!c};var g=a.getTarget(b);!a.ispage&&e.hasmousecapture&&g.setCapture();a.isiframe&&!e.hasmousecapture&&(a.saved.csspointerevents=a.doc.css("pointer-events"),a.css(a.doc,{"pointer-events":"none"}));a.hasmoving=!1;return a.cancelEvent(b)}};a.onmouseup=function(b){if(a.rail.drag){if(1!=a.rail.drag.pt)return!0;e.hasmousecapture&&document.releaseCapture();a.isiframe&&!e.hasmousecapture&&a.doc.css("pointer-events",a.saved.csspointerevents);a.rail.drag=!1;a.hasmoving&&a.triggerScrollEnd();return a.cancelEvent(b)}};
+a.onmousemove=function(b){if(a.rail.drag&&1==a.rail.drag.pt){if(e.ischrome&&0==b.which)return a.onmouseup(b);a.cursorfreezed=!0;a.hasmoving=!0;if(a.rail.drag.hr){a.scroll.x=a.rail.drag.sx+(b.clientX-a.rail.drag.x);0>a.scroll.x&&(a.scroll.x=0);var c=a.scrollvaluemaxw;a.scroll.x>c&&(a.scroll.x=c)}else a.scroll.y=a.rail.drag.sy+(b.clientY-a.rail.drag.y),0>a.scroll.y&&(a.scroll.y=0),c=a.scrollvaluemax,a.scroll.y>c&&(a.scroll.y=c);a.synched("mousemove",function(){a.rail.drag&&1==a.rail.drag.pt&&(a.showCursor(),
+a.rail.drag.hr?a.hasreversehr?a.doScrollLeft(a.scrollvaluemaxw-Math.round(a.scroll.x*a.scrollratio.x),a.opt.cursordragspeed):a.doScrollLeft(Math.round(a.scroll.x*a.scrollratio.x),a.opt.cursordragspeed):a.doScrollTop(Math.round(a.scroll.y*a.scrollratio.y),a.opt.cursordragspeed))});return a.cancelEvent(b)}};if(e.cantouch||a.opt.touchbehavior)a.onpreventclick=function(b){if(a.preventclick)return a.preventclick.tg.onclick=a.preventclick.click,a.preventclick=!1,a.cancelEvent(b)},a.bind(a.win,"mousedown",
+a.ontouchstart),a.onclick=e.isios?!1:function(b){return a.lastmouseup?(a.lastmouseup=!1,a.cancelEvent(b)):!0},a.opt.grabcursorenabled&&e.cursorgrabvalue&&(a.css(a.ispage?a.doc:a.win,{cursor:e.cursorgrabvalue}),a.css(a.rail,{cursor:e.cursorgrabvalue}));else{var p=function(b){if(a.selectiondrag){if(b){var c=a.win.outerHeight();b=b.pageY-a.selectiondrag.top;0<b&&b<c&&(b=0);b>=c&&(b-=c);a.selectiondrag.df=b}0!=a.selectiondrag.df&&(a.doScrollBy(2*-Math.floor(a.selectiondrag.df/6)),a.debounced("doselectionscroll",
+function(){p()},50))}};a.hasTextSelected="getSelection"in document?function(){return 0<document.getSelection().rangeCount}:"selection"in document?function(){return"None"!=document.selection.type}:function(){return!1};a.onselectionstart=function(b){a.ispage||(a.selectiondrag=a.win.offset())};a.onselectionend=function(b){a.selectiondrag=!1};a.onselectiondrag=function(b){a.selectiondrag&&a.hasTextSelected()&&a.debounced("selectionscroll",function(){p(b)},250)}}e.hasw3ctouch?(a.css(a.rail,{"touch-action":"none"}),
+a.css(a.cursor,{"touch-action":"none"}),a.bind(a.win,"pointerdown",a.ontouchstart),a.bind(document,"pointerup",a.ontouchend),a.bind(document,"pointermove",a.ontouchmove)):e.hasmstouch?(a.css(a.rail,{"-ms-touch-action":"none"}),a.css(a.cursor,{"-ms-touch-action":"none"}),a.bind(a.win,"MSPointerDown",a.ontouchstart),a.bind(document,"MSPointerUp",a.ontouchend),a.bind(document,"MSPointerMove",a.ontouchmove),a.bind(a.cursor,"MSGestureHold",function(a){a.preventDefault()}),a.bind(a.cursor,"contextmenu",
+function(a){a.preventDefault()})):this.istouchcapable&&(a.bind(a.win,"touchstart",a.ontouchstart),a.bind(document,"touchend",a.ontouchend),a.bind(document,"touchcancel",a.ontouchend),a.bind(document,"touchmove",a.ontouchmove));if(a.opt.cursordragontouch||!e.cantouch&&!a.opt.touchbehavior)a.rail.css({cursor:"default"}),a.railh&&a.railh.css({cursor:"default"}),a.jqbind(a.rail,"mouseenter",function(){if(!a.ispage&&!a.win.is(":visible"))return!1;a.canshowonmouseevent&&a.showCursor();a.rail.active=!0}),
+a.jqbind(a.rail,"mouseleave",function(){a.rail.active=!1;a.rail.drag||a.hideCursor()}),a.opt.sensitiverail&&(a.bind(a.rail,"click",function(b){a.doRailClick(b,!1,!1)}),a.bind(a.rail,"dblclick",function(b){a.doRailClick(b,!0,!1)}),a.bind(a.cursor,"click",function(b){a.cancelEvent(b)}),a.bind(a.cursor,"dblclick",function(b){a.cancelEvent(b)})),a.railh&&(a.jqbind(a.railh,"mouseenter",function(){if(!a.ispage&&!a.win.is(":visible"))return!1;a.canshowonmouseevent&&a.showCursor();a.rail.active=!0}),a.jqbind(a.railh,
+"mouseleave",function(){a.rail.active=!1;a.rail.drag||a.hideCursor()}),a.opt.sensitiverail&&(a.bind(a.railh,"click",function(b){a.doRailClick(b,!1,!0)}),a.bind(a.railh,"dblclick",function(b){a.doRailClick(b,!0,!0)}),a.bind(a.cursorh,"click",function(b){a.cancelEvent(b)}),a.bind(a.cursorh,"dblclick",function(b){a.cancelEvent(b)})));e.cantouch||a.opt.touchbehavior?(a.bind(e.hasmousecapture?a.win:document,"mouseup",a.ontouchend),a.bind(document,"mousemove",a.ontouchmove),a.onclick&&a.bind(document,"click",
+a.onclick),a.opt.cursordragontouch&&(a.bind(a.cursor,"mousedown",a.onmousedown),a.bind(a.cursor,"mouseup",a.onmouseup),a.cursorh&&a.bind(a.cursorh,"mousedown",function(b){a.onmousedown(b,!0)}),a.cursorh&&a.bind(a.cursorh,"mouseup",a.onmouseup))):(a.bind(e.hasmousecapture?a.win:document,"mouseup",a.onmouseup),a.bind(document,"mousemove",a.onmousemove),a.onclick&&a.bind(document,"click",a.onclick),a.bind(a.cursor,"mousedown",a.onmousedown),a.bind(a.cursor,"mouseup",a.onmouseup),a.railh&&(a.bind(a.cursorh,
+"mousedown",function(b){a.onmousedown(b,!0)}),a.bind(a.cursorh,"mouseup",a.onmouseup)),!a.ispage&&a.opt.enablescrollonselection&&(a.bind(a.win[0],"mousedown",a.onselectionstart),a.bind(document,"mouseup",a.onselectionend),a.bind(a.cursor,"mouseup",a.onselectionend),a.cursorh&&a.bind(a.cursorh,"mouseup",a.onselectionend),a.bind(document,"mousemove",a.onselectiondrag)),a.zoom&&(a.jqbind(a.zoom,"mouseenter",function(){a.canshowonmouseevent&&a.showCursor();a.rail.active=!0}),a.jqbind(a.zoom,"mouseleave",
+function(){a.rail.active=!1;a.rail.drag||a.hideCursor()})));a.opt.enablemousewheel&&(a.isiframe||a.bind(e.isie&&a.ispage?document:a.win,"mousewheel",a.onmousewheel),a.bind(a.rail,"mousewheel",a.onmousewheel),a.railh&&a.bind(a.railh,"mousewheel",a.onmousewheelhr));a.ispage||e.cantouch||/HTML|^BODY/.test(a.win[0].nodeName)||(a.win.attr("tabindex")||a.win.attr({tabindex:N++}),a.jqbind(a.win,"focus",function(b){y=a.getTarget(b).id||!0;a.hasfocus=!0;a.canshowonmouseevent&&a.noticeCursor()}),a.jqbind(a.win,
+"blur",function(b){y=!1;a.hasfocus=!1}),a.jqbind(a.win,"mouseenter",function(b){D=a.getTarget(b).id||!0;a.hasmousefocus=!0;a.canshowonmouseevent&&a.noticeCursor()}),a.jqbind(a.win,"mouseleave",function(){D=!1;a.hasmousefocus=!1;a.rail.drag||a.hideCursor()}))}a.onkeypress=function(b){if(a.railslocked&&0==a.page.maxh)return!0;b=b?b:window.e;var c=a.getTarget(b);if(c&&/INPUT|TEXTAREA|SELECT|OPTION/.test(c.nodeName)&&(!c.getAttribute("type")&&!c.type||!/submit|button|cancel/i.tp)||f(c).attr("contenteditable"))return!0;
+if(a.hasfocus||a.hasmousefocus&&!y||a.ispage&&!y&&!D){c=b.keyCode;if(a.railslocked&&27!=c)return a.cancelEvent(b);var g=b.ctrlKey||!1,d=b.shiftKey||!1,e=!1;switch(c){case 38:case 63233:a.doScrollBy(72);e=!0;break;case 40:case 63235:a.doScrollBy(-72);e=!0;break;case 37:case 63232:a.railh&&(g?a.doScrollLeft(0):a.doScrollLeftBy(72),e=!0);break;case 39:case 63234:a.railh&&(g?a.doScrollLeft(a.page.maxw):a.doScrollLeftBy(-72),e=!0);break;case 33:case 63276:a.doScrollBy(a.view.h);e=!0;break;case 34:case 63277:a.doScrollBy(-a.view.h);
+e=!0;break;case 36:case 63273:a.railh&&g?a.doScrollPos(0,0):a.doScrollTo(0);e=!0;break;case 35:case 63275:a.railh&&g?a.doScrollPos(a.page.maxw,a.page.maxh):a.doScrollTo(a.page.maxh);e=!0;break;case 32:a.opt.spacebarenabled&&(d?a.doScrollBy(a.view.h):a.doScrollBy(-a.view.h),e=!0);break;case 27:a.zoomactive&&(a.doZoom(),e=!0)}if(e)return a.cancelEvent(b)}};a.opt.enablekeyboard&&a.bind(document,e.isopera&&!e.isopera12?"keypress":"keydown",a.onkeypress);a.bind(document,"keydown",function(b){b.ctrlKey&&
+(a.wheelprevented=!0)});a.bind(document,"keyup",function(b){b.ctrlKey||(a.wheelprevented=!1)});a.bind(window,"blur",function(b){a.wheelprevented=!1});a.bind(window,"resize",a.lazyResize);a.bind(window,"orientationchange",a.lazyResize);a.bind(window,"load",a.lazyResize);if(e.ischrome&&!a.ispage&&!a.haswrapper){var r=a.win.attr("style"),c=parseFloat(a.win.css("width"))+1;a.win.css("width",c);a.synched("chromefix",function(){a.win.attr("style",r)})}a.onAttributeChange=function(b){a.lazyResize(a.isieold?
+250:30)};!1!==v&&(a.observerbody=new v(function(b){b.forEach(function(b){if("attributes"==b.type)return f("body").hasClass("modal-open")?a.hide():a.show()});if(document.body.scrollHeight!=a.page.maxh)return a.lazyResize(30)}),a.observerbody.observe(document.body,{childList:!0,subtree:!0,characterData:!1,attributes:!0,attributeFilter:["class"]}));a.ispage||a.haswrapper||(!1!==v?(a.observer=new v(function(b){b.forEach(a.onAttributeChange)}),a.observer.observe(a.win[0],{childList:!0,characterData:!1,
+attributes:!0,subtree:!1}),a.observerremover=new v(function(b){b.forEach(function(b){if(0<b.removedNodes.length)for(var c in b.removedNodes)if(a&&b.removedNodes[c]==a.win[0])return a.remove()})}),a.observerremover.observe(a.win[0].parentNode,{childList:!0,characterData:!1,attributes:!1,subtree:!1})):(a.bind(a.win,e.isie&&!e.isie9?"propertychange":"DOMAttrModified",a.onAttributeChange),e.isie9&&a.win[0].attachEvent("onpropertychange",a.onAttributeChange),a.bind(a.win,"DOMNodeRemoved",function(b){b.target==
+a.win[0]&&a.remove()})));!a.ispage&&a.opt.boxzoom&&a.bind(window,"resize",a.resizeZoom);a.istextarea&&a.bind(a.win,"mouseup",a.lazyResize);a.lazyResize(30)}if("IFRAME"==this.doc[0].nodeName){var M=function(){a.iframexd=!1;var b;try{b="contentDocument"in this?this.contentDocument:this.contentWindow.document}catch(c){a.iframexd=!0,b=!1}if(a.iframexd)return"console"in window&&console.log("NiceScroll error: policy restriced iframe"),!0;a.forcescreen=!0;a.isiframe&&(a.iframe={doc:f(b),html:a.doc.contents().find("html")[0],
+body:a.doc.contents().find("body")[0]},a.getContentSize=function(){return{w:Math.max(a.iframe.html.scrollWidth,a.iframe.body.scrollWidth),h:Math.max(a.iframe.html.scrollHeight,a.iframe.body.scrollHeight)}},a.docscroll=f(a.iframe.body));if(!e.isios&&a.opt.iframeautoresize&&!a.isiframe){a.win.scrollTop(0);a.doc.height("");var g=Math.max(b.getElementsByTagName("html")[0].scrollHeight,b.body.scrollHeight);a.doc.height(g)}a.lazyResize(30);e.isie7&&a.css(f(a.iframe.html),{"overflow-y":"hidden"});a.css(f(a.iframe.body),
+{"overflow-y":"hidden"});e.isios&&a.haswrapper&&a.css(f(b.body),{"-webkit-transform":"translate3d(0,0,0)"});"contentWindow"in this?a.bind(this.contentWindow,"scroll",a.onscroll):a.bind(b,"scroll",a.onscroll);a.opt.enablemousewheel&&a.bind(b,"mousewheel",a.onmousewheel);a.opt.enablekeyboard&&a.bind(b,e.isopera?"keypress":"keydown",a.onkeypress);if(e.cantouch||a.opt.touchbehavior)a.bind(b,"mousedown",a.ontouchstart),a.bind(b,"mousemove",function(b){return a.ontouchmove(b,!0)}),a.opt.grabcursorenabled&&
+e.cursorgrabvalue&&a.css(f(b.body),{cursor:e.cursorgrabvalue});a.bind(b,"mouseup",a.ontouchend);a.zoom&&(a.opt.dblclickzoom&&a.bind(b,"dblclick",a.doZoom),a.ongesturezoom&&a.bind(b,"gestureend",a.ongesturezoom))};this.doc[0].readyState&&"complete"==this.doc[0].readyState&&setTimeout(function(){M.call(a.doc[0],!1)},500);a.bind(this.doc,"load",M)}};this.showCursor=function(b,c){a.cursortimeout&&(clearTimeout(a.cursortimeout),a.cursortimeout=0);if(a.rail){a.autohidedom&&(a.autohidedom.stop().css({opacity:a.opt.cursoropacitymax}),
+a.cursoractive=!0);a.rail.drag&&1==a.rail.drag.pt||("undefined"!=typeof b&&!1!==b&&(a.scroll.y=Math.round(1*b/a.scrollratio.y)),"undefined"!=typeof c&&(a.scroll.x=Math.round(1*c/a.scrollratio.x)));a.cursor.css({height:a.cursorheight,top:a.scroll.y});if(a.cursorh){var d=a.hasreversehr?a.scrollvaluemaxw-a.scroll.x:a.scroll.x;!a.rail.align&&a.rail.visibility?a.cursorh.css({width:a.cursorwidth,left:d+a.rail.width}):a.cursorh.css({width:a.cursorwidth,left:d});a.cursoractive=!0}a.zoom&&a.zoom.stop().css({opacity:a.opt.cursoropacitymax})}};
+this.hideCursor=function(b){a.cursortimeout||!a.rail||!a.autohidedom||a.hasmousefocus&&"leave"==a.opt.autohidemode||(a.cursortimeout=setTimeout(function(){a.rail.active&&a.showonmouseevent||(a.autohidedom.stop().animate({opacity:a.opt.cursoropacitymin}),a.zoom&&a.zoom.stop().animate({opacity:a.opt.cursoropacitymin}),a.cursoractive=!1);a.cursortimeout=0},b||a.opt.hidecursordelay))};this.noticeCursor=function(b,c,d){a.showCursor(c,d);a.rail.active||a.hideCursor(b)};this.getContentSize=a.ispage?function(){return{w:Math.max(document.body.scrollWidth,
+document.documentElement.scrollWidth),h:Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}}:a.haswrapper?function(){return{w:a.doc.outerWidth()+parseInt(a.win.css("paddingLeft"))+parseInt(a.win.css("paddingRight")),h:a.doc.outerHeight()+parseInt(a.win.css("paddingTop"))+parseInt(a.win.css("paddingBottom"))}}:function(){return{w:a.docscroll[0].scrollWidth,h:a.docscroll[0].scrollHeight}};this.onResize=function(b,c){if(!a||!a.win)return!1;if(!a.haswrapper&&!a.ispage){if("none"==
+a.win.css("display"))return a.visibility&&a.hideRail().hideRailHr(),!1;a.hidden||a.visibility||a.showRail().showRailHr()}var d=a.page.maxh,e=a.page.maxw,f=a.view.h,h=a.view.w;a.view={w:a.ispage?a.win.width():parseInt(a.win[0].clientWidth),h:a.ispage?a.win.height():parseInt(a.win[0].clientHeight)};a.page=c?c:a.getContentSize();a.page.maxh=Math.max(0,a.page.h-a.view.h);a.page.maxw=Math.max(0,a.page.w-a.view.w);if(a.page.maxh==d&&a.page.maxw==e&&a.view.w==h&&a.view.h==f){if(a.ispage)return a;d=a.win.offset();
+if(a.lastposition&&(e=a.lastposition,e.top==d.top&&e.left==d.left))return a;a.lastposition=d}0==a.page.maxh?(a.hideRail(),a.scrollvaluemax=0,a.scroll.y=0,a.scrollratio.y=0,a.cursorheight=0,a.setScrollTop(0),a.rail.scrollable=!1):(a.page.maxh-=a.opt.railpadding.top+a.opt.railpadding.bottom,a.rail.scrollable=!0);0==a.page.maxw?(a.hideRailHr(),a.scrollvaluemaxw=0,a.scroll.x=0,a.scrollratio.x=0,a.cursorwidth=0,a.setScrollLeft(0),a.railh.scrollable=!1):(a.page.maxw-=a.opt.railpadding.left+a.opt.railpadding.right,
+a.railh.scrollable=!0);a.railslocked=a.locked||0==a.page.maxh&&0==a.page.maxw;if(a.railslocked)return a.ispage||a.updateScrollBar(a.view),!1;a.hidden||a.visibility?a.hidden||a.railh.visibility||a.showRailHr():a.showRail().showRailHr();a.istextarea&&a.win.css("resize")&&"none"!=a.win.css("resize")&&(a.view.h-=20);a.cursorheight=Math.min(a.view.h,Math.round(a.view.h/a.page.h*a.view.h));a.cursorheight=a.opt.cursorfixedheight?a.opt.cursorfixedheight:Math.max(a.opt.cursorminheight,a.cursorheight);a.cursorwidth=
+Math.min(a.view.w,Math.round(a.view.w/a.page.w*a.view.w));a.cursorwidth=a.opt.cursorfixedheight?a.opt.cursorfixedheight:Math.max(a.opt.cursorminheight,a.cursorwidth);a.scrollvaluemax=a.view.h-a.cursorheight-a.cursor.hborder-(a.opt.railpadding.top+a.opt.railpadding.bottom);a.railh&&(a.railh.width=0<a.page.maxh?a.view.w-a.rail.width:a.view.w,a.scrollvaluemaxw=a.railh.width-a.cursorwidth-a.cursorh.wborder-(a.opt.railpadding.left+a.opt.railpadding.right));a.ispage||a.updateScrollBar(a.view);a.scrollratio=
+{x:a.page.maxw/a.scrollvaluemaxw,y:a.page.maxh/a.scrollvaluemax};a.getScrollTop()>a.page.maxh?a.doScrollTop(a.page.maxh):(a.scroll.y=Math.round(a.getScrollTop()*(1/a.scrollratio.y)),a.scroll.x=Math.round(a.getScrollLeft()*(1/a.scrollratio.x)),a.cursoractive&&a.noticeCursor());a.scroll.y&&0==a.getScrollTop()&&a.doScrollTo(Math.floor(a.scroll.y*a.scrollratio.y));return a};this.resize=a.onResize;this.lazyResize=function(b){b=isNaN(b)?30:b;a.debounced("resize",a.resize,b);return a};this.jqbind=function(b,
+c,d){a.events.push({e:b,n:c,f:d,q:!0});f(b).bind(c,d)};this.bind=function(b,c,d,f){var h="jquery"in b?b[0]:b;"mousewheel"==c?window.addEventListener||"onwheel"in document?a._bind(h,"wheel",d,f||!1):(b="undefined"!=typeof document.onmousewheel?"mousewheel":"DOMMouseScroll",n(h,b,d,f||!1),"DOMMouseScroll"==b&&n(h,"MozMousePixelScroll",d,f||!1)):h.addEventListener?(e.cantouch&&/mouseup|mousedown|mousemove/.test(c)&&a._bind(h,"mousedown"==c?"touchstart":"mouseup"==c?"touchend":"touchmove",function(a){if(a.touches){if(2>
+a.touches.length){var b=a.touches.length?a.touches[0]:a;b.original=a;d.call(this,b)}}else a.changedTouches&&(b=a.changedTouches[0],b.original=a,d.call(this,b))},f||!1),a._bind(h,c,d,f||!1),e.cantouch&&"mouseup"==c&&a._bind(h,"touchcancel",d,f||!1)):a._bind(h,c,function(b){(b=b||window.event||!1)&&b.srcElement&&(b.target=b.srcElement);"pageY"in b||(b.pageX=b.clientX+document.documentElement.scrollLeft,b.pageY=b.clientY+document.documentElement.scrollTop);return!1===d.call(h,b)||!1===f?a.cancelEvent(b):
+!0})};e.haseventlistener?(this._bind=function(b,c,d,e){a.events.push({e:b,n:c,f:d,b:e,q:!1});b.addEventListener(c,d,e||!1)},this.cancelEvent=function(a){if(!a)return!1;a=a.original?a.original:a;a.preventDefault();a.stopPropagation();a.preventManipulation&&a.preventManipulation();return!1},this.stopPropagation=function(a){if(!a)return!1;a=a.original?a.original:a;a.stopPropagation();return!1},this._unbind=function(a,c,d,e){a.removeEventListener(c,d,e)}):(this._bind=function(b,c,d,e){a.events.push({e:b,
+n:c,f:d,b:e,q:!1});b.attachEvent?b.attachEvent("on"+c,d):b["on"+c]=d},this.cancelEvent=function(a){a=window.event||!1;if(!a)return!1;a.cancelBubble=!0;a.cancel=!0;return a.returnValue=!1},this.stopPropagation=function(a){a=window.event||!1;if(!a)return!1;a.cancelBubble=!0;return!1},this._unbind=function(a,c,d,e){a.detachEvent?a.detachEvent("on"+c,d):a["on"+c]=!1});this.unbindAll=function(){for(var b=0;b<a.events.length;b++){var c=a.events[b];c.q?c.e.unbind(c.n,c.f):a._unbind(c.e,c.n,c.f,c.b)}};this.showRail=
+function(){0==a.page.maxh||!a.ispage&&"none"==a.win.css("display")||(a.visibility=!0,a.rail.visibility=!0,a.rail.css("display","block"));return a};this.showRailHr=function(){if(!a.railh)return a;0==a.page.maxw||!a.ispage&&"none"==a.win.css("display")||(a.railh.visibility=!0,a.railh.css("display","block"));return a};this.hideRail=function(){a.visibility=!1;a.rail.visibility=!1;a.rail.css("display","none");return a};this.hideRailHr=function(){if(!a.railh)return a;a.railh.visibility=!1;a.railh.css("display",
+"none");return a};this.show=function(){a.hidden=!1;a.railslocked=!1;return a.showRail().showRailHr()};this.hide=function(){a.hidden=!0;a.railslocked=!0;return a.hideRail().hideRailHr()};this.toggle=function(){return a.hidden?a.show():a.hide()};this.remove=function(){a.stop();a.cursortimeout&&clearTimeout(a.cursortimeout);a.doZoomOut();a.unbindAll();e.isie9&&a.win[0].detachEvent("onpropertychange",a.onAttributeChange);!1!==a.observer&&a.observer.disconnect();!1!==a.observerremover&&a.observerremover.disconnect();
+!1!==a.observerbody&&a.observerbody.disconnect();a.events=null;a.cursor&&a.cursor.remove();a.cursorh&&a.cursorh.remove();a.rail&&a.rail.remove();a.railh&&a.railh.remove();a.zoom&&a.zoom.remove();for(var b=0;b<a.saved.css.length;b++){var c=a.saved.css[b];c[0].css(c[1],"undefined"==typeof c[2]?"":c[2])}a.saved=!1;a.me.data("__nicescroll","");var d=f.nicescroll;d.each(function(b){if(this&&this.id===a.id){delete d[b];for(var c=++b;c<d.length;c++,b++)d[b]=d[c];d.length--;d.length&&delete d[d.length]}});
+for(var h in a)a[h]=null,delete a[h];a=null};this.scrollstart=function(b){this.onscrollstart=b;return a};this.scrollend=function(b){this.onscrollend=b;return a};this.scrollcancel=function(b){this.onscrollcancel=b;return a};this.zoomin=function(b){this.onzoomin=b;return a};this.zoomout=function(b){this.onzoomout=b;return a};this.isScrollable=function(a){a=a.target?a.target:a;if("OPTION"==a.nodeName)return!0;for(;a&&1==a.nodeType&&!/^BODY|HTML/.test(a.nodeName);){var c=f(a),c=c.css("overflowY")||c.css("overflowX")||
+c.css("overflow")||"";if(/scroll|auto/.test(c))return a.clientHeight!=a.scrollHeight;a=a.parentNode?a.parentNode:!1}return!1};this.getViewport=function(a){for(a=a&&a.parentNode?a.parentNode:!1;a&&1==a.nodeType&&!/^BODY|HTML/.test(a.nodeName);){var c=f(a);if(/fixed|absolute/.test(c.css("position")))return c;var d=c.css("overflowY")||c.css("overflowX")||c.css("overflow")||"";if(/scroll|auto/.test(d)&&a.clientHeight!=a.scrollHeight||0<c.getNiceScroll().length)return c;a=a.parentNode?a.parentNode:!1}return!1};
+this.triggerScrollEnd=function(){if(a.onscrollend){var b=a.getScrollLeft(),c=a.getScrollTop();a.onscrollend.call(a,{type:"scrollend",current:{x:b,y:c},end:{x:b,y:c}})}};this.onmousewheel=function(b){if(!a.wheelprevented){if(a.railslocked)return a.debounced("checkunlock",a.resize,250),!0;if(a.rail.drag)return a.cancelEvent(b);"auto"==a.opt.oneaxismousemode&&0!=b.deltaX&&(a.opt.oneaxismousemode=!1);if(a.opt.oneaxismousemode&&0==b.deltaX&&!a.rail.scrollable)return a.railh&&a.railh.scrollable?a.onmousewheelhr(b):
+!0;var c=+new Date,d=!1;a.opt.preservenativescrolling&&a.checkarea+600<c&&(a.nativescrollingarea=a.isScrollable(b),d=!0);a.checkarea=c;if(a.nativescrollingarea)return!0;if(b=p(b,!1,d))a.checkarea=0;return b}};this.onmousewheelhr=function(b){if(!a.wheelprevented){if(a.railslocked||!a.railh.scrollable)return!0;if(a.rail.drag)return a.cancelEvent(b);var c=+new Date,d=!1;a.opt.preservenativescrolling&&a.checkarea+600<c&&(a.nativescrollingarea=a.isScrollable(b),d=!0);a.checkarea=c;return a.nativescrollingarea?
+!0:a.railslocked?a.cancelEvent(b):p(b,!0,d)}};this.stop=function(){a.cancelScroll();a.scrollmon&&a.scrollmon.stop();a.cursorfreezed=!1;a.scroll.y=Math.round(a.getScrollTop()*(1/a.scrollratio.y));a.noticeCursor();return a};this.getTransitionSpeed=function(b){var c=Math.round(10*a.opt.scrollspeed);b=Math.min(c,Math.round(b/20*a.opt.scrollspeed));return 20<b?b:0};a.opt.smoothscroll?a.ishwscroll&&e.hastransition&&a.opt.usetransition&&a.opt.smoothscroll?(this.prepareTransition=function(b,c){var d=c?20<
+b?b:0:a.getTransitionSpeed(b),f=d?e.prefixstyle+"transform "+d+"ms ease-out":"";a.lasttransitionstyle&&a.lasttransitionstyle==f||(a.lasttransitionstyle=f,a.doc.css(e.transitionstyle,f));return d},this.doScrollLeft=function(b,c){var d=a.scrollrunning?a.newscrolly:a.getScrollTop();a.doScrollPos(b,d,c)},this.doScrollTop=function(b,c){var d=a.scrollrunning?a.newscrollx:a.getScrollLeft();a.doScrollPos(d,b,c)},this.doScrollPos=function(b,c,d){var f=a.getScrollTop(),h=a.getScrollLeft();(0>(a.newscrolly-
+f)*(c-f)||0>(a.newscrollx-h)*(b-h))&&a.cancelScroll();0==a.opt.bouncescroll&&(0>c?c=0:c>a.page.maxh&&(c=a.page.maxh),0>b?b=0:b>a.page.maxw&&(b=a.page.maxw));if(a.scrollrunning&&b==a.newscrollx&&c==a.newscrolly)return!1;a.newscrolly=c;a.newscrollx=b;a.newscrollspeed=d||!1;if(a.timer)return!1;a.timer=setTimeout(function(){var d=a.getScrollTop(),f=a.getScrollLeft(),h,k;h=b-f;k=c-d;h=Math.round(Math.sqrt(Math.pow(h,2)+Math.pow(k,2)));h=a.newscrollspeed&&1<a.newscrollspeed?a.newscrollspeed:a.getTransitionSpeed(h);
+a.newscrollspeed&&1>=a.newscrollspeed&&(h*=a.newscrollspeed);a.prepareTransition(h,!0);a.timerscroll&&a.timerscroll.tm&&clearInterval(a.timerscroll.tm);0<h&&(!a.scrollrunning&&a.onscrollstart&&a.onscrollstart.call(a,{type:"scrollstart",current:{x:f,y:d},request:{x:b,y:c},end:{x:a.newscrollx,y:a.newscrolly},speed:h}),e.transitionend?a.scrollendtrapped||(a.scrollendtrapped=!0,a.bind(a.doc,e.transitionend,a.onScrollTransitionEnd,!1)):(a.scrollendtrapped&&clearTimeout(a.scrollendtrapped),a.scrollendtrapped=
+setTimeout(a.onScrollTransitionEnd,h)),a.timerscroll={bz:new A(d,a.newscrolly,h,0,0,.58,1),bh:new A(f,a.newscrollx,h,0,0,.58,1)},a.cursorfreezed||(a.timerscroll.tm=setInterval(function(){a.showCursor(a.getScrollTop(),a.getScrollLeft())},60)));a.synched("doScroll-set",function(){a.timer=0;a.scrollendtrapped&&(a.scrollrunning=!0);a.setScrollTop(a.newscrolly);a.setScrollLeft(a.newscrollx);if(!a.scrollendtrapped)a.onScrollTransitionEnd()})},50)},this.cancelScroll=function(){if(!a.scrollendtrapped)return!0;
+var b=a.getScrollTop(),c=a.getScrollLeft();a.scrollrunning=!1;e.transitionend||clearTimeout(e.transitionend);a.scrollendtrapped=!1;a._unbind(a.doc[0],e.transitionend,a.onScrollTransitionEnd);a.prepareTransition(0);a.setScrollTop(b);a.railh&&a.setScrollLeft(c);a.timerscroll&&a.timerscroll.tm&&clearInterval(a.timerscroll.tm);a.timerscroll=!1;a.cursorfreezed=!1;a.showCursor(b,c);return a},this.onScrollTransitionEnd=function(){a.scrollendtrapped&&a._unbind(a.doc[0],e.transitionend,a.onScrollTransitionEnd);
+a.scrollendtrapped=!1;a.prepareTransition(0);a.timerscroll&&a.timerscroll.tm&&clearInterval(a.timerscroll.tm);a.timerscroll=!1;var b=a.getScrollTop(),c=a.getScrollLeft();a.setScrollTop(b);a.railh&&a.setScrollLeft(c);a.noticeCursor(!1,b,c);a.cursorfreezed=!1;0>b?b=0:b>a.page.maxh&&(b=a.page.maxh);0>c?c=0:c>a.page.maxw&&(c=a.page.maxw);if(b!=a.newscrolly||c!=a.newscrollx)return a.doScrollPos(c,b,a.opt.snapbackspeed);a.onscrollend&&a.scrollrunning&&a.triggerScrollEnd();a.scrollrunning=!1}):(this.doScrollLeft=
+function(b,c){var d=a.scrollrunning?a.newscrolly:a.getScrollTop();a.doScrollPos(b,d,c)},this.doScrollTop=function(b,c){var d=a.scrollrunning?a.newscrollx:a.getScrollLeft();a.doScrollPos(d,b,c)},this.doScrollPos=function(b,c,d){function e(){if(a.cancelAnimationFrame)return!0;a.scrollrunning=!0;if(n=1-n)return a.timer=s(e)||1;var b=0,c,d,g=d=a.getScrollTop();if(a.dst.ay){g=a.bzscroll?a.dst.py+a.bzscroll.getNow()*a.dst.ay:a.newscrolly;c=g-d;if(0>c&&g<a.newscrolly||0<c&&g>a.newscrolly)g=a.newscrolly;
+a.setScrollTop(g);g==a.newscrolly&&(b=1)}else b=1;d=c=a.getScrollLeft();if(a.dst.ax){d=a.bzscroll?a.dst.px+a.bzscroll.getNow()*a.dst.ax:a.newscrollx;c=d-c;if(0>c&&d<a.newscrollx||0<c&&d>a.newscrollx)d=a.newscrollx;a.setScrollLeft(d);d==a.newscrollx&&(b+=1)}else b+=1;2==b?(a.timer=0,a.cursorfreezed=!1,a.bzscroll=!1,a.scrollrunning=!1,0>g?g=0:g>a.page.maxh&&(g=a.page.maxh),0>d?d=0:d>a.page.maxw&&(d=a.page.maxw),d!=a.newscrollx||g!=a.newscrolly?a.doScrollPos(d,g):a.onscrollend&&a.triggerScrollEnd()):
+a.timer=s(e)||1}c="undefined"==typeof c||!1===c?a.getScrollTop(!0):c;if(a.timer&&a.newscrolly==c&&a.newscrollx==b)return!0;a.timer&&t(a.timer);a.timer=0;var f=a.getScrollTop(),h=a.getScrollLeft();(0>(a.newscrolly-f)*(c-f)||0>(a.newscrollx-h)*(b-h))&&a.cancelScroll();a.newscrolly=c;a.newscrollx=b;a.bouncescroll&&a.rail.visibility||(0>a.newscrolly?a.newscrolly=0:a.newscrolly>a.page.maxh&&(a.newscrolly=a.page.maxh));a.bouncescroll&&a.railh.visibility||(0>a.newscrollx?a.newscrollx=0:a.newscrollx>a.page.maxw&&
+(a.newscrollx=a.page.maxw));a.dst={};a.dst.x=b-h;a.dst.y=c-f;a.dst.px=h;a.dst.py=f;var k=Math.round(Math.sqrt(Math.pow(a.dst.x,2)+Math.pow(a.dst.y,2)));a.dst.ax=a.dst.x/k;a.dst.ay=a.dst.y/k;var l=0,m=k;0==a.dst.x?(l=f,m=c,a.dst.ay=1,a.dst.py=0):0==a.dst.y&&(l=h,m=b,a.dst.ax=1,a.dst.px=0);k=a.getTransitionSpeed(k);d&&1>=d&&(k*=d);a.bzscroll=0<k?a.bzscroll?a.bzscroll.update(m,k):new A(l,m,k,0,1,0,1):!1;if(!a.timer){(f==a.page.maxh&&c>=a.page.maxh||h==a.page.maxw&&b>=a.page.maxw)&&a.checkContentSize();
+var n=1;a.cancelAnimationFrame=!1;a.timer=1;a.onscrollstart&&!a.scrollrunning&&a.onscrollstart.call(a,{type:"scrollstart",current:{x:h,y:f},request:{x:b,y:c},end:{x:a.newscrollx,y:a.newscrolly},speed:k});e();(f==a.page.maxh&&c>=f||h==a.page.maxw&&b>=h)&&a.checkContentSize();a.noticeCursor()}},this.cancelScroll=function(){a.timer&&t(a.timer);a.timer=0;a.bzscroll=!1;a.scrollrunning=!1;return a}):(this.doScrollLeft=function(b,c){var d=a.getScrollTop();a.doScrollPos(b,d,c)},this.doScrollTop=function(b,
+c){var d=a.getScrollLeft();a.doScrollPos(d,b,c)},this.doScrollPos=function(b,c,d){var e=b>a.page.maxw?a.page.maxw:b;0>e&&(e=0);var f=c>a.page.maxh?a.page.maxh:c;0>f&&(f=0);a.synched("scroll",function(){a.setScrollTop(f);a.setScrollLeft(e)})},this.cancelScroll=function(){});this.doScrollBy=function(b,c){var d=0,d=c?Math.floor((a.scroll.y-b)*a.scrollratio.y):(a.timer?a.newscrolly:a.getScrollTop(!0))-b;if(a.bouncescroll){var e=Math.round(a.view.h/2);d<-e?d=-e:d>a.page.maxh+e&&(d=a.page.maxh+e)}a.cursorfreezed=
+!1;e=a.getScrollTop(!0);if(0>d&&0>=e)return a.noticeCursor();if(d>a.page.maxh&&e>=a.page.maxh)return a.checkContentSize(),a.noticeCursor();a.doScrollTop(d)};this.doScrollLeftBy=function(b,c){var d=0,d=c?Math.floor((a.scroll.x-b)*a.scrollratio.x):(a.timer?a.newscrollx:a.getScrollLeft(!0))-b;if(a.bouncescroll){var e=Math.round(a.view.w/2);d<-e?d=-e:d>a.page.maxw+e&&(d=a.page.maxw+e)}a.cursorfreezed=!1;e=a.getScrollLeft(!0);if(0>d&&0>=e||d>a.page.maxw&&e>=a.page.maxw)return a.noticeCursor();a.doScrollLeft(d)};
+this.doScrollTo=function(b,c){c&&Math.round(b*a.scrollratio.y);a.cursorfreezed=!1;a.doScrollTop(b)};this.checkContentSize=function(){var b=a.getContentSize();b.h==a.page.h&&b.w==a.page.w||a.resize(!1,b)};a.onscroll=function(b){a.rail.drag||a.cursorfreezed||a.synched("scroll",function(){a.scroll.y=Math.round(a.getScrollTop()*(1/a.scrollratio.y));a.railh&&(a.scroll.x=Math.round(a.getScrollLeft()*(1/a.scrollratio.x)));a.noticeCursor()})};a.bind(a.docscroll,"scroll",a.onscroll);this.doZoomIn=function(b){if(!a.zoomactive){a.zoomactive=
+!0;a.zoomrestore={style:{}};var c="position top left zIndex backgroundColor marginTop marginBottom marginLeft marginRight".split(" "),d=a.win[0].style,h;for(h in c){var k=c[h];a.zoomrestore.style[k]="undefined"!=typeof d[k]?d[k]:""}a.zoomrestore.style.width=a.win.css("width");a.zoomrestore.style.height=a.win.css("height");a.zoomrestore.padding={w:a.win.outerWidth()-a.win.width(),h:a.win.outerHeight()-a.win.height()};e.isios4&&(a.zoomrestore.scrollTop=f(window).scrollTop(),f(window).scrollTop(0));
+a.win.css({position:e.isios4?"absolute":"fixed",top:0,left:0,"z-index":x+100,margin:"0px"});c=a.win.css("backgroundColor");(""==c||/transparent|rgba\(0, 0, 0, 0\)|rgba\(0,0,0,0\)/.test(c))&&a.win.css("backgroundColor","#fff");a.rail.css({"z-index":x+101});a.zoom.css({"z-index":x+102});a.zoom.css("backgroundPosition","0px -18px");a.resizeZoom();a.onzoomin&&a.onzoomin.call(a);return a.cancelEvent(b)}};this.doZoomOut=function(b){if(a.zoomactive)return a.zoomactive=!1,a.win.css("margin",""),a.win.css(a.zoomrestore.style),
+e.isios4&&f(window).scrollTop(a.zoomrestore.scrollTop),a.rail.css({"z-index":a.zindex}),a.zoom.css({"z-index":a.zindex}),a.zoomrestore=!1,a.zoom.css("backgroundPosition","0px 0px"),a.onResize(),a.onzoomout&&a.onzoomout.call(a),a.cancelEvent(b)};this.doZoom=function(b){return a.zoomactive?a.doZoomOut(b):a.doZoomIn(b)};this.resizeZoom=function(){if(a.zoomactive){var b=a.getScrollTop();a.win.css({width:f(window).width()-a.zoomrestore.padding.w+"px",height:f(window).height()-a.zoomrestore.padding.h+"px"});
+a.onResize();a.setScrollTop(Math.min(a.page.maxh,b))}};this.init();f.nicescroll.push(this)},L=function(f){var c=this;this.nc=f;this.steptime=this.lasttime=this.speedy=this.speedx=this.lasty=this.lastx=0;this.snapy=this.snapx=!1;this.demuly=this.demulx=0;this.lastscrolly=this.lastscrollx=-1;this.timer=this.chky=this.chkx=0;this.time=function(){return+new Date};this.reset=function(f,k){c.stop();var d=c.time();c.steptime=0;c.lasttime=d;c.speedx=0;c.speedy=0;c.lastx=f;c.lasty=k;c.lastscrollx=-1;c.lastscrolly=
+-1};this.update=function(f,k){var d=c.time();c.steptime=d-c.lasttime;c.lasttime=d;var d=k-c.lasty,n=f-c.lastx,p=c.nc.getScrollTop(),a=c.nc.getScrollLeft(),p=p+d,a=a+n;c.snapx=0>a||a>c.nc.page.maxw;c.snapy=0>p||p>c.nc.page.maxh;c.speedx=n;c.speedy=d;c.lastx=f;c.lasty=k};this.stop=function(){c.nc.unsynched("domomentum2d");c.timer&&clearTimeout(c.timer);c.timer=0;c.lastscrollx=-1;c.lastscrolly=-1};this.doSnapy=function(f,k){var d=!1;0>k?(k=0,d=!0):k>c.nc.page.maxh&&(k=c.nc.page.maxh,d=!0);0>f?(f=0,d=
+!0):f>c.nc.page.maxw&&(f=c.nc.page.maxw,d=!0);d?c.nc.doScrollPos(f,k,c.nc.opt.snapbackspeed):c.nc.triggerScrollEnd()};this.doMomentum=function(f){var k=c.time(),d=f?k+f:c.lasttime;f=c.nc.getScrollLeft();var n=c.nc.getScrollTop(),p=c.nc.page.maxh,a=c.nc.page.maxw;c.speedx=0<a?Math.min(60,c.speedx):0;c.speedy=0<p?Math.min(60,c.speedy):0;d=d&&60>=k-d;if(0>n||n>p||0>f||f>a)d=!1;f=c.speedx&&d?c.speedx:!1;if(c.speedy&&d&&c.speedy||f){var s=Math.max(16,c.steptime);50<s&&(f=s/50,c.speedx*=f,c.speedy*=f,s=
+50);c.demulxy=0;c.lastscrollx=c.nc.getScrollLeft();c.chkx=c.lastscrollx;c.lastscrolly=c.nc.getScrollTop();c.chky=c.lastscrolly;var e=c.lastscrollx,r=c.lastscrolly,t=function(){var d=600<c.time()-k?.04:.02;c.speedx&&(e=Math.floor(c.lastscrollx-c.speedx*(1-c.demulxy)),c.lastscrollx=e,0>e||e>a)&&(d=.1);c.speedy&&(r=Math.floor(c.lastscrolly-c.speedy*(1-c.demulxy)),c.lastscrolly=r,0>r||r>p)&&(d=.1);c.demulxy=Math.min(1,c.demulxy+d);c.nc.synched("domomentum2d",function(){c.speedx&&(c.nc.getScrollLeft()!=
+c.chkx&&c.stop(),c.chkx=e,c.nc.setScrollLeft(e));c.speedy&&(c.nc.getScrollTop()!=c.chky&&c.stop(),c.chky=r,c.nc.setScrollTop(r));c.timer||(c.nc.hideCursor(),c.doSnapy(e,r))});1>c.demulxy?c.timer=setTimeout(t,s):(c.stop(),c.nc.hideCursor(),c.doSnapy(e,r))};t()}else c.doSnapy(c.nc.getScrollLeft(),c.nc.getScrollTop())}},w=f.fn.scrollTop;f.cssHooks.pageYOffset={get:function(k,c,h){return(c=f.data(k,"__nicescroll")||!1)&&c.ishwscroll?c.getScrollTop():w.call(k)},set:function(k,c){var h=f.data(k,"__nicescroll")||
+!1;h&&h.ishwscroll?h.setScrollTop(parseInt(c)):w.call(k,c);return this}};f.fn.scrollTop=function(k){if("undefined"==typeof k){var c=this[0]?f.data(this[0],"__nicescroll")||!1:!1;return c&&c.ishwscroll?c.getScrollTop():w.call(this)}return this.each(function(){var c=f.data(this,"__nicescroll")||!1;c&&c.ishwscroll?c.setScrollTop(parseInt(k)):w.call(f(this),k)})};var B=f.fn.scrollLeft;f.cssHooks.pageXOffset={get:function(k,c,h){return(c=f.data(k,"__nicescroll")||!1)&&c.ishwscroll?c.getScrollLeft():B.call(k)},
+set:function(k,c){var h=f.data(k,"__nicescroll")||!1;h&&h.ishwscroll?h.setScrollLeft(parseInt(c)):B.call(k,c);return this}};f.fn.scrollLeft=function(k){if("undefined"==typeof k){var c=this[0]?f.data(this[0],"__nicescroll")||!1:!1;return c&&c.ishwscroll?c.getScrollLeft():B.call(this)}return this.each(function(){var c=f.data(this,"__nicescroll")||!1;c&&c.ishwscroll?c.setScrollLeft(parseInt(k)):B.call(f(this),k)})};var C=function(k){var c=this;this.length=0;this.name="nicescrollarray";this.each=function(d){for(var f=
+0,h=0;f<c.length;f++)d.call(c[f],h++);return c};this.push=function(d){c[c.length]=d;c.length++};this.eq=function(d){return c[d]};if(k)for(var h=0;h<k.length;h++){var m=f.data(k[h],"__nicescroll")||!1;m&&(this[this.length]=m,this.length++)}return this};(function(f,c,h){for(var m=0;m<c.length;m++)h(f,c[m])})(C.prototype,"show hide toggle onResize resize remove stop doScrollPos".split(" "),function(f,c){f[c]=function(){var f=arguments;return this.each(function(){this[c].apply(this,f)})}});f.fn.getNiceScroll=
+function(k){return"undefined"==typeof k?new C(this):this[k]&&f.data(this[k],"__nicescroll")||!1};f.extend(f.expr[":"],{nicescroll:function(k){return f.data(k,"__nicescroll")?!0:!1}});f.fn.niceScroll=function(k,c){"undefined"!=typeof c||"object"!=typeof k||"jquery"in k||(c=k,k=!1);c=f.extend({},c);var h=new C;"undefined"==typeof c&&(c={});k&&(c.doc=f(k),c.win=f(this));var m=!("doc"in c);m||"win"in c||(c.win=f(this));this.each(function(){var d=f(this).data("__nicescroll")||!1;d||(c.doc=m?f(this):c.doc,
+d=new R(c,f(this)),f(this).data("__nicescroll",d));h.push(d)});return 1==h.length?h[0]:h};window.NiceScroll={getjQuery:function(){return f}};f.nicescroll||(f.nicescroll=new C,f.nicescroll.options=I)});