Commit 0be6debb authored by Jacob Vosmaer's avatar Jacob Vosmaer

Merge branch 'master' of dev.gitlab.org:gitlab/gitlabhq into backup-archive-permissions

parents d371331a cd6046e1
Please view this file on the master branch, on stable branches it's out of date. 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) 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) - 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) - Fix external issue tracker hook/test for HTTPS URLs (Daniel Gerhardt)
- Remove link leading to a 404 error in Deploy Keys page (Stan Hu) - Remove link leading to a 404 error in Deploy Keys page (Stan Hu)
- Add support for unlocking users in admin settings (Stan Hu) - Add support for unlocking users in admin settings (Stan Hu)
- Add Irker service configuration options (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) - 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 - 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) - Add `two_factor_enabled` field to admin user API (Stan Hu)
- Fix invalid timestamps in RSS feeds (Rowan Wookey) - 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) - 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) - 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) - 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) - 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) - 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) - 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 - Remove project visibility icons from dashboard projects list
- Rename "Design" profile settings page to "Preferences". - Rename "Design" profile settings page to "Preferences".
- Allow users to customize their default Dashboard page. - Allow users to customize their default Dashboard page.
...@@ -38,11 +53,21 @@ v 7.13.0 (unreleased) ...@@ -38,11 +53,21 @@ v 7.13.0 (unreleased)
- Query Optimization in MySQL. - Query Optimization in MySQL.
- Allow users to be blocked and unblocked via the API - Allow users to be blocked and unblocked via the API
- Allow custom backup archive permissions - 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 v 7.12.2
- Correctly show anonymous authorized applications under Profile > Applications. - Correctly show anonymous authorized applications under Profile > Applications.
- Faster automerge check and merge itself when source and target branches are in same repository - Faster automerge check and merge itself when source and target branches are in same repository
- Audit log for user authentication - Audit log for user authentication
- Fix transferring of project to another group using the API.
v 7.12.1 v 7.12.1
- Fix error when deleting a user who has projects (Stan Hu) - Fix error when deleting a user who has projects (Stan Hu)
...@@ -52,6 +77,8 @@ v 7.12.1 ...@@ -52,6 +77,8 @@ v 7.12.1
- Fix closed merge request scope at milestone page (Dmitriy Zaporozhets) - Fix closed merge request scope at milestone page (Dmitriy Zaporozhets)
- Revert merge request states renaming - Revert merge request states renaming
- Fix hooks for web based events with external issue references (Daniel Gerhardt) - 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 v 7.12.0
- Fix Error 500 when one user attempts to access a personal, internal snippet (Stan Hu) - Fix Error 500 when one user attempts to access a personal, internal snippet (Stan Hu)
...@@ -110,6 +137,7 @@ v 7.12.0 ...@@ -110,6 +137,7 @@ v 7.12.0
- Improve group removing logic - Improve group removing logic
- Trigger create-hooks on backup restore task - Trigger create-hooks on backup restore task
- Add option to automatically link omniauth and LDAP identities - Add option to automatically link omniauth and LDAP identities
- Allow special character in users bio. I.e.: I <3 GitLab
v 7.11.4 v 7.11.4
- Fix missing bullets when creating lists - Fix missing bullets when creating lists
...@@ -128,9 +156,6 @@ v 7.11.1 ...@@ -128,9 +156,6 @@ v 7.11.1
v 7.11.0 v 7.11.0
- Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger) - 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. - 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) - 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 - Don't show duplicate deploy keys
- Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger) - Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger)
......
...@@ -203,7 +203,7 @@ gem 'jquery-ui-rails' ...@@ -203,7 +203,7 @@ gem 'jquery-ui-rails'
gem 'nprogress-rails' gem 'nprogress-rails'
gem 'raphael-rails', '~> 2.1.2' gem 'raphael-rails', '~> 2.1.2'
gem 'request_store' gem 'request_store'
gem 'select2-rails' gem 'select2-rails', '~> 3.5.9'
gem 'virtus' gem 'virtus'
group :development do group :development do
...@@ -227,30 +227,24 @@ end ...@@ -227,30 +227,24 @@ end
group :development, :test do group :development, :test do
gem 'awesome_print' gem 'awesome_print'
gem 'byebug' gem 'byebug', platform: :mri
gem 'fuubar', '~> 2.0.0' gem 'fuubar', '~> 2.0.0'
gem 'pry-rails' gem 'pry-rails'
gem 'coveralls', require: false gem 'coveralls', '~> 0.8.2', require: false
gem 'database_cleaner', '~> 1.4.0' gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails' gem 'factory_girl_rails'
gem 'rspec-rails', '~> 3.3.0' gem 'rspec-rails', '~> 3.3.0'
gem 'rubocop', '0.28.0', require: false gem 'rubocop', '0.28.0', require: false
gem 'spinach-rails' 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) # Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.3.0' gem 'minitest', '~> 5.3.0'
# Generate Fake data # Generate Fake data
gem 'ffaker', '~> 2.0.0' gem 'ffaker', '~> 2.0.0'
gem 'capybara', '~> 2.3.0' gem 'capybara', '~> 2.4.0'
gem 'capybara-screenshot', '~> 1.0.0' gem 'capybara-screenshot', '~> 1.0.0'
gem 'poltergeist', '~> 1.6.0' gem 'poltergeist', '~> 1.6.0'
......
...@@ -82,7 +82,7 @@ GEM ...@@ -82,7 +82,7 @@ GEM
columnize (~> 0.8) columnize (~> 0.8)
debugger-linecache (~> 1.2) debugger-linecache (~> 1.2)
cal-heatmap-rails (0.0.1) cal-heatmap-rails (0.0.1)
capybara (2.3.0) capybara (2.4.4)
mime-types (>= 1.16) mime-types (>= 1.16)
nokogiri (>= 1.3.3) nokogiri (>= 1.3.3)
rack (>= 1.0.0) rack (>= 1.0.0)
...@@ -598,7 +598,7 @@ GEM ...@@ -598,7 +598,7 @@ GEM
seed-fu (2.3.5) seed-fu (2.3.5)
activerecord (>= 3.1, < 4.3) activerecord (>= 3.1, < 4.3)
activesupport (>= 3.1, < 4.3) activesupport (>= 3.1, < 4.3)
select2-rails (3.5.2) select2-rails (3.5.9.3)
thor (~> 0.14) thor (~> 0.14)
settingslogic (2.0.9) settingslogic (2.0.9)
sexp_processor (4.4.5) sexp_processor (4.4.5)
...@@ -703,7 +703,7 @@ GEM ...@@ -703,7 +703,7 @@ GEM
underscore-rails (1.4.4) underscore-rails (1.4.4)
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.6) unf_ext (0.0.7.1)
unicorn (4.6.3) unicorn (4.6.3)
kgio (~> 2.6) kgio (~> 2.6)
rack rack
...@@ -753,13 +753,13 @@ DEPENDENCIES ...@@ -753,13 +753,13 @@ DEPENDENCIES
browser (~> 0.8.0) browser (~> 0.8.0)
byebug byebug
cal-heatmap-rails (~> 0.0.1) cal-heatmap-rails (~> 0.0.1)
capybara (~> 2.3.0) capybara (~> 2.4.0)
capybara-screenshot (~> 1.0.0) capybara-screenshot (~> 1.0.0)
carrierwave carrierwave
charlock_holmes charlock_holmes
coffee-rails coffee-rails
colored colored
coveralls coveralls (~> 0.8.2)
creole (~> 0.3.6) creole (~> 0.3.6)
d3_rails (~> 3.5.5) d3_rails (~> 3.5.5)
database_cleaner (~> 1.4.0) database_cleaner (~> 1.4.0)
...@@ -833,7 +833,6 @@ DEPENDENCIES ...@@ -833,7 +833,6 @@ DEPENDENCIES
redis-rails redis-rails
request_store request_store
rerun (~> 0.10.0) rerun (~> 0.10.0)
rest-client (~> 1.8.0)
rqrcode-rails3 rqrcode-rails3
rspec-rails (~> 3.3.0) rspec-rails (~> 3.3.0)
rubocop (= 0.28.0) rubocop (= 0.28.0)
...@@ -842,7 +841,7 @@ DEPENDENCIES ...@@ -842,7 +841,7 @@ DEPENDENCIES
sass-rails (~> 4.0.5) sass-rails (~> 4.0.5)
sdoc sdoc
seed-fu seed-fu
select2-rails select2-rails (~> 3.5.9)
settingslogic settingslogic
shoulda-matchers (~> 2.8.0) shoulda-matchers (~> 2.8.0)
sidekiq (~> 3.3) sidekiq (~> 3.3)
...@@ -878,4 +877,4 @@ DEPENDENCIES ...@@ -878,4 +877,4 @@ DEPENDENCIES
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH BUNDLED WITH
1.10.4 1.10.5
# 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 ## 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. 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 ## 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/). 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 ...@@ -17,21 +22,12 @@ To see how GitLab looks please see the [features page on our website](https://ab
## Editions ## Editions
There are two editions of GitLab. 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)
- [![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) Included with the GitLab Omnibus Packages is [GitLab CI](https://about.gitlab.com/gitlab-ci/) that can easily build, test and deploy code.
- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
## Website ## Website
...@@ -46,23 +42,39 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a ...@@ -46,23 +42,39 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a
## Requirements ## 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. Please see the [requirements documentation](doc/install/requirements.md) for system requirements and more information about the supported operating systems.
## Installation ## 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. 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 ## Third-party applications
...@@ -74,17 +86,7 @@ Since 2011 a minor or major version of GitLab is released on the 22nd of every m ...@@ -74,17 +86,7 @@ Since 2011 a minor or major version of GitLab is released on the 22nd of every m
## Upgrading ## 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. For upgrading information please see our [update page](https://about.gitlab.com/update/).
## 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).
## Documentation ## Documentation
......
...@@ -40,6 +40,7 @@ ...@@ -40,6 +40,7 @@
#= require shortcuts_issuable #= require shortcuts_issuable
#= require shortcuts_network #= require shortcuts_network
#= require cal-heatmap #= require cal-heatmap
#= require jquery.nicescroll.min
#= require_tree . #= require_tree .
window.slugify = (text) -> window.slugify = (text) ->
...@@ -104,6 +105,8 @@ if location.hash ...@@ -104,6 +105,8 @@ if location.hash
window.addEventListener "hashchange", shiftWindow 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 # Click a .js-select-on-focus field, select the contents
$(".js-select-on-focus").on "focusin", -> $(".js-select-on-focus").on "focusin", ->
# Prevent a mouseup event from deselecting the input # Prevent a mouseup event from deselecting the input
......
...@@ -62,8 +62,9 @@ class Dispatcher ...@@ -62,8 +62,9 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'projects:commits:show' when 'projects:commits:show'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'projects:activity'
shortcut_handler = new ShortcutsNavigation()
when 'projects:show' when 'projects:show'
new Activities()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'groups:show' when 'groups:show'
new Activities() new Activities()
...@@ -127,7 +128,10 @@ class Dispatcher ...@@ -127,7 +128,10 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new ZenMode() new ZenMode()
new DropzoneInput($('.wiki-form')) 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() shortcut_handler = new ShortcutsNavigation()
when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches' when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
......
...@@ -25,10 +25,10 @@ class @DropzoneInput ...@@ -25,10 +25,10 @@ class @DropzoneInput
form_dropzone = $(form).find('.div-dropzone') form_dropzone = $(form).find('.div-dropzone')
form_dropzone.parent().addClass "div-dropzone-wrapper" form_dropzone.parent().addClass "div-dropzone-wrapper"
form_dropzone.append divHover form_dropzone.append divHover
$(".div-dropzone-hover").append iconPaperclip form_dropzone.find(".div-dropzone-hover").append iconPaperclip
form_dropzone.append divSpinner form_dropzone.append divSpinner
$(".div-dropzone-spinner").append iconSpinner form_dropzone.find(".div-dropzone-spinner").append iconSpinner
$(".div-dropzone-spinner").css form_dropzone.find(".div-dropzone-spinner").css
"opacity": 0 "opacity": 0
"display": "none" "display": "none"
......
...@@ -70,7 +70,7 @@ class @LineHighlighter ...@@ -70,7 +70,7 @@ class @LineHighlighter
@clearHighlight() @clearHighlight()
lineNumber = $(event.target).data('line-number') lineNumber = $(event.target).closest('a').data('line-number')
current = @hashToRange(@_hash) current = @hashToRange(@_hash)
unless current[0] && event.shiftKey unless current[0] && event.shiftKey
......
...@@ -15,9 +15,7 @@ class @MergeRequest ...@@ -15,9 +15,7 @@ class @MergeRequest
this.$('.show-all-commits').on 'click', => this.$('.show-all-commits').on 'click', =>
this.showAllCommits() this.showAllCommits()
# `MergeRequests#new` has no tab-persisting or lazy-loading behavior @initTabs()
unless @opts.action == 'new'
new MergeRequestTabs(@opts)
# Prevent duplicate event bindings # Prevent duplicate event bindings
@disableTaskList() @disableTaskList()
...@@ -29,6 +27,14 @@ class @MergeRequest ...@@ -29,6 +27,14 @@ class @MergeRequest
$: (selector) -> $: (selector) ->
this.$el.find(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: -> showAllCommits: ->
this.$('.first-commits').remove() this.$('.first-commits').remove()
this.$('.all-commits').removeClass 'hide' this.$('.all-commits').removeClass 'hide'
......
...@@ -298,7 +298,7 @@ class @Notes ...@@ -298,7 +298,7 @@ class @Notes
note.find(".note-header").hide() note.find(".note-header").hide()
base_form = note.find(".note-edit-form") base_form = note.find(".note-edit-form")
form = base_form.clone().insertAfter(base_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() form.find('.div-dropzone').remove()
# Show the attachment delete link # Show the attachment delete link
......
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
@loading.show() @loading.show()
$.ajax $.ajax
type: "GET" type: "GET"
url: location.href url: $(".content_list").data('href') || location.href
data: "limit=" + @limit + "&offset=" + @offset data: "limit=" + @limit + "&offset=" + @offset
complete: => complete: =>
@loading.hide() @loading.hide()
......
class @ProjectShow class @ProjectShow
constructor: -> constructor: ->
$('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) -> # I kept class for future
$(@).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"
...@@ -4,6 +4,7 @@ class @ShortcutsNavigation extends Shortcuts ...@@ -4,6 +4,7 @@ class @ShortcutsNavigation extends Shortcuts
constructor: -> constructor: ->
super() super()
Mousetrap.bind('g p', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project')) 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 f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'))
Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits')) Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits'))
Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network')) Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network'))
......
...@@ -109,7 +109,7 @@ ...@@ -109,7 +109,7 @@
font-size: 1.2em; font-size: 1.2em;
} }
blockquote p { blockquote {
color: #888; color: #888;
font-size: 15px; font-size: 15px;
line-height: 1.5; line-height: 1.5;
......
...@@ -44,20 +44,18 @@ ...@@ -44,20 +44,18 @@
.project-home-panel { .project-home-panel {
padding-left: 0 !important; padding-left: 0 !important;
.project-home-row { .project-avatar {
.project-home-desc { display: block;
margin-right: 0 !important;
float: none !important;
} }
.project-repo-buttons { .project-repo-buttons,
position: static; .git-clone-holder {
margin-top: 15px; display: none;
width: 100%;
float: none;
text-align: left;
} }
} }
.project-stats {
display: none;
} }
.container .title { .container .title {
......
...@@ -2,6 +2,9 @@ ...@@ -2,6 +2,9 @@
.sidebar-wrapper { .sidebar-wrapper {
position: fixed; position: fixed;
top: 0; top: 0;
bottom: 0;
overflow-y: auto;
overflow-x: hidden;
left: 0; left: 0;
height: 100%; height: 100%;
transition-duration: .3s; transition-duration: .3s;
...@@ -21,8 +24,9 @@ ...@@ -21,8 +24,9 @@
} }
.nav-sidebar { .nav-sidebar {
margin-top: 29 + $header-height;
margin-bottom: 50px;
transition-duration: .3s; transition-duration: .3s;
margin: 0;
list-style: none; list-style: none;
overflow: hidden; overflow: hidden;
...@@ -39,12 +43,12 @@ ...@@ -39,12 +43,12 @@
} }
a { a {
padding: 8px 15px;
font-size: 13px;
line-height: 18px;
color: $gray; color: $gray;
display: block; display: block;
text-decoration: none; text-decoration: none;
padding: 8px 15px;
font-size: 14px;
line-height: 20px;
padding-left: 16px; padding-left: 16px;
&:hover { &:hover {
...@@ -88,14 +92,17 @@ ...@@ -88,14 +92,17 @@
width: $sidebar_width; width: $sidebar_width;
.nav-sidebar { .nav-sidebar {
margin-top: 29px;
position: fixed;
top: $header-height;
width: $sidebar_width; width: $sidebar_width;
} }
.nav-sidebar li a{ .nav-sidebar li a{
width: 230px; width: 230px;
&.back-link {
i {
visibility: hidden;
}
}
} }
} }
} }
...@@ -108,15 +115,9 @@ ...@@ -108,15 +115,9 @@
width: $sidebar_collapsed_width; width: $sidebar_collapsed_width;
.nav-sidebar { .nav-sidebar {
margin-top: 29px;
position: fixed;
top: $header-height;
width: $sidebar_collapsed_width; width: $sidebar_collapsed_width;
li a { li a {
font-size: 14px;
padding: 8px 15px;
text-align: left;
padding-left: 16px; padding-left: 16px;
} }
} }
...@@ -175,7 +176,7 @@ ...@@ -175,7 +176,7 @@
} }
.sidebar-user { .sidebar-user {
position: absolute; position: fixed;
bottom: 0; bottom: 0;
width: $sidebar_width; width: $sidebar_width;
padding: 10px; padding: 10px;
......
...@@ -17,6 +17,14 @@ pre { ...@@ -17,6 +17,14 @@ pre {
background: #333; background: #333;
color: $background-color; color: $background-color;
} }
&.plain-readme {
background: none;
border: none;
padding: 0;
margin: 0;
font-size: 14px;
}
} }
.monospace { .monospace {
......
...@@ -72,13 +72,28 @@ ul.notes { ...@@ -72,13 +72,28 @@ ul.notes {
.note { .note {
display: block; display: block;
position:relative; position:relative;
.note-body { .note-body {
overflow: auto; overflow: auto;
.note-text { .note-text {
overflow: auto; overflow: auto;
word-wrap: break-word; word-wrap: break-word;
@include md-typography; @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 // Reduce left padding of first task list ul element
ul.task-list:first-child { ul.task-list:first-child {
padding-left: 10px; padding-left: 10px;
...@@ -94,6 +109,7 @@ ul.notes { ...@@ -94,6 +109,7 @@ ul.notes {
} }
} }
} }
.note-header { .note-header {
padding-bottom: 3px; padding-bottom: 3px;
} }
......
...@@ -15,48 +15,31 @@ ...@@ -15,48 +15,31 @@
} }
.project-home-panel { .project-home-panel {
margin-top: 10px; text-align: center;
margin-bottom: 15px; margin-bottom: 20px;
position: relative;
padding-left: 65px;
min-height: 50px;
.project-identicon-holder { .project-identicon-holder {
position: absolute; margin-bottom: 15px;
left: 0;
top: -14px;
.avatar { .avatar, .identicon {
width: 50px; margin: 0 auto;
height: 50px; float: none;
} }
.identicon { .identicon {
font-size: 26px; @include border-radius(50%);
line-height: 50px;
} }
} }
.project-home-row { .lead {
@extend .clearfix;
margin-bottom: 15px;
&.project-home-row-top {
margin-bottom: 15px;
}
.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 { p {
display: inline; display: inline;
} }
} }
.git-clone-holder {
max-width: 600px;
margin: 0 auto;
} }
.visibility-level-label { .visibility-level-label {
...@@ -67,22 +50,22 @@ ...@@ -67,22 +50,22 @@
} }
.project-repo-buttons { .project-repo-buttons {
margin-top: -3px; margin-top: 25px;
position: absolute; margin-bottom: 25px;
right: 0;
width: 265px;
text-align: right;
.btn { .btn {
@extend .btn-info;
margin-left: 10px;
font-weight: bold; font-weight: bold;
font-size: 14px; font-size: 14px;
line-height: 16px; line-height: 16px;
padding: 8px 12px;
.count { .count {
padding-left: 10px; padding-left: 7px;
border-left: 1px solid #ccc;
display: inline-block; display: inline-block;
margin-left: 10px; margin-left: 7px;
} }
} }
} }
...@@ -307,3 +290,15 @@ table.table.protected-branches-list tr.no-border { ...@@ -307,3 +290,15 @@ table.table.protected-branches-list tr.no-border {
float: left; float: left;
margin-right: 10px; 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;
}
...@@ -35,9 +35,9 @@ ...@@ -35,9 +35,9 @@
.sidebar-wrapper { .sidebar-wrapper {
background: $color-darker; background: $color-darker;
border-right: 1px solid $color-darker;
.sidebar-user { .sidebar-user {
background: $color-darker;
color: $color-light; color: $color-light;
&:hover { &:hover {
......
...@@ -23,7 +23,8 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -23,7 +23,8 @@ class Admin::ProjectsController < Admin::ApplicationController
end end
def transfer 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 @project.reload
redirect_to admin_namespace_project_path(@project.namespace, @project) redirect_to admin_namespace_project_path(@project.namespace, @project)
......
...@@ -55,6 +55,12 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -55,6 +55,12 @@ class Admin::UsersController < Admin::ApplicationController
end end
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 def create
opts = { opts = {
force_random_password: true, force_random_password: true,
......
...@@ -183,7 +183,10 @@ class ApplicationController < ActionController::Base ...@@ -183,7 +183,10 @@ class ApplicationController < ActionController::Base
headers['X-XSS-Protection'] = '1; mode=block' headers['X-XSS-Protection'] = '1; mode=block'
headers['X-UA-Compatible'] = 'IE=edge' headers['X-UA-Compatible'] = 'IE=edge'
headers['X-Content-Type-Options'] = 'nosniff' 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 end
def add_gon_variables def add_gon_variables
...@@ -265,6 +268,7 @@ class ApplicationController < ActionController::Base ...@@ -265,6 +268,7 @@ class ApplicationController < ActionController::Base
params[:scope] = 'all' if params[:scope].blank? params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank? params[:state] = 'opened' if params[:state].blank?
@sort = params[:sort]
@filter_params = params.dup @filter_params = params.dup
if @project if @project
......
class AutocompleteController < ApplicationController class AutocompleteController < ApplicationController
skip_before_action :authenticate_user!, only: [:users]
def users def users
begin
@users = @users =
if params[:project_id].present? if params[:project_id].present?
project = Project.find(params[:project_id]) project = Project.find(params[:project_id])
...@@ -13,10 +16,20 @@ class AutocompleteController < ApplicationController ...@@ -13,10 +16,20 @@ class AutocompleteController < ApplicationController
if can?(current_user, :read_group, group) if can?(current_user, :read_group, group)
group.users group.users
end end
else elsif current_user
User.all User.all
end end
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.search(params[:search]) if params[:search].present?
@users = @users.active @users = @users.active
@users = @users.page(params[:page]).per(PER_PAGE) @users = @users.page(params[:page]).per(PER_PAGE)
......
...@@ -32,6 +32,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController ...@@ -32,6 +32,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController
params.require(:user).permit( params.require(:user).permit(
:color_scheme_id, :color_scheme_id,
:dashboard, :dashboard,
:project_view,
:theme_id :theme_id
) )
end end
......
...@@ -29,13 +29,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController ...@@ -29,13 +29,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
end end
def destroy def destroy
current_user.update_attributes({ current_user.disable_two_factor!
two_factor_enabled: false,
encrypted_otp_secret: nil,
encrypted_otp_secret_iv: nil,
encrypted_otp_secret_salt: nil,
otp_backup_codes: nil
})
redirect_to profile_account_path redirect_to profile_account_path
end end
......
...@@ -32,7 +32,7 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -32,7 +32,7 @@ class Projects::BranchesController < Projects::ApplicationController
end end
def destroy def destroy
DeleteBranchService.new(project, current_user).execute(params[:id]) status = DeleteBranchService.new(project, current_user).execute(params[:id])
@branch_name = params[:id] @branch_name = params[:id]
respond_to do |format| respond_to do |format|
...@@ -40,7 +40,7 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -40,7 +40,7 @@ class Projects::BranchesController < Projects::ApplicationController
redirect_to namespace_project_branches_path(@project.namespace, redirect_to namespace_project_branches_path(@project.namespace,
@project) @project)
end end
format.js format.js { render status: status[:return_code] }
end end
end end
end end
class Projects::GraphsController < Projects::ApplicationController class Projects::GraphsController < Projects::ApplicationController
include ExtractsPath
# Authorize # Authorize
before_action :require_non_empty_project before_action :require_non_empty_project
before_action :assign_ref_vars
before_action :authorize_download_code! before_action :authorize_download_code!
def show def show
...@@ -13,7 +16,7 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -13,7 +16,7 @@ class Projects::GraphsController < Projects::ApplicationController
end end
def commits 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_graph = Gitlab::Graphs::Commits.new(@commits)
@commits_per_week_days = @commits_graph.commits_per_week_days @commits_per_week_days = @commits_graph.commits_per_week_days
@commits_per_time = @commits_graph.commits_per_time @commits_per_time = @commits_graph.commits_per_time
...@@ -23,7 +26,7 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -23,7 +26,7 @@ class Projects::GraphsController < Projects::ApplicationController
private private
def fetch_graph def fetch_graph
@commits = @project.repository.commits(nil, nil, 6000, 0, true) @commits = @project.repository.commits(@ref, nil, 6000, 0, true)
@log = [] @log = []
@commits.each do |commit| @commits.each do |commit|
......
...@@ -64,7 +64,12 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -64,7 +64,12 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
def destroy 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 @milestone.destroy
......
...@@ -8,14 +8,18 @@ class Projects::RefsController < Projects::ApplicationController ...@@ -8,14 +8,18 @@ class Projects::RefsController < Projects::ApplicationController
def switch def switch
respond_to do |format| respond_to do |format|
format.html do format.html do
new_path = if params[:destination] == "tree" new_path =
namespace_project_tree_path(@project.namespace, @project, case params[:destination]
(@id)) when "tree"
elsif params[:destination] == "blob" namespace_project_tree_path(@project.namespace, @project, @id)
namespace_project_blob_path(@project.namespace, @project, when "blob"
(@id)) namespace_project_blob_path(@project.namespace, @project, @id)
elsif params[:destination] == "graph" when "graph"
namespace_project_network_path(@project.namespace, @project, @id, @options) 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 else
namespace_project_commits_path(@project.namespace, @project, @id) namespace_project_commits_path(@project.namespace, @project, @id)
end end
......
...@@ -7,13 +7,15 @@ class Projects::TreeController < Projects::ApplicationController ...@@ -7,13 +7,15 @@ class Projects::TreeController < Projects::ApplicationController
before_action :authorize_download_code! before_action :authorize_download_code!
def show def show
return not_found! unless @repository.commit(@ref)
if tree.entries.empty? if tree.entries.empty?
if @repository.blob_at(@commit.id, @path) if @repository.blob_at(@commit.id, @path)
redirect_to( redirect_to(
namespace_project_blob_path(@project.namespace, @project, namespace_project_blob_path(@project.namespace, @project,
File.join(@ref, @path)) File.join(@ref, @path))
) and return ) and return
else elsif @path.present?
return not_found! return not_found!
end end
end end
......
class ProjectsController < ApplicationController class ProjectsController < ApplicationController
prepend_before_filter :render_go_import, only: [:show] 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 :project, except: [:new, :create]
before_action :repository, except: [:new, :create] before_action :repository, except: [:new, :create]
# Authorize # Authorize
before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive] 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 layout :determine_layout
...@@ -52,10 +52,21 @@ class ProjectsController < ApplicationController ...@@ -52,10 +52,21 @@ class ProjectsController < ApplicationController
end end
def transfer def transfer
transfer_params = params.permit(:new_namespace_id) namespace = Namespace.find_by(id: params[:new_namespace_id])
::Projects::TransferService.new(project, current_user, transfer_params).execute ::Projects::TransferService.new(project, current_user).execute(namespace)
if @project.errors[:namespace_id].present?
flash[:alert] = @project.errors[:namespace_id].first 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
end end
...@@ -65,15 +76,12 @@ class ProjectsController < ApplicationController ...@@ -65,15 +76,12 @@ class ProjectsController < ApplicationController
return return
end end
@show_star = !(current_user && current_user.starred?(@project))
respond_to do |format| respond_to do |format|
format.html do format.html do
if @project.repository_exists? if @project.repository_exists?
if @project.empty_repo? if @project.empty_repo?
render 'projects/empty' render 'projects/empty'
else else
@last_push = current_user.recent_push(@project.id) if current_user
render :show render :show
end end
else else
...@@ -81,11 +89,6 @@ class ProjectsController < ApplicationController ...@@ -81,11 +89,6 @@ class ProjectsController < ApplicationController
end end
end end
format.json do
load_events
pager_json('events/_events', @events.count)
end
format.atom do format.atom do
load_events load_events
render layout: false render layout: false
...@@ -147,7 +150,10 @@ class ProjectsController < ApplicationController ...@@ -147,7 +150,10 @@ class ProjectsController < ApplicationController
def toggle_star def toggle_star
current_user.toggle_star(@project) current_user.toggle_star(@project)
@project.reload @project.reload
render json: { star_count: @project.star_count }
render json: {
html: view_to_html_string("projects/buttons/_star")
}
end end
def markdown_preview def markdown_preview
......
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# state: 'open' or 'closed' or 'all' # state: 'open' or 'closed' or 'all'
# group_id: integer # group_id: integer
# project_id: integer # project_id: integer
# milestone_id: integer # milestone_title: string
# assignee_id: integer # assignee_id: integer
# search: string # search: string
# label_name: string # label_name: string
...@@ -76,7 +76,7 @@ class IssuableFinder ...@@ -76,7 +76,7 @@ class IssuableFinder
return @milestones if defined?(@milestones) return @milestones if defined?(@milestones)
@milestones = @milestones =
if milestones? && params[:milestone_title] != NONE if milestones? && params[:milestone_title] != Milestone::None.title
Milestone.where(title: params[:milestone_title]) Milestone.where(title: params[:milestone_title])
else else
nil nil
......
...@@ -213,6 +213,10 @@ module ApplicationHelper ...@@ -213,6 +213,10 @@ module ApplicationHelper
Haml::Helpers.preserve(markdown(file_content)) Haml::Helpers.preserve(markdown(file_content))
elsif asciidoc?(file_name) elsif asciidoc?(file_name)
asciidoc(file_content) asciidoc(file_content)
elsif plain?(file_name)
content_tag :pre, class: 'plain-readme' do
file_content
end
else else
GitHub::Markup.render(file_name, file_content). GitHub::Markup.render(file_name, file_content).
force_encoding(file_content.encoding).html_safe force_encoding(file_content.encoding).html_safe
...@@ -221,6 +225,10 @@ module ApplicationHelper ...@@ -221,6 +225,10 @@ module ApplicationHelper
simple_format(file_content) simple_format(file_content)
end end
def plain?(filename)
Gitlab::MarkupHelper.plain?(filename)
end
def markup?(filename) def markup?(filename)
Gitlab::MarkupHelper.markup?(filename) Gitlab::MarkupHelper.markup?(filename)
end end
......
...@@ -28,7 +28,7 @@ module ApplicationSettingsHelper ...@@ -28,7 +28,7 @@ module ApplicationSettingsHelper
def restricted_level_checkboxes(help_block_id) def restricted_level_checkboxes(help_block_id)
Gitlab::VisibilityLevel.options.map do |name, level| Gitlab::VisibilityLevel.options.map do |name, level|
checked = restricted_visibility_levels(true).include?(level) checked = restricted_visibility_levels(true).include?(level)
css_class = 'btn btn-primary' css_class = 'btn'
css_class += ' active' if checked css_class += ' active' if checked
checkbox_name = 'application_setting[restricted_visibility_levels][]' checkbox_name = 'application_setting[restricted_visibility_levels][]'
......
...@@ -118,7 +118,7 @@ module GitlabMarkdownHelper ...@@ -118,7 +118,7 @@ module GitlabMarkdownHelper
# Returns a random markdown tip for use as a textarea placeholder # Returns a random markdown tip for use as a textarea placeholder
def random_markdown_tip def random_markdown_tip
"Tip: #{MARKDOWN_TIPS.sample}" MARKDOWN_TIPS.sample
end end
private private
......
...@@ -17,6 +17,10 @@ module GitlabRoutingHelper ...@@ -17,6 +17,10 @@ module GitlabRoutingHelper
namespace_project_path(project.namespace, project, *args) namespace_project_path(project.namespace, project, *args)
end end
def activity_project_path(project, *args)
activity_namespace_project_path(project.namespace, project, *args)
end
def edit_project_path(project, *args) def edit_project_path(project, *args)
edit_namespace_project_path(project.namespace, project, *args) edit_namespace_project_path(project.namespace, project, *args)
end end
......
...@@ -29,6 +29,8 @@ module MilestonesHelper ...@@ -29,6 +29,8 @@ module MilestonesHelper
end.active end.active
grouped_milestones = Milestones::GroupService.new(milestones).execute grouped_milestones = Milestones::GroupService.new(milestones).execute
grouped_milestones.unshift(Milestone::None)
options_from_collection_for_select(grouped_milestones, 'title', 'title', params[:milestone_title]) options_from_collection_for_select(grouped_milestones, 'title', 'title', params[:milestone_title])
end end
end end
...@@ -42,6 +42,13 @@ module PreferencesHelper ...@@ -42,6 +42,13 @@ module PreferencesHelper
end end
end end
def project_view_choices
[
['Readme (default)', :readme],
['Activity view', :activity]
]
end
def user_application_theme def user_application_theme
theme = Gitlab::Themes.by_id(current_user.try(:theme_id)) theme = Gitlab::Themes.by_id(current_user.try(:theme_id))
theme.css_class theme.css_class
...@@ -50,4 +57,9 @@ module PreferencesHelper ...@@ -50,4 +57,9 @@ module PreferencesHelper
def user_color_scheme_class def user_color_scheme_class
COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user) COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
end end
def prefer_readme?
!current_user ||
current_user.project_view == 'readme'
end
end end
...@@ -84,58 +84,21 @@ module ProjectsHelper ...@@ -84,58 +84,21 @@ module ProjectsHelper
@project.milestones.active.order("due_date, title ASC") @project.milestones.active.order("due_date, title ASC")
end end
def link_to_toggle_star(title, starred) def project_for_deploy_key(deploy_key)
cls = 'star-btn btn btn-sm btn-default' if deploy_key.projects.include?(@project)
@project
toggle_text =
if starred
' Unstar'
else else
' Star' deploy_key.projects.find { |project| can?(current_user, :read_project, project) }
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 end
def link_to_toggle_fork
html = content_tag('span') do
icon('code-fork') + ' Fork'
end end
count_html = content_tag(:span, class: 'count') do def can_change_visibility_level?(project, current_user)
@project.forks_count.to_s return false unless can?(current_user, :change_visibility_level, project)
end
html + count_html if project.forked?
end project.forked_from_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE
def project_for_deploy_key(deploy_key)
if deploy_key.projects.include?(@project)
@project
else else
deploy_key.projects.find { |project| can?(current_user, :read_project, project) } true
end end
end end
...@@ -168,8 +131,12 @@ module ProjectsHelper ...@@ -168,8 +131,12 @@ module ProjectsHelper
nav_tabs << :snippets nav_tabs << :snippets
end end
if can?(current_user, :read_label, project)
nav_tabs << :labels
end
if can?(current_user, :read_milestone, project) if can?(current_user, :read_milestone, project)
nav_tabs << [:milestones, :labels] nav_tabs << :milestones
end end
nav_tabs.flatten nav_tabs.flatten
...@@ -285,16 +252,6 @@ module ProjectsHelper ...@@ -285,16 +252,6 @@ module ProjectsHelper
end end
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) def user_max_access_in_project(user, project)
level = project.team.max_member_access(user) level = project.team.max_member_access(user)
...@@ -306,4 +263,35 @@ module ProjectsHelper ...@@ -306,4 +263,35 @@ module ProjectsHelper
def leave_project_message(project) def leave_project_message(project)
"Are you sure you want to leave \"#{project.name}\" project?" "Are you sure you want to leave \"#{project.name}\" project?"
end 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 end
...@@ -86,4 +86,10 @@ module VisibilityLevelHelper ...@@ -86,4 +86,10 @@ module VisibilityLevelHelper
def default_snippet_visibility def default_snippet_visibility
current_application_settings.default_snippet_visibility current_application_settings.default_snippet_visibility
end 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 end
...@@ -31,10 +31,11 @@ class Ability ...@@ -31,10 +31,11 @@ class Ability
end end
if project && project.public? if project && project.public?
[ rules = [
:read_project, :read_project,
:read_wiki, :read_wiki,
:read_issue, :read_issue,
:read_label,
:read_milestone, :read_milestone,
:read_project_snippet, :read_project_snippet,
:read_project_member, :read_project_member,
...@@ -42,6 +43,8 @@ class Ability ...@@ -42,6 +43,8 @@ class Ability
:read_note, :read_note,
:download_code :download_code
] ]
rules - project_disabled_features_rules(project)
else else
group = if subject.kind_of?(Group) group = if subject.kind_of?(Group)
subject subject
...@@ -102,28 +105,7 @@ class Ability ...@@ -102,28 +105,7 @@ class Ability
rules -= project_archived_rules rules -= project_archived_rules
end end
unless project.issues_enabled rules - project_disabled_features_rules(project)
rules -= named_abilities('issue')
end
unless project.merge_requests_enabled
rules -= named_abilities('merge_request')
end
unless project.issues_enabled or project.merge_requests_enabled
rules -= named_abilities('label')
rules -= named_abilities('milestone')
end
unless project.snippets_enabled
rules -= named_abilities('project_snippet')
end
unless project.wiki_enabled
rules -= named_abilities('wiki')
end
rules
end end
end end
...@@ -205,6 +187,33 @@ class Ability ...@@ -205,6 +187,33 @@ class Ability
] ]
end 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) def group_abilities(user, group)
rules = [] rules = []
......
...@@ -79,22 +79,36 @@ module Mentionable ...@@ -79,22 +79,36 @@ module Mentionable
end end
end end
# If the mentionable_text field is about to change, locate any *added* references and create cross references for # When a mentionable field is changed, creates cross-reference notes that
# them. Invoke from an observer's #before_save implementation. # don't already exist
def notice_added_references(p = project, a = author) def create_new_cross_references!(p = project, a = author)
ch = changed_attributes changes = detect_mentionable_changes
original, mentionable_changed = "", false
self.class.mentionable_attrs.each do |attr| return if changes.empty?
if ch[attr]
original << ch[attr]
mentionable_changed = true
end
end
# Only proceed if the saved changes actually include a chance to an attr_mentionable field. original_text = changes.collect { |_, vals| vals.first }.join(' ')
return unless mentionable_changed
preexisting = references(p, self.author, original) preexisting = references(p, self.author, original_text)
create_cross_references!(p, a, preexisting) create_cross_references!(p, a, preexisting)
end 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 end
...@@ -24,6 +24,7 @@ class Key < ActiveRecord::Base ...@@ -24,6 +24,7 @@ class Key < ActiveRecord::Base
validates :title, presence: true, length: { within: 0..255 } 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, 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' } validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' }
delegate :name, :email, to: :user, prefix: true delegate :name, :email, to: :user, prefix: true
......
...@@ -14,6 +14,10 @@ ...@@ -14,6 +14,10 @@
# #
class Milestone < ActiveRecord::Base 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 InternalId
include Sortable include Sortable
......
...@@ -356,7 +356,7 @@ class Note < ActiveRecord::Base ...@@ -356,7 +356,7 @@ class Note < ActiveRecord::Base
end end
def set_references def set_references
notice_added_references(project, author) create_new_cross_references!(project, author)
end end
def editable? def editable?
......
...@@ -683,6 +683,10 @@ class Project < ActiveRecord::Base ...@@ -683,6 +683,10 @@ class Project < ActiveRecord::Base
update_attribute(:repository_size, repository.size) update_attribute(:repository_size, repository.size)
end end
def update_commit_count
update_attribute(:commit_count, repository.commit_count)
end
def forks_count def forks_count
ForkedProjectLink.where(forked_from_project_id: self.id).count ForkedProjectLink.where(forked_from_project_id: self.id).count
end end
......
...@@ -22,8 +22,12 @@ class GitlabCiService < CiService ...@@ -22,8 +22,12 @@ class GitlabCiService < CiService
API_PREFIX = "api/v1" API_PREFIX = "api/v1"
prop_accessor :project_url, :token prop_accessor :project_url, :token
validates :project_url, presence: true, if: :activated? validates :project_url,
validates :token, presence: true, if: :activated? 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? after_save :compose_service_hook, if: :activated?
......
...@@ -63,7 +63,7 @@ class IrkerService < Service ...@@ -63,7 +63,7 @@ class IrkerService < Service
help: 'Irker daemon hostname (defaults to localhost)' }, help: 'Irker daemon hostname (defaults to localhost)' },
{ type: 'text', name: 'server_port', placeholder: 6659, { type: 'text', name: 'server_port', placeholder: 6659,
help: 'Irker daemon port (defaults to 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)', help: 'A default IRC URI to prepend before each recipient (optional)',
placeholder: 'irc://irc.network.net:6697/' }, placeholder: 'irc://irc.network.net:6697/' },
{ type: 'textarea', name: 'recipients', { type: 'textarea', name: 'recipients',
......
...@@ -94,18 +94,6 @@ class Repository ...@@ -94,18 +94,6 @@ class Repository
gitlab_shell.rm_tag(path_with_namespace, tag_name) gitlab_shell.rm_tag(path_with_namespace, tag_name)
end 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 def branch_names
cache.fetch(:branch_names) { raw_repository.branch_names } cache.fetch(:branch_names) { raw_repository.branch_names }
end end
...@@ -130,28 +118,29 @@ class Repository ...@@ -130,28 +118,29 @@ class Repository
cache.fetch(:size) { raw_repository.size } cache.fetch(:size) { raw_repository.size }
end 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 def expire_cache
%i(size branch_names tag_names commit_count graph_log cache_keys.each do |key|
readme version contribution_guide changelog license).each do |key|
cache.expire(key) cache.expire(key)
end end
end end
def graph_log def rebuild_cache
cache.fetch(:graph_log) do cache_keys.each do |key|
commits = raw_repository.log(limit: 6000, skip_merges: true, cache.expire(key)
ref: root_ref) send(key)
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
end end
end end
...@@ -431,6 +420,39 @@ class Repository ...@@ -431,6 +420,39 @@ class Repository
end end
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 private
def cache def cache
......
...@@ -177,6 +177,10 @@ class User < ActiveRecord::Base ...@@ -177,6 +177,10 @@ class User < ActiveRecord::Base
# Note: When adding an option, it MUST go on the end of the array. # Note: When adding an option, it MUST go on the end of the array.
enum dashboard: [:projects, :stars] 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 alias_attribute :private_token, :authentication_token
delegate :path, to: :namespace, allow_nil: true, prefix: true delegate :path, to: :namespace, allow_nil: true, prefix: true
...@@ -322,6 +326,16 @@ class User < ActiveRecord::Base ...@@ -322,6 +326,16 @@ class User < ActiveRecord::Base
@reset_token @reset_token
end 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 def namespace_uniq
namespace_name = self.username namespace_name = self.username
existing_namespace = Namespace.by_path(namespace_name) existing_namespace = Namespace.by_path(namespace_name)
......
...@@ -21,7 +21,6 @@ class GitPushService ...@@ -21,7 +21,6 @@ class GitPushService
project.ensure_satellite_exists project.ensure_satellite_exists
project.repository.expire_cache project.repository.expire_cache
project.update_repository_size
if push_remove_branch?(ref, newrev) if push_remove_branch?(ref, newrev)
@push_commits = [] @push_commits = []
...@@ -61,6 +60,7 @@ class GitPushService ...@@ -61,6 +60,7 @@ class GitPushService
EventCreateService.new.push(project, user, @push_data) EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :push_hooks) project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup, :push_hooks) project.execute_services(@push_data.dup, :push_hooks)
ProjectCacheWorker.perform_async(project.id)
end end
protected protected
......
...@@ -2,15 +2,15 @@ class GitTagPushService ...@@ -2,15 +2,15 @@ class GitTagPushService
attr_accessor :project, :user, :push_data attr_accessor :project, :user, :push_data
def execute(project, user, oldrev, newrev, ref) 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) @push_data = build_push_data(oldrev, newrev, ref)
EventCreateService.new.push(project, user, @push_data) EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :tag_push_hooks) project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks) project.execute_services(@push_data.dup, :tag_push_hooks)
ProjectCacheWorker.perform_async(project.id)
project.repository.expire_cache
true true
end end
......
module Issues module Issues
class UpdateService < Issues::BaseService class UpdateService < Issues::BaseService
def execute(issue) def execute(issue)
state = params[:state_event] case params.delete(:state_event)
case state
when 'reopen' when 'reopen'
Issues::ReopenService.new(project, current_user, {}).execute(issue) Issues::ReopenService.new(project, current_user, {}).execute(issue)
when 'close' when 'close'
Issues::CloseService.new(project, current_user, {}).execute(issue) 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 end
params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
...@@ -20,8 +14,7 @@ module Issues ...@@ -20,8 +14,7 @@ module Issues
filter_params filter_params
old_labels = issue.labels.to_a old_labels = issue.labels.to_a
if params.present? && issue.update_attributes(params.except(:state_event, if params.present? && issue.update_attributes(params)
:task_num))
issue.reset_events_cache issue.reset_events_cache
if issue.labels != old_labels if issue.labels != old_labels
...@@ -42,7 +35,7 @@ module Issues ...@@ -42,7 +35,7 @@ module Issues
create_title_change_note(issue, issue.previous_changes['title'].first) create_title_change_note(issue, issue.previous_changes['title'].first)
end end
issue.notice_added_references(issue.project, current_user) issue.create_new_cross_references!(issue.project, current_user)
execute_hooks(issue, 'update') execute_hooks(issue, 'update')
end end
......
...@@ -11,17 +11,11 @@ module MergeRequests ...@@ -11,17 +11,11 @@ module MergeRequests
params.except!(:target_project_id) params.except!(:target_project_id)
params.except!(:source_branch) params.except!(:source_branch)
state = params[:state_event] case params.delete(:state_event)
case state
when 'reopen' when 'reopen'
MergeRequests::ReopenService.new(project, current_user, {}).execute(merge_request) MergeRequests::ReopenService.new(project, current_user, {}).execute(merge_request)
when 'close' when 'close'
MergeRequests::CloseService.new(project, current_user, {}).execute(merge_request) 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 end
params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
...@@ -30,9 +24,7 @@ module MergeRequests ...@@ -30,9 +24,7 @@ module MergeRequests
filter_params filter_params
old_labels = merge_request.labels.to_a old_labels = merge_request.labels.to_a
if params.present? && merge_request.update_attributes( if params.present? && merge_request.update_attributes(params)
params.except(:state_event, :task_num)
)
merge_request.reset_events_cache merge_request.reset_events_cache
if merge_request.labels != old_labels if merge_request.labels != old_labels
...@@ -67,7 +59,7 @@ module MergeRequests ...@@ -67,7 +59,7 @@ module MergeRequests
merge_request.mark_as_unchecked merge_request.mark_as_unchecked
end 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') execute_hooks(merge_request, 'update')
end end
......
...@@ -11,19 +11,16 @@ module Projects ...@@ -11,19 +11,16 @@ module Projects
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
class TransferError < StandardError; end class TransferError < StandardError; end
def execute def execute(new_namespace)
namespace_id = params[:new_namespace_id] if allowed_transfer?(current_user, project, new_namespace)
namespace = Namespace.find_by(id: namespace_id) transfer(project, new_namespace)
if allowed_transfer?(current_user, project, namespace)
transfer(project, namespace)
else else
project.errors.add(:namespace, 'is invalid') project.errors.add(:new_namespace, 'is invalid')
false false
end end
rescue Projects::TransferService::TransferError => ex rescue Projects::TransferService::TransferError => ex
project.reload project.reload
project.errors.add(:namespace_id, ex.message) project.errors.add(:new_namespace, ex.message)
false false
end end
......
...@@ -43,6 +43,7 @@ ...@@ -43,6 +43,7 @@
%strong{class: @user.two_factor_enabled? ? 'cgreen' : 'cred'} %strong{class: @user.two_factor_enabled? ? 'cgreen' : 'cred'}
- if @user.two_factor_enabled? - if @user.two_factor_enabled?
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 - else
Disabled Disabled
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
- if event.created_project? - if event.created_project?
= cache [event, current_user] do = cache [event, current_user] do
= image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:''
= render "events/event/created_project", event: event = render "events/event/created_project", event: event
- else - else
= cache event do = cache event do
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
.repo-info .repo-info
- unless project.empty_repo? - 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; &middot;
= link_to pluralize(project.repository.branch_names.count, 'branch'), namespace_project_branches_path(project.namespace, project) = link_to pluralize(project.repository.branch_names.count, 'branch'), namespace_project_branches_path(project.namespace, project)
&middot; &middot;
......
...@@ -79,6 +79,12 @@ ...@@ -79,6 +79,12 @@
%td.shortcut %td.shortcut
.key g .key g
.key p .key p
%td
Go to the project's home page
%tr
%td.shortcut
.key g
.key e
%td %td
Go to the project's activity feed Go to the project's activity feed
%tr %tr
......
...@@ -7,14 +7,29 @@ ...@@ -7,14 +7,29 @@
%title= page_title %title= page_title
= favicon_link_tag 'favicon.ico' = 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" = javascript_include_tag "application"
= csrf_meta_tags = csrf_meta_tags
= include_gon = include_gon
%meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'} %meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'}
%meta{name: 'theme-color', content: '#474D57'} %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 = yield :meta_tags
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id') = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
......
.page-with-sidebar{ class: nav_sidebar_class } .page-with-sidebar{ class: nav_sidebar_class }
= render "layouts/broadcast" = render "layouts/broadcast"
.sidebar-wrapper .sidebar-wrapper.nicescroll
- if defined?(sidebar) && sidebar - if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}" = render "layouts/nav/#{sidebar}"
- elsif current_user - elsif current_user
......
%ul.nav.nav-sidebar %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 = nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home', data: {placement: 'right'} do = link_to group_path(@group), title: 'Home', data: {placement: 'right'} do
= icon('dashboard fw') = icon('dashboard fw')
%span %span
Activity Group
- if current_user - if current_user
= nav_link(controller: [:group, :milestones]) do = nav_link(controller: [:group, :milestones]) do
= link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} do = link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} do
......
%ul.nav.nav-sidebar %ul.nav.nav-sidebar
= nav_link do = 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') = icon('caret-square-o-left fw')
%span %span
Back to group Back to group
......
%ul.nav.nav-sidebar %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 = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile', data: {placement: 'right'} do = link_to profile_path, title: 'Profile', data: {placement: 'right'} do
= icon('user fw') = icon('user fw')
......
%ul.nav.nav-sidebar %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 = nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project', data: {placement: 'right'} do = link_to project_path(@project), title: 'Project', class: 'shortcuts-project', data: {placement: 'right'} do
= icon('dashboard fw') = icon('home fw')
%span %span
Project 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 - if project_nav_tab? :files
= nav_link(controller: %w(tree blob blame edit_tree new_tree)) do = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do
= link_to namespace_project_tree_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree', data: {placement: 'right'} do = link_to namespace_project_tree_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree', data: {placement: 'right'} do
......
%ul.nav.nav-sidebar %ul.nav.nav-sidebar
= nav_link do = 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') = icon('caret-square-o-left fw')
%span %span
Back to project Back to project
......
...@@ -66,4 +66,4 @@ ...@@ -66,4 +66,4 @@
%td= token.scopes %td= token.scopes
%td= render 'doorkeeper/authorized_applications/delete_form', token: token %td= render 'doorkeeper/authorized_applications/delete_form', token: token
- else - else
%p.light You dont have any authorized applications %p.light You don't have any authorized applications
...@@ -38,5 +38,13 @@ ...@@ -38,5 +38,13 @@
= link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank') = link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank')
.col-sm-10 .col-sm-10
= f.select :dashboard, dashboard_choices, {}, class: 'form-control' = 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 .panel-footer
= f.submit 'Save', class: 'btn btn-save' = f.submit 'Save', class: 'btn btn-save'
= 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()
.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
- empty_repo = @project.empty_repo? - empty_repo = @project.empty_repo?
.project-home-panel.clearfix{:class => ("empty-project" if empty_repo)} .project-home-panel.clearfix{:class => ("empty-project" if empty_repo)}
.project-identicon-holder .project-identicon-holder
= project_icon(@project, alt: '', class: 'avatar project-avatar') = project_icon(@project, alt: '', class: 'project-avatar avatar s90')
.project-home-row.project-home-row-top .project-home-desc.lead
.project-home-desc
- if @project.description.present? - if @project.description.present?
= markdown(@project.description, pipeline: :description) = 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 .project-repo-buttons
.inline.star.js-toggler-container{class: @show_star ? 'on' : ''} = render 'projects/buttons/star'
- 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 - unless empty_repo
- if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace = render 'projects/buttons/fork'
.inline.fork-buttons.prepend-left-10
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - if forked_from_project = @project.forked_from_project
= 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 project_path(forked_from_project), class: 'btn' do
= link_to_toggle_fork = icon("code-fork fw")
- else Forked from
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-sm btn-default' do = forked_from_project.namespace.try(:name)
= link_to_toggle_fork
- 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"
- 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
- 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.
%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)
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
%input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' } %input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' }
.zen-backdrop .zen-backdrop
- classes << ' js-gfm-input markdown-area' - 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 = link_to nil, class: 'zen-enter-link', tabindex: '-1' do
%i.fa.fa-expand %i.fa.fa-expand
Edit in fullscreen Edit in fullscreen
......
= render 'projects/activity'
- page_title @blob.path, @ref - page_title @blob.path, @ref
= render 'projects/last_push'
%div.tree-ref-holder %div.tree-ref-holder
= render 'shared/ref_switcher', destination: 'blob', path: @path = render 'shared/ref_switcher', destination: 'blob', path: @path
......
- 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
- 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
- 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
.alert.alert-warning .alert.alert-warning
%h4 %h4
Too many changes. Too many changes to show.
.pull-right .pull-right
- unless diff_hard_limit_enabled? - 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" = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true)), class: "btn btn-sm btn-warning"
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
.col-sm-10= f.select(:default_branch, @repository.branch_names, {}, {class: 'select2 select-wide'}) .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 .form-group
= f.label :tag_list, "Tags", class: 'control-label' = f.label :tag_list, "Tags", class: 'control-label'
...@@ -176,7 +176,7 @@ ...@@ -176,7 +176,7 @@
.form-group .form-group
= label_tag :new_namespace_id, nil, class: 'control-label' do = label_tag :new_namespace_id, nil, class: 'control-label' do
%span Namespace %span Namespace
.col-sm-10 .col-sm-9
.form-group .form-group
= select_tag :new_namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace', class: 'select2' } = select_tag :new_namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace', class: 'select2' }
%ul %ul
......
...@@ -4,30 +4,30 @@ ...@@ -4,30 +4,30 @@
= render "home_panel" = render "home_panel"
.center.well .center.light-well
%h3 %h3.page-title
The repository for this project is empty The repository for this project is empty
%h4 %p
You can If you already have files you can push them using command line instructions below.
= link_to namespace_project_new_blob_path(@project.namespace, @project, 'master'), class: 'btn btn-new btn-lg' do %br
add a file Otherwise you can start with
&nbsp;or do a push via the command line. = link_to "adding README", new_readme_path, class: 'underlined-link'
file to this project.
.well .prepend-top-20
= render "shared/clone_panel" %h3.page-title
%h4 Command line instructions
%strong Command line instructions
%div.git-empty %div.git-empty
%fieldset %fieldset
%legend Git global setup %h5 Git global setup
%pre.dark %pre.light-well
:preserve :preserve
git config --global user.name "#{git_user_name}" git config --global user.name "#{git_user_name}"
git config --global user.email "#{git_user_email}" git config --global user.email "#{git_user_email}"
%fieldset %fieldset
%legend Create a new repository %h5 Create a new repository
%pre.dark %pre.light-well
:preserve :preserve
git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')} git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
cd #{@project.path} cd #{@project.path}
...@@ -37,8 +37,8 @@ ...@@ -37,8 +37,8 @@
git push -u origin master git push -u origin master
%fieldset %fieldset
%legend Existing folder or Git repository %h5 Existing folder or Git repository
%pre.dark %pre.light-well
:preserve :preserve
cd existing_folder cd existing_folder
git init git init
......
- page_title "Commit statistics" - page_title "Commit statistics"
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'graphs_commits'
= render 'head' = render 'head'
%p.lead %p.lead
Commit statistics for Commit statistics for
%strong #{@repository.root_ref} %strong #{@ref}
#{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')} #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')}
.row .row
......
- page_title "Contributor statistics" - page_title "Contributor statistics"
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'graphs'
= render 'head' = render 'head'
.loading-graph .loading-graph
.center .center
%h3.page-title %h3.page-title
...@@ -11,7 +14,7 @@ ...@@ -11,7 +14,7 @@
.header.clearfix .header.clearfix
%h3#date_header.page-title %h3#date_header.page-title
%p.light %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"} %input#brush_change{:type => "hidden"}
.graphs .graphs
#contributors-master #contributors-master
...@@ -35,4 +38,3 @@ ...@@ -35,4 +38,3 @@
$(".stat-graph").fadeIn(); $(".stat-graph").fadeIn();
$(".loading-graph").hide(); $(".loading-graph").hide();
dataType: "json" dataType: "json"
...@@ -6,5 +6,5 @@ ...@@ -6,5 +6,5 @@
= pluralize label.open_issues_count, 'open issue' = pluralize label.open_issues_count, 'open issue'
- if can? current_user, :admin_label, @project - if can? current_user, :admin_label, @project
= link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn' = 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-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} = 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?"}
...@@ -31,6 +31,16 @@ ...@@ -31,6 +31,16 @@
%li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch) %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff) %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/show/how_to_merge"
= render "projects/merge_requests/widget/show.html.haml" = render "projects/merge_requests/widget/show.html.haml"
......
:plain :plain
$(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}"); $(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}");
$('.js-timeago').timeago()
:plain :plain
$(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}"); $(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}");
$('.js-timeago').timeago()
- page_title "Merge Requests" - page_title "Merge Requests"
= render 'projects/last_push'
.append-bottom-10 .append-bottom-10
.pull-right .pull-right
= render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project) = render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
......
...@@ -5,6 +5,10 @@ ...@@ -5,6 +5,10 @@
%i.fa.fa-pencil-square-o %i.fa.fa-pencil-square-o
Edit 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 '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 %h4
= link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) = 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? - if milestone.expired? and not milestone.closed?
......
...@@ -19,6 +19,9 @@ ...@@ -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" = 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 - 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 '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 %hr
- if @milestone.issues.any? && @milestone.can_be_closed? - if @milestone.issues.any? && @milestone.can_be_closed?
......
...@@ -85,7 +85,7 @@ ...@@ -85,7 +85,7 @@
%li %li
The import will time out after 4 minutes. For big repositories, use a clone/push combination. The import will time out after 4 minutes. For big repositories, use a clone/push combination.
%li %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 %hr.prepend-botton-10
......
...@@ -5,8 +5,8 @@ ...@@ -5,8 +5,8 @@
= render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field' = render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field'
.comment-hints.clearfix .comment-hints.clearfix
.pull-left Comments are parsed with #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'),{ target: '_blank', tabindex: -1 }} .pull-left #{link_to '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-right #{link_to 'Attach a file', '#', class: 'markdown-selector', tabindex: -1 }
.note-form-actions .note-form-actions
.buttons .buttons
......
...@@ -12,8 +12,14 @@ ...@@ -12,8 +12,14 @@
classes: 'note_text js-note-text' classes: 'note_text js-note-text'
.comment-hints.clearfix .comment-hints.clearfix
.pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }} .pull-left
.pull-right Attach files by dragging &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. = 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 .error-alert
.note-form-actions .note-form-actions
......
...@@ -56,7 +56,7 @@ ...@@ -56,7 +56,7 @@
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''} .note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
= cache [note, 'markdown'] do = cache [note, 'markdown', user_color_scheme_class] do
.note-text .note-text
= preserve do = preserve do
= markdown(note.note, {no_header_anchors: true}) = markdown(note.note, {no_header_anchors: true})
......
...@@ -6,11 +6,56 @@ ...@@ -6,11 +6,56 @@
= render 'shared/no_ssh' = render 'shared/no_ssh'
= render 'shared/no_password' = render 'shared/no_password'
- if prefer_readme?
= render 'projects/last_push'
= render "home_panel" = render "home_panel"
= render 'shared/show_aside'
.row .project-stats
%section.col-md-8 %ul.nav.nav-pills
= render 'section' %li
%aside.col-md-4.project-side = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do
= render 'aside' = 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
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
- if current_user - 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") = 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 .tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path = render 'shared/ref_switcher', destination: 'tree', path: @path
......
- blob = @project.repository.parse_search_result(blob)
.blob-result .blob-result
.file-holder .file-holder
.file-title .file-title
......
- wiki_blob = @project.repository.parse_search_result(wiki_blob)
.blob-result .blob-result
.file-holder .file-holder
.file-title .file-title
......
...@@ -20,7 +20,7 @@ ...@@ -20,7 +20,7 @@
:"data-container" => "body"} :"data-container" => "body"}
= gitlab_config.protocol.upcase = 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 = 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 .input-group-addon
.visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(project.visibility_level)} project" } .visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(project.visibility_level)} project" }
= visibility_level_icon(project.visibility_level) = visibility_level_icon(project.visibility_level)
......
- name = field[:name] - name = field[:name]
- title = field[:title] || name.humanize - title = field[:title] || name.humanize
- value = service_field_value(field[:type], @service.send(name)) - value = @service.send(name)
- type = field[:type] - type = field[:type]
- placeholder = field[:placeholder] - placeholder = field[:placeholder]
- choices = field[:choices] - choices = field[:choices]
...@@ -19,6 +19,6 @@ ...@@ -19,6 +19,6 @@
- elsif type == 'select' - elsif type == 'select'
= form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" } = form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
- elsif type == 'password' - elsif type == 'password'
= form.password_field name, placeholder: value, class: 'form-control' = form.password_field name, value: value, class: 'form-control'
- if help - if help
%span.help-block= help %span.help-block= help
- Gitlab::VisibilityLevel.values.each do |level| - Gitlab::VisibilityLevel.values.each do |level|
- next if skip_level?(form_model, level)
.radio .radio
- restricted = restricted_visibility_levels.include?(level) - restricted = restricted_visibility_levels.include?(level)
= form.label "#{model_method}_#{level}" do = form.label "#{model_method}_#{level}" do
......
...@@ -8,7 +8,7 @@ ...@@ -8,7 +8,7 @@
- else - else
none none
.issuable-context-selectbox .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) = 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 %div.prepend-top-20.clearfix
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
- else - else
none none
.issuable-context-selectbox .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'}) = f.select(:milestone_id, milestone_options(issuable), { include_blank: 'Select milestone' }, {class: 'select2 select2-compact js-select2 js-milestone'})
= hidden_field_tag :issuable_context = hidden_field_tag :issuable_context
= f.submit class: 'btn hide' = f.submit class: 'btn hide'
......
...@@ -43,11 +43,15 @@ ...@@ -43,11 +43,15 @@
placeholder: 'Author', class: 'trigger-submit', any_user: true, first_user: true) placeholder: 'Author', class: 'trigger-submit', any_user: true, first_user: true)
.filter-item.inline.milestone-filter .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 - if @project
.filter-item.inline.labels-filter .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 .pull-right
= render 'shared/sort_dropdown' = render 'shared/sort_dropdown'
......
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
...@@ -28,7 +28,7 @@ class RepositoryImportWorker ...@@ -28,7 +28,7 @@ class RepositoryImportWorker
project.import_finish project.import_finish
project.save project.save
project.satellite.create unless project.satellite.exists? 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' Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
end end
end end
...@@ -96,6 +96,7 @@ module Gitlab ...@@ -96,6 +96,7 @@ module Gitlab
end end
redis_config_hash[:namespace] = 'cache:gitlab' 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 config.cache_store = :redis_store, redis_config_hash
# This is needed for gitlab-shell # This is needed for gitlab-shell
......
...@@ -11,6 +11,7 @@ if Gitlab::LDAP::Config.enabled? ...@@ -11,6 +11,7 @@ if Gitlab::LDAP::Config.enabled?
end end
end end
OmniAuth.config.full_host = Settings.gitlab['url']
OmniAuth.config.allowed_request_methods = [:post] 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) #In case of auto sign-in, the GET method is used (users don't get to click on a button)
OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present? OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present?
......
...@@ -6,7 +6,8 @@ Doorkeeper.configure do ...@@ -6,7 +6,8 @@ Doorkeeper.configure do
# This block will be called to check whether the resource owner is authenticated or not. # This block will be called to check whether the resource owner is authenticated or not.
resource_owner_authenticator do resource_owner_authenticator do
# Put your resource owner authentication logic here. # 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) current_user || redirect_to(new_user_session_url)
end end
......
...@@ -159,6 +159,7 @@ Gitlab::Application.routes.draw do ...@@ -159,6 +159,7 @@ Gitlab::Application.routes.draw do
put :block put :block
put :unblock put :unblock
put :unlock put :unlock
patch :disable_two_factor
delete 'remove/:email_id', action: 'remove_email', as: 'remove_email' delete 'remove/:email_id', action: 'remove_email', as: 'remove_email'
end end
end end
...@@ -314,6 +315,7 @@ Gitlab::Application.routes.draw do ...@@ -314,6 +315,7 @@ Gitlab::Application.routes.draw do
post :toggle_star post :toggle_star
post :markdown_preview post :markdown_preview
get :autocomplete_sources get :autocomplete_sources
get :activity
end end
scope module: :projects do scope module: :projects do
...@@ -479,7 +481,7 @@ Gitlab::Application.routes.draw do ...@@ -479,7 +481,7 @@ Gitlab::Application.routes.draw do
end end
end end
resources :milestones, except: [:destroy], constraints: { id: /\d+/ } do resources :milestones, constraints: { id: /\d+/ } do
member do member do
put :sort_issues put :sort_issues
put :sort_merge_requests put :sort_merge_requests
......
...@@ -5,7 +5,7 @@ Gitlab::Seeder.quiet do ...@@ -5,7 +5,7 @@ Gitlab::Seeder.quiet do
s.email = 'admin@example.com' s.email = 'admin@example.com'
s.notification_email = 'admin@example.com' s.notification_email = 'admin@example.com'
s.username = 'root' s.username = 'root'
s.password = 'password' s.password = '5iveL!fe'
s.admin = true s.admin = true
s.projects_limit = 100 s.projects_limit = 100
s.confirmed_at = DateTime.now s.confirmed_at = DateTime.now
......
...@@ -11,9 +11,42 @@ Sidekiq::Testing.inline! do ...@@ -11,9 +11,42 @@ Sidekiq::Testing.inline! do
'https://github.com/twitter/flight.git', 'https://github.com/twitter/flight.git',
'https://github.com/twitter/typeahead.js.git', 'https://github.com/twitter/typeahead.js.git',
'https://github.com/h5bp/html5-boilerplate.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_path, project_path = url.split('/')[-2..-1]
group = Group.find_by(path: group_path) group = Group.find_by(path: group_path)
......
if ENV['GITLAB_ROOT_PASSWORD'].blank? if ENV['GITLAB_ROOT_PASSWORD'].blank?
password = 'password' password = '5iveL!fe'
expire_time = Time.now expire_time = Time.now
else else
password = ENV['GITLAB_ROOT_PASSWORD'] password = ENV['GITLAB_ROOT_PASSWORD']
......
class AddProjectViewToUsers < ActiveRecord::Migration
def change
add_column :users, :project_view, :integer, default: 0
end
end
class AddCommitsCountToProject < ActiveRecord::Migration
def change
add_column :projects, :commit_count, :integer, default: 0
end
end
...@@ -11,7 +11,7 @@ ...@@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -28,11 +28,11 @@ ActiveRecord::Schema.define(version: 20150620233230) do ...@@ -28,11 +28,11 @@ ActiveRecord::Schema.define(version: 20150620233230) do
t.integer "default_branch_protection", default: 2 t.integer "default_branch_protection", default: 2
t.boolean "twitter_sharing_enabled", default: true t.boolean "twitter_sharing_enabled", default: true
t.text "restricted_visibility_levels" t.text "restricted_visibility_levels"
t.boolean "version_check_enabled", default: true
t.integer "max_attachment_size", default: 10, null: false t.integer "max_attachment_size", default: 10, null: false
t.integer "default_project_visibility" t.integer "default_project_visibility"
t.integer "default_snippet_visibility" t.integer "default_snippet_visibility"
t.text "restricted_signup_domains" t.text "restricted_signup_domains"
t.boolean "version_check_enabled", default: true
t.boolean "user_oauth_applications", default: true t.boolean "user_oauth_applications", default: true
t.string "after_sign_out_path" t.string "after_sign_out_path"
t.integer "session_expire_delay", default: 10080, null: false t.integer "session_expire_delay", default: 10080, null: false
...@@ -374,6 +374,7 @@ ActiveRecord::Schema.define(version: 20150620233230) do ...@@ -374,6 +374,7 @@ ActiveRecord::Schema.define(version: 20150620233230) do
t.integer "star_count", default: 0, null: false t.integer "star_count", default: 0, null: false
t.string "import_type" t.string "import_type"
t.string "import_source" t.string "import_source"
t.integer "commit_count", default: 0
end end
add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree 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 ...@@ -510,13 +511,14 @@ ActiveRecord::Schema.define(version: 20150620233230) do
t.string "bitbucket_access_token" t.string "bitbucket_access_token"
t.string "bitbucket_access_token_secret" t.string "bitbucket_access_token_secret"
t.string "location" t.string "location"
t.string "public_email", default: "", null: false
t.string "encrypted_otp_secret" t.string "encrypted_otp_secret"
t.string "encrypted_otp_secret_iv" t.string "encrypted_otp_secret_iv"
t.string "encrypted_otp_secret_salt" t.string "encrypted_otp_secret_salt"
t.boolean "otp_required_for_login", default: false, null: false t.boolean "otp_required_for_login", default: false, null: false
t.text "otp_backup_codes" t.text "otp_backup_codes"
t.string "public_email", default: "", null: false
t.integer "dashboard", default: 0 t.integer "dashboard", default: 0
t.integer "project_view", default: 0
end end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
- [Users](users.md) - [Users](users.md)
- [Session](session.md) - [Session](session.md)
- [Projects](projects.md) - [Projects](projects.md) including setting Webhooks
- [Project Snippets](project_snippets.md) - [Project Snippets](project_snippets.md)
- [Services](services.md) - [Services](services.md)
- [Repositories](repositories.md) - [Repositories](repositories.md)
...@@ -20,6 +20,7 @@ ...@@ -20,6 +20,7 @@
- [System Hooks](system_hooks.md) - [System Hooks](system_hooks.md)
- [Groups](groups.md) - [Groups](groups.md)
- [Namespaces](namespaces.md) - [Namespaces](namespaces.md)
- [Settings](settings.md)
## Clients ## Clients
......
...@@ -49,7 +49,8 @@ Parameters: ...@@ -49,7 +49,8 @@ Parameters:
"state": "active", "state": "active",
"created_at": "2012-04-29T08:46:00Z" "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: ...@@ -94,7 +95,8 @@ Parameters:
"state": "active", "state": "active",
"created_at": "2012-04-29T08:46:00Z" "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: ...@@ -118,6 +120,7 @@ Parameters:
"project_id": 4, "project_id": 4,
"title": "Blanditiis beatae suscipit hic assumenda et molestias nisi asperiores repellat et.", "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.", "description": "Qui voluptatibus placeat ipsa alias quasi. Deleniti rem ut sint. Optio velit qui distinctio.",
"work_in_progress": false,
"state": "reopened", "state": "reopened",
"created_at": "2015-02-02T19:49:39.159Z", "created_at": "2015-02-02T19:49:39.159Z",
"updated_at": "2015-02-02T20:08:49.959Z", "updated_at": "2015-02-02T20:08:49.959Z",
...@@ -336,14 +339,6 @@ Parameters: ...@@ -336,14 +339,6 @@ Parameters:
```json ```json
{ {
"author": {
"id": 1,
"username": "admin",
"email": "admin@example.com",
"name": "Administrator",
"blocked": false,
"created_at": "2012-04-29T08:46:00Z"
},
"note": "text1" "note": "text1"
} }
``` ```
......
...@@ -479,6 +479,9 @@ rely on the returned JSON structure. ...@@ -479,6 +479,9 @@ rely on the returned JSON structure.
## Hooks ## Hooks
Also called Project Hooks and Webhooks.
These are different for [System Hooks](system_hooks.md) that are system wide.
### List project hooks ### List project hooks
Get a list of project hooks. Get a list of project hooks.
......
# 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": ""
}
```
...@@ -11,3 +11,9 @@ Step-by-step guides on the basics of working with Git and GitLab. ...@@ -11,3 +11,9 @@ Step-by-step guides on the basics of working with Git and GitLab.
* [Basic Git commands](basic-git-commands.md) * [Basic Git commands](basic-git-commands.md)
* [Create a project](create-project.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)
# Basic Git commands # 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 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 git pull REMOTE NAME-OF-BRANCH -u
``` ```
(REMOTE: origin) (NAME-OF-BRANCH: could be "master" or an existing branch) (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 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 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 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 add CHANGES IN RED
git commit -m "DESCRIBE THE INTENTION OF THE COMMIT" 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 . 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 git clean -f
``` ```
* Remove all the changes that you don't want to send to gitlab.com ### Merge created branch with master branch
``` You need to be in the created branch.
git add NAME-OF-FILE -all
```
* Merge created branch with master branch. You need to be in the created branch
``` ```
git checkout NAME-OF-BRANCH git checkout NAME-OF-BRANCH
git merge master git merge master
......
...@@ -2,46 +2,47 @@ ...@@ -2,46 +2,47 @@
## Start working on your project ## 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) ![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) ![Copy the HTTPS or SSH](basicsimages/https.png)
## On the command line ## 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 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 cd NAME-OF-PROJECT-OR-FILE
``` ```
* Go back one directory or file ### Go back one directory or file
``` ```
cd ../ cd ../
``` ```
* To see what’s in the directory that you are in ### View what’s in the directory that you are in
``` ```
ls ls
``` ```
* Create a directory ### Create a directory
``` ```
mkdir NAME-OF-YOUR-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 touch README.md
nano README.md nano README.md
...@@ -51,22 +52,23 @@ nano README.md ...@@ -51,22 +52,23 @@ nano README.md
#### Press: enter #### Press: enter
``` ```
* Remove a file ### Remove a file
``` ```
rm NAME-OF-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 rm -rf NAME-OF-DIRECTORY
``` ```
* View history in the command line ### View history in the command line
``` ```
history 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 sudo
``` ```
# 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)
# 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)
# How to create a project in GitLab # 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) ![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) 1. Project path or the name of your project (you can't add spaces, so you can use hyphens or underscores)
......
...@@ -4,34 +4,34 @@ You need to connect your computer to your GitLab account through SSH Keys. They ...@@ -4,34 +4,34 @@ You need to connect your computer to your GitLab account through SSH Keys. They
## Generate your SSH Key ## 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 ## 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) ![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) ![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) ![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) ![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) ![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.
# 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.
# 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 ## 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 - [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 ...@@ -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 ## Check if Git has already been installed
* Git is usually preinstalled on Mac and Linux Git is usually preinstalled on Mac and Linux.
* Type the following command and then press enter
Type the following command and then press enter:
``` ```
git --version 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 ## Add your Git username and set your email
* It is important because every Git commit that you create will use this information 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
On your shell, type the following command to add your username:
``` ```
git config --global user.name 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 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 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 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 ## Check your information
* To view the information that you entered, type To view the information that you entered, type:
``` ```
git config --global --list git config --global --list
``` ```
...@@ -404,7 +404,7 @@ NOTE: Supply `SANITIZE=true` environment variable to `gitlab:check` to omit proj ...@@ -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: 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 root
password 5iveL!fe
**Important Note:** On login you'll be prompted to change the password. **Important Note:** On login you'll be prompted to change the password.
......
...@@ -57,6 +57,7 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim ...@@ -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 - 16 cores supports up to 10,000 users
- 32 cores supports up to 20,000 users - 32 cores supports up to 20,000 users
- 64 cores supports up to 40,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 ### Memory
...@@ -64,15 +65,17 @@ You need at least 2GB of addressable memory (RAM + swap) to install and use GitL ...@@ -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. 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. - 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 - 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 500 users - **2GB RAM** is the **recommended** memory size and supports up to 100 users
- 4GB RAM supports up to 2,000 users - 4GB RAM supports up to 1,000 users
- 8GB RAM supports up to 5,000 users - 8GB RAM supports up to 2,000 users
- 16GB RAM supports up to 10,000 users - 16GB RAM supports up to 4,000 users
- 32GB RAM supports up to 20,000 users - 32GB RAM supports up to 8,000 users
- 64GB RAM supports up to 40,000 users - 64GB RAM supports up to 16,000 users
- 128GB RAM supports up to 32,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. - 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 ## Unicorn Workers
...@@ -107,3 +110,7 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o ...@@ -107,3 +110,7 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
- Safari 7+ (known problem: required fields in html5 do not work) - Safari 7+ (known problem: required fields in html5 do not work)
- Opera (Latest released version) - Opera (Latest released version)
- IE 10+ - 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
...@@ -30,3 +30,9 @@ will be. Setting it to **Starred Projects** will make that Dashboard view the ...@@ -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. default when signing in or clicking the application logo in the upper left.
The default is **Your Projects**. 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**.
...@@ -63,5 +63,10 @@ your phone's application or a recovery code to log in. ...@@ -63,5 +63,10 @@ your phone's application or a recovery code to log in.
1. Go to **Account**. 1. Go to **Account**.
1. Click **Disable Two-factor Authentication**. 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 [Google Authenticator]: https://support.google.com/accounts/answer/1066447?hl=en
[FreeOTP]: https://fedorahosted.org/freeotp/ [FreeOTP]: https://fedorahosted.org/freeotp/
...@@ -34,7 +34,7 @@ need to follow the firsts steps of the next section. ...@@ -34,7 +34,7 @@ need to follow the firsts steps of the next section.
in the `Server host` field on the Web page in the `Server host` field on the Web page
1. Enter the server port of `irkerd` (e.g. defaults to 6659) in the 1. Enter the server port of `irkerd` (e.g. defaults to 6659) in the
`Server port` field on the Web page. `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 `irc[s]://domain.name` and will be prepend to each and every channel provided
by the user which is not a full URI. by the user which is not a full URI.
1. Specify the recipients (e.g. #channel1, user1, etc.) 1. Specify the recipients (e.g. #channel1, user1, etc.)
......
...@@ -9,6 +9,13 @@ This archive will be saved in backup_path (see `config/gitlab.yml`). ...@@ -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. 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 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)* 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 ...@@ -160,15 +167,39 @@ gitlab_rails['backup_archive_permissions'] = 0644 # Makes the backup archives wo
## Storing configuration files ## 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 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 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 ## 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. 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 ### Installation from source
``` ```
......
...@@ -6,6 +6,7 @@ It starts 7 working days before the release. ...@@ -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 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 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 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 ## Release Manager
......
...@@ -105,3 +105,6 @@ IdentityFile ~/my-ssh-key-directory/company-com-private-key-filename ...@@ -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. 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. 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.
# From 6.x or 7.x to 7.12 # 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.12.md) for the most up to date instructions.* *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 ## Global issue numbers
...@@ -71,7 +71,7 @@ sudo -u git -H git checkout -- db/schema.rb # local changes will be restored aut ...@@ -71,7 +71,7 @@ sudo -u git -H git checkout -- db/schema.rb # local changes will be restored aut
For GitLab Community Edition: For GitLab Community Edition:
```bash ```bash
sudo -u git -H git checkout 7-12-stable sudo -u git -H git checkout 7-13-stable
``` ```
OR OR
...@@ -79,7 +79,7 @@ OR ...@@ -79,7 +79,7 @@ OR
For GitLab Enterprise Edition: For GitLab Enterprise Edition:
```bash ```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 ## 4. Install additional packages
...@@ -162,11 +162,11 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab ...@@ -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: 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/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-12-stable/config/unicorn.rb.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. * 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 * Copy rack attack middleware config
...@@ -182,14 +182,14 @@ sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab ...@@ -182,14 +182,14 @@ sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab
### Change Nginx settings ### 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. * 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-12-stable/lib/support/nginx/gitlab-ssl 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. * 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 ### Check the version of /usr/local/bin/git
If you installed Git from source into /usr/local/bin/git then please [check 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 ## 9. Start application
......
# 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.
...@@ -16,6 +16,7 @@ git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab ...@@ -16,6 +16,7 @@ git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab
cd mysql-postgresql-converter cd mysql-postgresql-converter
mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production -p 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 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 # Import the database dump as the application database user
sudo -u git psql -f gitlabhq_production.psql -d gitlabhq_production 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. ...@@ -56,13 +57,17 @@ sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.
# Convert gitlabhq_production.mysql # Convert gitlabhq_production.mysql
sudo -u git -H mkdir db 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 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. # Replace the MySQL dump in TIMESTAMP_gitlab_backup.tar.
# Warning: if you forget to replace TIMESTAMP below, tar will create a new file # Warning: if you forget to replace TIMESTAMP below, tar will create a new file
# 'TIMESTAMP_gitlab_backup.tar' without giving an error. # '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 # Done! TIMESTAMP_gitlab_backup.tar can now be restored into a Postgres GitLab
# installation. Remember to recreate the indexes after the import. # installation. Remember to recreate the indexes after the import.
......
...@@ -7,7 +7,7 @@ ...@@ -7,7 +7,7 @@
- [Groups](groups.md) - [Groups](groups.md)
- [Keyboard shortcuts](shortcuts.md) - [Keyboard shortcuts](shortcuts.md)
- [Labels](labels.md) - [Labels](labels.md)
- [Notifications](notifications.md) - [Notification emails](notifications.md)
- [Project Features](project_features.md) - [Project Features](project_features.md)
- [Project forking workflow](forking_workflow.md) - [Project forking workflow](forking_workflow.md)
- [Protected branches](protected_branches.md) - [Protected branches](protected_branches.md)
......
# Labels # 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. 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. ...@@ -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. You will be presented with the same form as when creating a new label.
![edit label](labels/label3.png) ![edit label](labels/label3.png)
You can add labels to Merge Requests when you create or edit them.
# 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 ## Notification settings
...@@ -67,5 +67,3 @@ Below is the table of events users can be notified of: ...@@ -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 | | 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 | | 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 | | New comment | Mentioned users [1], users participating [2], project members [3] | [1] [2] not disabled, [3] higher than participating |
...@@ -7,7 +7,9 @@ RUN apt-get update -q \ ...@@ -7,7 +7,9 @@ RUN apt-get update -q \
ca-certificates \ ca-certificates \
openssh-server \ openssh-server \
wget \ wget \
apt-transport-https apt-transport-https \
vim \
nano
# Download & Install GitLab # Download & Install GitLab
# If you run GitLab Enterprise Edition point it to a location where you have downloaded it. # 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 \ ...@@ -23,12 +25,23 @@ RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
&& ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \ && ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \
&& mkdir -p /var/run/sshd && 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 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
COPY assets/wrapper /usr/local/bin/ COPY assets/wrapper /usr/local/bin/
COPY assets/gitlab.rb /etc/gitlab/
# Wrapper to handle signal, trigger runit and reconfigure GitLab # Wrapper to handle signal, trigger runit and reconfigure GitLab
CMD ["/usr/local/bin/wrapper"] CMD ["/usr/local/bin/wrapper"]
# GitLab Docker images # GitLab Docker images
## What is GitLab? The GitLab docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ce/).
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)
## After starting a container ## After starting a container
...@@ -11,152 +8,162 @@ After starting a container you can go to [http://localhost:8080/](http://localho ...@@ -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. 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. 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. Run the image:
Please run all the commands from the GitLab repo root directory. ```bash
People using boot2docker should run all the commands without sudo. 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. After this you can login to the web interface as explained above in 'After starting a container'.
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.
## 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 You can fine tune these directories to meet your requirements.
sudo docker pull sytse/gitlab-ce:7.10.1
```
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 ```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'. You can also edit just `/etc/gitlab/gitlab.rb`:
Build the image:
```bash ```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 **You may also be interesting in [Enabling HTTPS](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/nginx.md#enable-https).**
sudo docker push sytse/gitlab-ce
``` **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 ```bash
sudo docker run -i -t sytse/gitlab-ce:7.10.1 sudo docker restart gitlab
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
``` ```
## 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 ```bash
sudo docker pull sytse/gitlab-data sudo docker logs gitlab
sudo docker pull sytse/gitlab-app:7.10.1
``` ```
### Run the images Enter running container:
```bash ```bash
sudo docker run --name gitlab-data sytse/gitlab-data /bin/true sudo docker exec -it gitlab /bin/bash
sudo docker run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data sytse/gitlab-app:7.10.1
``` ```
After this you can login to the web interface as explained above in 'After starting a container'. 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 images
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 ```bash
sudo docker build --tag gitlab-data docker/data/ sudo docker stop gitlab
sudo docker build --tag gitlab-app:7.10.1 docker/app/
``` ```
After this run the images: 1. stop running container,
```bash ```bash
sudo docker run --name gitlab-data gitlab-data /bin/true sudo docker rm gitlab
sudo docker run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data gitlab-app:7.10.1
``` ```
We assume using a data volume container, this will simplify migrations and backups. 1. remove existing container,
This empty container will exist to persist as volumes the 3 directories used by GitLab, so remember not to delete it. ```bash
sudo docker pull gitlab/gitlab-ce:latest
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. create the container once again with previously specified options.
```bash ```bash
sudo docker run -ti -e TERM=linux --rm --volumes-from gitlab-data ubuntu sudo docker run --detach \
vi /etc/gitlab/gitlab.rb --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 ```bash
sudo docker stop gitlab-app sudo docker run --detach \
sudo docker rm gitlab-app --publish 1.1.1.1:443:443 --publish 1.1.1.1:80:80 --publish 1.1.1.1:22:22 \
sudo docker build --tag gitlab-app:7.10.1 docker/app/ --name gitlab \
sudo docker run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data gitlab-app:7.10.1 --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 ```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 - Ensure the containers are running
- Login to Dockerhub with `sudo docker login` - 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 ```bash
sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-app sytse/gitlab-app:7.10.1 sudo docker login
sudo docker push sytse/gitlab-app:7.10.1 sudo docker push gitlab/gitlab-ce:latest
sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-data sytse/gitlab-data
sudo docker push sytse/gitlab-data
``` ```
## Troubleshooting ## Troubleshooting
Please see the [troubleshooting](troubleshooting.md) file in this directory. 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.
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
#!/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
...@@ -13,4 +13,9 @@ function entrypoint() { ...@@ -13,4 +13,9 @@ function entrypoint() {
gitlab-ctl tail # tail all logs 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 entrypoint
FROM busybox
# Declare volumes
VOLUME ["/var/opt/gitlab", "/var/log/gitlab", "/etc/gitlab"]
# Copy assets
COPY assets/gitlab.rb /etc/gitlab/
CMD /bin/sh
# 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'
app:
build: .
{
"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
# 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'
{
"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
...@@ -9,24 +9,19 @@ postgresql['log_directory'] = '/var/log/gitlab/postgresql' ...@@ -9,24 +9,19 @@ postgresql['log_directory'] = '/var/log/gitlab/postgresql'
# Commands # Commands
```bash ```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
sudo docker rm -f gitlab_data
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 exec gitlab cat /etc/gitlab/gitlab.rb
sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab.rb
``` ```
# Interactively # Interactively
...@@ -37,7 +32,16 @@ sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab ...@@ -37,7 +32,16 @@ sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab
# - we run interactively (-t -i) # - we run interactively (-t -i)
# - we define TERM=linux because it allows to use arrow keys in vi (!!!) # - we define TERM=linux because it allows to use arrow keys in vi (!!!)
# - we choose another startup command (bash) # - 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 # Configure GitLab to redirect PostgreSQL logs
echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb
...@@ -64,10 +68,17 @@ free -m ...@@ -64,10 +68,17 @@ free -m
# Cleanup # 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.**
``` ```bash
docker rm $(docker ps -a -q) # Remove all containers with attached volumes
docker rm -v $(docker ps -a -q)
# Remove all images
docker rmi $(docker images -q) docker rmi $(docker images -q)
# Remove GitLab persistent data
rm -rf /srv/gitlab
``` ```
...@@ -4,6 +4,10 @@ Feature: Groups ...@@ -4,6 +4,10 @@ Feature: Groups
And "John Doe" is owner of group "Owned" And "John Doe" is owner of group "Owned"
And "John Doe" is guest of group "Guest" 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 @javascript
Scenario: I should see group "Owned" dashboard list Scenario: I should see group "Owned" dashboard list
When I visit group "Owned" page When I visit group "Owned" page
......
...@@ -189,6 +189,7 @@ Feature: Project Issues ...@@ -189,6 +189,7 @@ Feature: Project Issues
Given I logout Given I logout
Given public project "Community" Given public project "Community"
When I visit project "Community" page When I visit project "Community" page
And I visit project "Community" issues page
And I click link "New Issue" And I click link "New Issue"
And I should not see assignee field And I should not see assignee field
And I should not see milestone field And I should not see milestone field
......
...@@ -17,6 +17,10 @@ Feature: Project Issues Milestones ...@@ -17,6 +17,10 @@ Feature: Project Issues Milestones
And I submit new milestone "v2.3" And I submit new milestone "v2.3"
Then I should see 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 @javascript
Scenario: Listing closed issues Scenario: Listing closed issues
Given the milestone has open and closed issues Given the milestone has open and closed issues
......
...@@ -18,9 +18,22 @@ Feature: Project ...@@ -18,9 +18,22 @@ Feature: Project
Then I should see the default project avatar Then I should see the default project avatar
And I should not see the "Remove avatar" button 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 @javascript
Scenario: I should see project activity 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 Then I should see project "Shop" activity feed
Scenario: I visit edit project Scenario: I visit edit project
...@@ -38,24 +51,12 @@ Feature: Project ...@@ -38,24 +51,12 @@ Feature: Project
And change project path settings And change project path settings
Then I should see project with new 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 Scenario: I should change project default branch
When I visit edit project "Shop" page When I visit edit project "Shop" page
And change project default branch And change project default branch
And I save project And I save project
Then I should see project default branch changed 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 Scenario: I tag a project
When I visit edit project "Shop" page When I visit edit project "Shop" page
Then I should see project settings Then I should see project settings
......
...@@ -3,7 +3,7 @@ Feature: Project Shortcuts ...@@ -3,7 +3,7 @@ Feature: Project Shortcuts
Background: Background:
Given I sign in as a user Given I sign in as a user
And I own a project And I own a project
And I visit my project's home page And I visit my project's commits page
@javascript @javascript
Scenario: Navigate to files tab Scenario: Navigate to files tab
...@@ -12,6 +12,7 @@ Feature: Project Shortcuts ...@@ -12,6 +12,7 @@ Feature: Project Shortcuts
@javascript @javascript
Scenario: Navigate to commits tab Scenario: Navigate to commits tab
Given I visit my project's files page
Given I press "g" and "c" Given I press "g" and "c"
Then the active main tab should be Commits Then the active main tab should be Commits
...@@ -46,7 +47,11 @@ Feature: Project Shortcuts ...@@ -46,7 +47,11 @@ Feature: Project Shortcuts
Then the active main tab should be Wiki Then the active main tab should be Wiki
@javascript @javascript
Scenario: Navigate to project feed Scenario: Navigate to project home
Given I visit my project's files page
Given I press "g" and "p" Given I press "g" and "p"
Then the active main tab should be Home 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
...@@ -5,6 +5,10 @@ class Spinach::Features::Groups < Spinach::FeatureSteps ...@@ -5,6 +5,10 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
include SharedUser include SharedUser
include Select2Helper 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 step 'gitlab user "Mike"' do
create(:user, name: "Mike") create(:user, name: "Mike")
end end
......
...@@ -194,6 +194,11 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps ...@@ -194,6 +194,11 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end end
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 When "I visit empty project's issues page" do
project = Project.find_by(name: 'Empty Project') project = Project.find_by(name: 'Empty Project')
visit namespace_project_issues_path(project.namespace, project) visit namespace_project_issues_path(project.namespace, project)
......
...@@ -56,4 +56,12 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps ...@@ -56,4 +56,12 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
step 'I should see 3 issues' do step 'I should see 3 issues' do
expect(page).to have_selector('#tab-issues li.issue-row', count: 4) expect(page).to have_selector('#tab-issues li.issue-row', count: 4)
end 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 end
...@@ -86,14 +86,16 @@ class Spinach::Features::Project < Spinach::FeatureSteps ...@@ -86,14 +86,16 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end end
step 'I should see project "Forum" README' do step 'I should see project "Forum" README' do
expect(page).to have_link 'README.md' page.within('#README') do
expect(page).to have_content 'Sample repo for testing gitlab features' expect(page).to have_content 'Sample repo for testing gitlab features'
end end
end
step 'I should see project "Shop" README' do step 'I should see project "Shop" README' do
expect(page).to have_link 'README.md' page.within('#README') do
expect(page).to have_content 'testme' expect(page).to have_content 'testme'
end end
end
step 'I add project tags' do step 'I add project tags' do
fill_in 'Tags', with: 'tag1, tag2' fill_in 'Tags', with: 'tag1, tag2'
...@@ -114,4 +116,18 @@ class Spinach::Features::Project < Spinach::FeatureSteps ...@@ -114,4 +116,18 @@ class Spinach::Features::Project < Spinach::FeatureSteps
step 'I should not see "Snippets" button' do step 'I should not see "Snippets" button' do
expect(page).not_to have_link 'Snippets' expect(page).not_to have_link 'Snippets'
end 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 end
...@@ -33,4 +33,9 @@ class Spinach::Features::ProjectShortcuts < Spinach::FeatureSteps ...@@ -33,4 +33,9 @@ class Spinach::Features::ProjectShortcuts < Spinach::FeatureSteps
find('body').native.send_key('g') find('body').native.send_key('g')
find('body').native.send_key('w') find('body').native.send_key('w')
end end
step 'I press "g" and "e"' do
find('body').native.send_key('g')
find('body').native.send_key('e')
end
end end
...@@ -187,7 +187,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -187,7 +187,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end end
step 'I click on "add a file" link' do 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 # Remove pre-receive hook so we can push without auth
FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive')) FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive'))
......
...@@ -5,7 +5,7 @@ class Spinach::Features::ProjectStar < Spinach::FeatureSteps ...@@ -5,7 +5,7 @@ class Spinach::Features::ProjectStar < Spinach::FeatureSteps
include SharedUser include SharedUser
step "The project has no stars" do step "The project has no stars" do
expect(page).not_to have_content '.star-buttons' expect(page).not_to have_content '.toggle-star'
end end
step "The project has 0 stars" do step "The project has 0 stars" do
......
...@@ -251,6 +251,10 @@ module SharedPaths ...@@ -251,6 +251,10 @@ module SharedPaths
visit namespace_project_path(project.namespace, project) visit namespace_project_path(project.namespace, project)
end 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 step 'I visit project "Forked Shop" merge requests page' do
visit namespace_project_merge_requests_path(@forked_project.namespace, @forked_project) visit namespace_project_merge_requests_path(@forked_project.namespace, @forked_project)
end end
......
...@@ -49,4 +49,8 @@ module SharedProjectTab ...@@ -49,4 +49,8 @@ module SharedProjectTab
expect(page).to have_content('Back to project') expect(page).to have_content('Back to project')
end end
end end
step 'the active main tab should be Activity' do
ensure_active_main_tab('Activity')
end
end end
...@@ -49,5 +49,6 @@ module API ...@@ -49,5 +49,6 @@ module API
mount Namespaces mount Namespaces
mount Branches mount Branches
mount Labels mount Labels
mount Settings
end end
end end
...@@ -171,6 +171,7 @@ module API ...@@ -171,6 +171,7 @@ module API
expose :source_project_id, :target_project_id expose :source_project_id, :target_project_id
expose :label_names, as: :labels expose :label_names, as: :labels
expose :description expose :description
expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone expose :milestone, using: Entities::Milestone
end end
...@@ -277,5 +278,27 @@ module API ...@@ -277,5 +278,27 @@ module API
class BroadcastMessage < Grape::Entity class BroadcastMessage < Grape::Entity
expose :message, :starts_at, :ends_at, :color, :font expose :message, :starts_at, :ends_at, :color, :font
end 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
end end
...@@ -74,9 +74,9 @@ module API ...@@ -74,9 +74,9 @@ module API
# POST /groups/:id/projects/:project_id # POST /groups/:id/projects/:project_id
post ":id/projects/:project_id" do post ":id/projects/:project_id" do
authenticated_as_admin! authenticated_as_admin!
group = Group.find(params[:id]) group = Group.find_by(id: params[:id])
project = Project.find(params[:project_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 if result
present group present group
......
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
...@@ -18,23 +18,31 @@ module Backup ...@@ -18,23 +18,31 @@ module Backup
when "postgresql" then when "postgresql" then
$progress.print "Dumping PostgreSQL database #{config['database']} ... " $progress.print "Dumping PostgreSQL database #{config['database']} ... "
pg_env 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 end
report_success(success) report_success(success)
abort 'Backup failed' unless 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 end
def restore 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"] success = case config["adapter"]
when /^mysql/ then when /^mysql/ then
$progress.print "Restoring MySQL database #{config['database']} ... " $progress.print "Restoring MySQL database #{config['database']} ... "
system('mysql', *mysql_args, config['database'], in: db_file_name) system('mysql', *mysql_args, config['database'], in: db_file_name)
when "postgresql" then when "postgresql" then
$progress.print "Restoring PostgreSQL database #{config['database']} ... " $progress.print "Restoring PostgreSQL database #{config['database']} ... "
# Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE
# statements like MySQL.
Rake::Task["gitlab:db:drop_all_tables"].invoke
Rake::Task["gitlab:db:drop_all_postgres_sequences"].invoke
pg_env pg_env
system('psql', config['database'], '-f', db_file_name) system('psql', config['database'], '-f', db_file_name)
end end
...@@ -48,6 +56,10 @@ module Backup ...@@ -48,6 +56,10 @@ module Backup
File.join(db_dir, 'database.sql') File.join(db_dir, 'database.sql')
end end
def db_file_name_gz
File.join(db_dir, 'database.sql.gz')
end
def mysql_args def mysql_args
args = { args = {
'host' => '--host', 'host' => '--host',
......
...@@ -327,7 +327,7 @@ module Gitlab ...@@ -327,7 +327,7 @@ module Gitlab
link = "https://storage.googleapis.com/google-code-attachments/#{@repo.name}/issue-#{issue_id}/comment-#{comment_id}/#{filename}" link = "https://storage.googleapis.com/google-code-attachments/#{@repo.name}/issue-#{issue_id}/comment-#{comment_id}/#{filename}"
text = "[#{filename}](#{link})" 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 text
end.compact end.compact
end end
......
...@@ -33,6 +33,16 @@ module Gitlab ...@@ -33,6 +33,16 @@ module Gitlab
filename.downcase.end_with?(*%w(.adoc .ad .asciidoc)) filename.downcase.end_with?(*%w(.adoc .ad .asciidoc))
end 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) def previewable?(filename)
markup?(filename) markup?(filename)
end end
......
...@@ -47,6 +47,10 @@ module Gitlab ...@@ -47,6 +47,10 @@ module Gitlab
def valid_level?(level) def valid_level?(level)
options.has_value?(level) options.has_value?(level)
end end
def allowed_fork_levels(origin_level)
[PRIVATE, INTERNAL, PUBLIC].select{ |level| level <= origin_level }
end
end end
def private? def private?
......
...@@ -18,4 +18,12 @@ class RepositoryCache ...@@ -18,4 +18,12 @@ class RepositoryCache
def fetch(key, &block) def fetch(key, &block)
backend.fetch(cache_key(key), &block) backend.fetch(cache_key(key), &block)
end end
def exist?(key)
backend.exist?(cache_key(key))
end
def read(key)
backend.read(cache_key(key))
end
end end
...@@ -41,7 +41,7 @@ shell_path="/bin/bash" ...@@ -41,7 +41,7 @@ shell_path="/bin/bash"
test -f /etc/default/gitlab && . /etc/default/gitlab test -f /etc/default/gitlab && . /etc/default/gitlab
# Switch to the app_user if it is not he/she who is running the script. # 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; eval su - "$app_user" -s $shell_path -c $(echo \")$0 "$@"$(echo \"); exit;
fi fi
......
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
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
...@@ -6,7 +6,7 @@ namespace :gitlab do ...@@ -6,7 +6,7 @@ namespace :gitlab do
%W(rake rubocop), %W(rake rubocop),
%W(rake spinach), %W(rake spinach),
%W(rake spec), %W(rake spec),
%W(rake jasmine:ci) %W(rake teaspoon)
] ]
cmds.each do |cmd| cmds.each do |cmd|
......
...@@ -36,4 +36,32 @@ describe Admin::UsersController do ...@@ -36,4 +36,32 @@ describe Admin::UsersController do
expect(user.access_locked?).to be_falsey expect(user.access_locked?).to be_falsey
end end
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 end
...@@ -9,34 +9,58 @@ describe AutocompleteController do ...@@ -9,34 +9,58 @@ describe AutocompleteController do
before do before do
sign_in(user) sign_in(user)
project.team << [user, :master] project.team << [user, :master]
get(:users, project_id: project.id)
end end
let(:body) { JSON.parse(response.body) } let(:body) { JSON.parse(response.body) }
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).to be_kind_of(Array) }
it { expect(body.size).to eq 1 } it { expect(body.size).to eq 1 }
it { expect(body.first["username"]).to eq user.username } it { expect(body.first["username"]).to eq user.username }
end 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 context 'group members' do
let(:group) { create(:group) } let(:group) { create(:group) }
before do before do
sign_in(user) sign_in(user)
group.add_owner(user) group.add_owner(user)
get(:users, group_id: group.id)
end end
let(:body) { JSON.parse(response.body) } let(:body) { JSON.parse(response.body) }
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).to be_kind_of(Array) }
it { expect(body.size).to eq 1 } it { expect(body.size).to eq 1 }
it { expect(body.first["username"]).to eq user.username } it { expect(body.first["username"]).to eq user.username }
end 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 context 'all users' do
before do before do
sign_in(user) sign_in(user)
...@@ -48,4 +72,52 @@ describe AutocompleteController do ...@@ -48,4 +72,52 @@ describe AutocompleteController do
it { expect(body).to be_kind_of(Array) } it { expect(body).to be_kind_of(Array) }
it { expect(body.size).to eq User.count } it { expect(body.size).to eq User.count }
end 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 end
...@@ -55,4 +55,30 @@ describe Projects::BranchesController do ...@@ -55,4 +55,30 @@ describe Projects::BranchesController do
it { is_expected.to render_template('new') } it { is_expected.to render_template('new') }
end end
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 end
...@@ -106,18 +106,11 @@ describe Profiles::TwoFactorAuthsController do ...@@ -106,18 +106,11 @@ describe Profiles::TwoFactorAuthsController do
describe 'DELETE destroy' do describe 'DELETE destroy' do
let(:user) { create(:user, :two_factor) } let(:user) { create(:user, :two_factor) }
let!(:codes) { user.generate_otp_backup_codes! }
it 'clears all 2FA-related fields' do it 'disables two factor' do
expect(user).to be_two_factor_enabled expect(user).to receive(:disable_two_factor!)
expect(user.otp_backup_codes).not_to be_nil
expect(user.encrypted_otp_secret).not_to be_nil
delete :destroy 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 end
it 'redirects to profile_account_path' do it 'redirects to profile_account_path' do
......
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
...@@ -8,9 +8,6 @@ describe Projects::TreeController do ...@@ -8,9 +8,6 @@ describe Projects::TreeController do
sign_in(user) sign_in(user)
project.team << [user, :master] 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) controller.instance_variable_set(:@project, project)
end end
...@@ -44,6 +41,32 @@ describe Projects::TreeController do ...@@ -44,6 +41,32 @@ describe Projects::TreeController do
let(:id) { 'invalid-branch/encoding/' } let(:id) { 'invalid-branch/encoding/' }
it { is_expected.to respond_with(:not_found) } it { is_expected.to respond_with(:not_found) }
end 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 end
describe 'GET show with blob path' do describe 'GET show with blob path' do
......
...@@ -32,6 +32,7 @@ FactoryGirl.define do ...@@ -32,6 +32,7 @@ FactoryGirl.define do
before(:create) do |user| before(:create) do |user|
user.two_factor_enabled = true user.two_factor_enabled = true
user.otp_secret = User.generate_otp_secret(32) user.otp_secret = User.generate_otp_secret(32)
user.generate_otp_backup_codes!
end end
end end
......
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
require 'spec_helper' require 'spec_helper'
feature 'Group' do feature 'Group', feature: true do
describe 'description' do describe 'description' do
let(:group) { create(:group) } let(:group) { create(:group) }
let(:path) { group_path(group) } let(:path) { group_path(group) }
......
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
...@@ -92,22 +92,6 @@ describe 'Issues', feature: true do ...@@ -92,22 +92,6 @@ describe 'Issues', feature: true do
let(:issue) { @issue } 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 it 'should allow filtering by issues with no specified assignee' do
visit namespace_project_issues_path(project.namespace, project, assignee_id: IssuableFinder::NONE) visit namespace_project_issues_path(project.namespace, project, assignee_id: IssuableFinder::NONE)
......
require 'spec_helper' require 'spec_helper'
feature 'Login' do feature 'Login', feature: true do
describe 'with two-factor authentication' do describe 'with two-factor authentication' do
context 'with valid username/password' do context 'with valid username/password' do
let(:user) { create(:user, :two_factor) } let(:user) { create(:user, :two_factor) }
......
...@@ -32,7 +32,7 @@ require 'erb' ...@@ -32,7 +32,7 @@ require 'erb'
# #
# See the MarkdownFeature class for setup details. # See the MarkdownFeature class for setup details.
describe 'GitLab Markdown' do describe 'GitLab Markdown', feature: true do
include ActionView::Helpers::TagHelper include ActionView::Helpers::TagHelper
include ActionView::Helpers::UrlHelper include ActionView::Helpers::UrlHelper
include Capybara::Node::Matchers include Capybara::Node::Matchers
......
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
require 'spec_helper' require 'spec_helper'
describe 'Comments' do describe 'Comments', feature: true do
include RepoHelpers include RepoHelpers
describe 'On a merge request', js: true, feature: true do describe 'On a merge request', js: true, feature: true do
......
require 'spec_helper' require 'spec_helper'
feature 'Password reset' do feature 'Password reset', feature: true do
def forgot_password def forgot_password
click_on 'Forgot your password?' click_on 'Forgot your password?'
fill_in 'Email', with: user.email fill_in 'Email', with: user.email
......
...@@ -9,8 +9,7 @@ describe 'Profile account page', feature: true do ...@@ -9,8 +9,7 @@ describe 'Profile account page', feature: true do
describe 'when signup is enabled' do describe 'when signup is enabled' do
before do before do
allow_any_instance_of(ApplicationSetting). stub_application_setting(signup_enabled: true)
to receive(:signup_enabled?).and_return(true)
visit profile_account_path visit profile_account_path
end end
...@@ -24,8 +23,7 @@ describe 'Profile account page', feature: true do ...@@ -24,8 +23,7 @@ describe 'Profile account page', feature: true do
describe 'when signup is disabled' do describe 'when signup is disabled' do
before do before do
allow_any_instance_of(ApplicationSetting). stub_application_setting(signup_enabled: false)
to receive(:signup_enabled?).and_return(false)
visit profile_account_path visit profile_account_path
end end
......
require 'spec_helper' require 'spec_helper'
describe 'Profile > Preferences' do describe 'Profile > Preferences', feature: true do
let(:user) { create(:user) } let(:user) { create(:user) }
before do before do
......
require 'spec_helper' require 'spec_helper'
feature 'Project' do feature 'Project', feature: true do
describe 'description' do describe 'description' do
let(:project) { create(:project) } let(:project) { create(:project) }
let(:path) { namespace_project_path(project.namespace, project) } let(:path) { namespace_project_path(project.namespace, project) }
......
require 'spec_helper' require 'spec_helper'
feature 'Users' do feature 'Users', feature: true do
scenario 'GET /users/sign_in creates a new user account' do scenario 'GET /users/sign_in creates a new user account' do
visit new_user_session_path visit new_user_session_path
fill_in 'user_name', with: 'Name Surname' fill_in 'user_name', with: 'Name Surname'
......
...@@ -382,6 +382,11 @@ ...@@ -382,6 +382,11 @@
"fileName" : "screenshot.png", "fileName" : "screenshot.png",
"fileSize" : 0, "fileSize" : 0,
"mimetype" : "image/png" "mimetype" : "image/png"
}, {
"attachmentId" : "001",
"fileName" : "screenshot1.PNG",
"fileSize" : 0,
"mimetype" : "image/x-png"
} ] } ]
}, { }, {
"id" : 1, "id" : 1,
......
...@@ -137,7 +137,7 @@ describe GitlabMarkdownHelper do ...@@ -137,7 +137,7 @@ describe GitlabMarkdownHelper do
describe 'random_markdown_tip' do describe 'random_markdown_tip' do
it 'returns a random Markdown tip' do it 'returns a random Markdown tip' do
stub_const("#{described_class}::MARKDOWN_TIPS", ['Random tip']) 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 end
end end
...@@ -8,4 +8,48 @@ describe ProjectsHelper do ...@@ -8,4 +8,48 @@ describe ProjectsHelper do
expect(project_status_css_class("finished")).to eq("success") expect(project_status_css_class("finished")).to eq("success")
end end
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 end
...@@ -72,4 +72,43 @@ describe VisibilityLevelHelper do ...@@ -72,4 +72,43 @@ describe VisibilityLevelHelper do
end end
end 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 end
...@@ -2,7 +2,9 @@ ...@@ -2,7 +2,9 @@
.file-content .file-content
.line-numbers .line-numbers
- 1.upto(25) do |i| - 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 %pre.code.highlight
%code %code
- 1.upto(25) do |i| - 1.upto(25) do |i|
......
...@@ -48,6 +48,14 @@ describe 'LineHighlighter', -> ...@@ -48,6 +48,14 @@ describe 'LineHighlighter', ->
clickLine(13) clickLine(13)
expect(spy).toHaveBeenPrevented() 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', -> describe 'without shiftKey', ->
it 'highlights one line when clicked', -> it 'highlights one line when clicked', ->
clickLine(13) clickLine(13)
......
...@@ -65,6 +65,7 @@ describe Gitlab::GoogleCodeImport::Importer do ...@@ -65,6 +65,7 @@ describe Gitlab::GoogleCodeImport::Importer do
expect(issue.description).to include('all the best!') 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('[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('![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 end
it "imports issue comments" do it "imports issue comments" do
......
...@@ -28,4 +28,53 @@ describe Issue, "Mentionable" do ...@@ -28,4 +28,53 @@ describe Issue, "Mentionable" do
issue.create_cross_references!(project, author, [commit2]) issue.create_cross_references!(project, author, [commit2])
end end
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 end
...@@ -63,7 +63,7 @@ describe Key do ...@@ -63,7 +63,7 @@ describe Key do
key = build(:key) key = build(:key)
# Not always the middle, but close enough # 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 expect(key).not_to be_valid
end end
...@@ -71,6 +71,12 @@ describe Key do ...@@ -71,6 +71,12 @@ describe Key do
it 'rejects the unfingerprintable key (not a key)' do it 'rejects the unfingerprintable key (not a key)' do
expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid
end end
it 'rejects the multiple line key' do
key = build(:key)
key.key.gsub!(' ', "\n")
expect(key).not_to be_valid
end
end end
context 'callbacks' do context 'callbacks' do
......
...@@ -26,6 +26,33 @@ describe GitlabCiService do ...@@ -26,6 +26,33 @@ describe GitlabCiService do
it { is_expected.to have_one(:service_hook) } it { is_expected.to have_one(:service_hook) }
end 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 describe 'commits methods' do
before do before do
@service = GitlabCiService.new @service = GitlabCiService.new
......
...@@ -47,4 +47,28 @@ describe Repository do ...@@ -47,4 +47,28 @@ describe Repository do
it { is_expected.to be_falsey } it { is_expected.to be_falsey }
end end
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 end
...@@ -217,6 +217,24 @@ describe User do ...@@ -217,6 +217,24 @@ describe User do
end end
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 describe 'projects' do
before do before do
@user = create :user @user = create :user
......
...@@ -14,10 +14,13 @@ describe API::API, api: true do ...@@ -14,10 +14,13 @@ describe API::API, api: true do
describe "GET /projects/:id/repository/branches" do describe "GET /projects/:id/repository/branches" do
it "should return an array of project branches" do it "should return an array of project branches" do
project.repository.expire_cache
get api("/projects/#{project.id}/repository/branches", user) get api("/projects/#{project.id}/repository/branches", user)
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response).to be_an Array 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
end end
......
...@@ -89,7 +89,7 @@ describe API::API, api: true do ...@@ -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 it 'returns projects in the correct order when ci_enabled_first parameter is passed' do
[project, project2, project3].each{ |project| project.build_missing_services } [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' } get api('/projects', user), { ci_enabled_first: 'true' }
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(json_response).to be_an Array expect(json_response).to be_an Array
...@@ -220,9 +220,7 @@ describe API::API, api: true do ...@@ -220,9 +220,7 @@ describe API::API, api: true do
context 'when a visibility level is restricted' do context 'when a visibility level is restricted' do
before do before do
@project = attributes_for(:project, { public: true }) @project = attributes_for(:project, { public: true })
allow_any_instance_of(ApplicationSetting).to( stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
receive(:restricted_visibility_levels).and_return([20])
)
end end
it 'should not allow a non-admin to use a restricted visibility level' do it 'should not allow a non-admin to use a restricted visibility level' do
......
...@@ -7,7 +7,7 @@ describe API::API, api: true do ...@@ -7,7 +7,7 @@ describe API::API, api: true do
describe "POST /projects/:id/services/gitlab-ci" do describe "POST /projects/:id/services/gitlab-ci" do
it "should update gitlab-ci settings" 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) expect(response.status).to eq(200)
end end
...@@ -17,6 +17,18 @@ describe API::API, api: true do ...@@ -17,6 +17,18 @@ describe API::API, api: true do
expect(response.status).to eq(400) expect(response.status).to eq(400)
end 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 end
describe "DELETE /projects/:id/services/gitlab-ci" do describe "DELETE /projects/:id/services/gitlab-ci" do
......
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
...@@ -14,11 +14,7 @@ describe CreateSnippetService do ...@@ -14,11 +14,7 @@ describe CreateSnippetService do
context 'When public visibility is restricted' do context 'When public visibility is restricted' do
before do before do
allow_any_instance_of(ApplicationSetting).to( stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
receive(:restricted_visibility_levels).and_return(
[Gitlab::VisibilityLevel::PUBLIC]
)
)
@opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
end end
......
...@@ -124,9 +124,7 @@ describe GitPushService do ...@@ -124,9 +124,7 @@ describe GitPushService do
end end
it "when pushing a branch for the first time with default branch protection disabled" do it "when pushing a branch for the first time with default branch protection disabled" do
allow(ApplicationSetting.current_application_settings). stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
to receive(:default_branch_protection).
and_return(Gitlab::Access::PROTECTION_NONE)
expect(project).to receive(:execute_hooks) expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master") expect(project.default_branch).to eq("master")
...@@ -135,9 +133,7 @@ describe GitPushService do ...@@ -135,9 +133,7 @@ describe GitPushService do
end end
it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do
allow(ApplicationSetting.current_application_settings). stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
to receive(:default_branch_protection).
and_return(Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
expect(project).to receive(:execute_hooks) expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master") expect(project.default_branch).to eq("master")
......
...@@ -58,9 +58,7 @@ describe Projects::CreateService do ...@@ -58,9 +58,7 @@ describe Projects::CreateService do
context 'restricted visibility level' do context 'restricted visibility level' do
before do before do
allow_any_instance_of(ApplicationSetting).to( stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
receive(:restricted_visibility_levels).and_return([20])
)
@opts.merge!( @opts.merge!(
visibility_level: Gitlab::VisibilityLevel.options['Public'] visibility_level: Gitlab::VisibilityLevel.options['Public']
......
...@@ -8,7 +8,7 @@ describe Projects::TransferService do ...@@ -8,7 +8,7 @@ describe Projects::TransferService do
context 'namespace -> namespace' do context 'namespace -> namespace' do
before do before do
group.add_owner(user) group.add_owner(user)
@result = transfer_project(project, user, new_namespace_id: group.id) @result = transfer_project(project, user, group)
end end
it { expect(@result).to be_truthy } it { expect(@result).to be_truthy }
...@@ -17,7 +17,7 @@ describe Projects::TransferService do ...@@ -17,7 +17,7 @@ describe Projects::TransferService do
context 'namespace -> no namespace' do context 'namespace -> no namespace' do
before do before do
@result = transfer_project(project, user, new_namespace_id: nil) @result = transfer_project(project, user, nil)
end end
it { expect(@result).to eq false } it { expect(@result).to eq false }
...@@ -26,14 +26,14 @@ describe Projects::TransferService do ...@@ -26,14 +26,14 @@ describe Projects::TransferService do
context 'namespace -> not allowed namespace' do context 'namespace -> not allowed namespace' do
before do before do
@result = transfer_project(project, user, new_namespace_id: group.id) @result = transfer_project(project, user, group)
end end
it { expect(@result).to eq false } it { expect(@result).to eq false }
it { expect(project.namespace).to eq(user.namespace) } it { expect(project.namespace).to eq(user.namespace) }
end end
def transfer_project(project, user, params) def transfer_project(project, user, new_namespace)
Projects::TransferService.new(project, user, params).execute Projects::TransferService.new(project, user).execute(new_namespace)
end end
end end
...@@ -47,9 +47,7 @@ describe Projects::UpdateService do ...@@ -47,9 +47,7 @@ describe Projects::UpdateService do
context 'respect configured visibility restrictions setting' do context 'respect configured visibility restrictions setting' do
before(:each) do before(:each) do
allow_any_instance_of(ApplicationSetting).to( stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
receive(:restricted_visibility_levels).and_return([20])
)
end end
context 'should be private when updated to private' do context 'should be private when updated to private' do
......
...@@ -14,11 +14,7 @@ describe UpdateSnippetService do ...@@ -14,11 +14,7 @@ describe UpdateSnippetService do
context 'When public visibility is restricted' do context 'When public visibility is restricted' do
before do before do
allow_any_instance_of(ApplicationSetting).to( stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
receive(:restricted_visibility_levels).and_return(
[Gitlab::VisibilityLevel::PUBLIC]
)
)
@snippet = create_snippet(@project, @user, @opts) @snippet = create_snippet(@project, @user, @opts)
@opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
......
if ENV['SIMPLECOV']
require 'simplecov'
SimpleCov.start :rails
end
if ENV['COVERALLS']
require 'coveralls'
Coveralls.wear_merged!
end
ENV["RAILS_ENV"] ||= 'test' ENV["RAILS_ENV"] ||= 'test'
require File.expand_path("../../config/environment", __FILE__) require File.expand_path("../../config/environment", __FILE__)
require 'rspec/rails' require 'rspec/rails'
require 'shoulda/matchers' require 'shoulda/matchers'
......
if ENV['SIMPLECOV']
require 'simplecov'
end
if ENV['COVERALLS']
require 'coveralls'
Coveralls.wear_merged!
end
...@@ -143,6 +143,6 @@ shared_examples 'an editable mentionable' do ...@@ -143,6 +143,6 @@ shared_examples 'an editable mentionable' do
end end
set_mentionable_text.call(new_text) set_mentionable_text.call(new_text)
subject.notice_added_references(project, author) subject.create_new_cross_references!(project, author)
end end
end end
module StubConfiguration module StubConfiguration
def stub_application_setting(messages) 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). allow(Gitlab::CurrentSettings.current_application_settings).
to receive_messages(messages) to receive_messages(messages)
end end
...@@ -11,4 +16,25 @@ module StubConfiguration ...@@ -11,4 +16,25 @@ module StubConfiguration
def stub_gravatar_setting(messages) def stub_gravatar_setting(messages)
allow(Gitlab.config.gravatar).to receive_messages(messages) allow(Gitlab.config.gravatar).to receive_messages(messages)
end 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 end
...@@ -5,6 +5,7 @@ module TestEnv ...@@ -5,6 +5,7 @@ module TestEnv
# When developing the seed repository, comment out the branch you will modify. # When developing the seed repository, comment out the branch you will modify.
BRANCH_SHA = { BRANCH_SHA = {
'empty-branch' => '7efb185',
'flatten-dir' => 'e56497b', 'flatten-dir' => 'e56497b',
'feature' => '0b4bc9a', 'feature' => '0b4bc9a',
'feature_conflict' => 'bb5206f', 'feature_conflict' => 'bb5206f',
...@@ -14,9 +15,13 @@ module TestEnv ...@@ -14,9 +15,13 @@ module TestEnv
'master' => '5937ac0' 'master' => '5937ac0'
} }
FORKED_BRANCH_SHA = BRANCH_SHA.merge({ # gitlab-test-fork is a fork of gitlab-fork, but we don't necessarily
'add-submodule-version-bump' => '3f547c08' # 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 # Test environment
# #
......
/* 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)});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment