Commit e75300df authored by Valery Sizov's avatar Valery Sizov

Merge branch 'ce-upstream' into 'master'

CE upstream

Unresolved conflicts
```
both modified:   app/controllers/omniauth_callbacks_controller.rb
both modified:   app/views/shared/issuable/_sidebar.html.haml

```

See merge request !166
parents 97bc167b 23caf7b6
...@@ -2,26 +2,44 @@ Please view this file on the master branch, on stable branches it's out of date. ...@@ -2,26 +2,44 @@ Please view this file on the master branch, on stable branches it's out of date.
v 8.5.0 (unreleased) v 8.5.0 (unreleased)
- Ensure rake tasks that don't need a DB connection can be run without one - Ensure rake tasks that don't need a DB connection can be run without one
- Update New Relic gem to 3.14.1.311 (Stan Hu)
- Add "visibility" flag to GET /projects api endpoint - Add "visibility" flag to GET /projects api endpoint
- Ignore binary files in code search to prevent Error 500 (Stan Hu) - Ignore binary files in code search to prevent Error 500 (Stan Hu)
- Render sanitized SVG images (Stan Hu) - Render sanitized SVG images (Stan Hu)
- Support download access by PRIVATE-TOKEN header (Stan Hu)
- Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push - Upgrade gitlab_git to 7.2.23 to fix commit message mentions in first branch push
- New UI for pagination - New UI for pagination
- Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet - Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet
set it up set it up
- Fix diff comments loaded by AJAX to load comment with diff in discussion tab - Fix diff comments loaded by AJAX to load comment with diff in discussion tab
- Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel) - Whitelist raw "abbr" elements when parsing Markdown (Benedict Etzel)
- Fix label links for a merge request pointing to issues list
- Don't vendor minified JS - Don't vendor minified JS
- Display 404 error on group not found - Display 404 error on group not found
- Track project import failure - Track project import failure
- Support Two-factor Authentication for LDAP users
- Display database type and version in Administration dashboard
- Fix visibility level text in admin area (Zeger-Jan van de Weg) - Fix visibility level text in admin area (Zeger-Jan van de Weg)
- Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg) - Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg)
- Update the ExternalIssue regex pattern (Blake Hitchcock) - Update the ExternalIssue regex pattern (Blake Hitchcock)
- Remember user's inline/side-by-side diff view preference in a cookie (Kirill Katsnelson)
- Optimized performance of finding issues to be closed by a merge request - Optimized performance of finding issues to be closed by a merge request
- Revert "Add IP check against DNSBLs at account sign-up" - Revert "Add IP check against DNSBLs at account sign-up"
- Fix API to keep request parameters in Link header (Michael Potthoff)
- Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead - Deprecate API "merge_request/:merge_request_id/comments". Use "merge_requests/:merge_request_id/notes" instead
- Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead - Deprecate API "merge_request/:merge_request_id/...". Use "merge_requests/:merge_request_id/..." instead
- Prevent parse error when name of project ends with .atom and prevent path issues
- Mark inline difference between old and new paths when a file is renamed - Mark inline difference between old and new paths when a file is renamed
- Support Akismet spam checking for creation of issues via API (Stan Hu)
- Improve UI consistency between projects and groups lists
- Add sort dropdown to dashboard projects page
- Fixed logo animation on Safari (Roman Rott)
- Hide remove source branch button when the MR is merged but new commits are pushed (Zeger-Jan van de Weg)
- In seach autocomplete show only groups and projects you are member of
- Fix: init.d script not working on OS X
v 8.4.4
- Update omniauth-saml gem to 1.4.2
v 8.4.3 v 8.4.3
- Increase lfs_objects size column to 8-byte integer to allow files larger - Increase lfs_objects size column to 8-byte integer to allow files larger
...@@ -30,6 +48,9 @@ v 8.4.3 ...@@ -30,6 +48,9 @@ v 8.4.3
- Fix highlighting in blame view - Fix highlighting in blame view
- Update sentry-raven gem to prevent "Not a git repository" console output - Update sentry-raven gem to prevent "Not a git repository" console output
when running certain commands when running certain commands
- Add instrumentation to additional Gitlab::Git and Rugged methods for
performance monitoring
- Allow autosize textareas to also be manually resized
v 8.4.2 v 8.4.2
- Bump required gitlab-workhorse version to bring in a fix for missing - Bump required gitlab-workhorse version to bring in a fix for missing
...@@ -177,6 +198,7 @@ v 8.3.0 ...@@ -177,6 +198,7 @@ v 8.3.0
- Handle and report SSL errors in Web hook test (Stan Hu) - Handle and report SSL errors in Web hook test (Stan Hu)
- Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu) - Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu)
- Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera) - Fix: Assignee selector is empty when 'Unassigned' is selected (Jose Corcuera)
- WIP identifier on merge requests no longer requires trailing space
- Add rake tasks for git repository maintainance (Zeger-Jan van de Weg) - Add rake tasks for git repository maintainance (Zeger-Jan van de Weg)
- Fix 500 error when update group member permission - Fix 500 error when update group member permission
- Fix: As an admin, cannot add oneself as a member to a group/project - Fix: As an admin, cannot add oneself as a member to a group/project
......
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
- [Contribute to GitLab](#contribute-to-gitlab)
- [Contributor license agreement](#contributor-license-agreement)
- [Security vulnerability disclosure](#security-vulnerability-disclosure)
- [Closing policy for issues and merge requests](#closing-policy-for-issues-and-merge-requests)
- [Helping others](#helping-others)
- [I want to contribute!](#i-want-to-contribute)
- [Issue tracker](#issue-tracker)
- [Feature proposals](#feature-proposals)
- [Issue tracker guidelines](#issue-tracker-guidelines)
- [Issue weight](#issue-weight)
- [Regression issues](#regression-issues)
- [Merge requests](#merge-requests)
- [Merge request guidelines](#merge-request-guidelines)
- [Merge request description format](#merge-request-description-format)
- [Contribution acceptance criteria](#contribution-acceptance-criteria)
- [Changes for Stable Releases](#changes-for-stable-releases)
- [Definition of done](#definition-of-done)
- [Style guides](#style-guides)
- [Code of conduct](#code-of-conduct)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
# Contribute to GitLab # Contribute to GitLab
Thank you for your interest in contributing to GitLab. This guide details how Thank you for your interest in contributing to GitLab. This guide details how
...@@ -177,6 +203,26 @@ is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9. ...@@ -177,6 +203,26 @@ is probably 1, adding a new Git Hook maybe 4 or 5, big features 7-9.
issues or chunks. You can simply not set the weight of a parent issue and set issues or chunks. You can simply not set the weight of a parent issue and set
weights to children issues. weights to children issues.
### Regression issues
Every monthly release has a corresponding issue on the CE issue tracker to keep
track of functionality broken by that release and any fixes that need to be
included in a patch release (see [8.3 Regressions] as an example).
As outlined in the issue description, the intended workflow is to post one note
with a reference to an issue describing the regression, and then to update that
note with a reference to the merge request that fixes it as it becomes available.
If you're a contributor who doesn't have the required permissions to update
other users' notes, please post a new note with a reference to both the issue
and the merge request.
The release manager will [update the notes] in the regression issue as fixes are
addressed.
[8.3 Regressions]: https://gitlab.com/gitlab-org/gitlab-ce/issues/4127
[update the notes]: https://gitlab.com/gitlab-org/release-tools/blob/master/doc/pro-tips.md#update-the-regression-issue
## Merge requests ## Merge requests
We welcome merge requests with fixes and improvements to GitLab code, tests, We welcome merge requests with fixes and improvements to GitLab code, tests,
...@@ -214,15 +260,17 @@ request is as follows: ...@@ -214,15 +260,17 @@ request is as follows:
1. Add your changes to the [CHANGELOG](CHANGELOG) 1. Add your changes to the [CHANGELOG](CHANGELOG)
1. If you are changing the README, some documentation or other things which 1. If you are changing the README, some documentation or other things which
have no effect on the tests, add `[ci skip]` somewhere in the commit message have no effect on the tests, add `[ci skip]` somewhere in the commit message
and make sure to read the [documentation styleguide][doc-styleguide]
1. If you have multiple commits please combine them into one commit by 1. If you have multiple commits please combine them into one commit by
[squashing them][git-squash] [squashing them][git-squash]
1. Push the commit(s) to your fork 1. Push the commit(s) to your fork
1. Submit a merge request (MR) to the master branch 1. Submit a merge request (MR) to the master branch
1. The MR title should describe the change you want to make 1. The MR title should describe the change you want to make
1. The MR description should give a motive for your change and the method you 1. The MR description should give a motive for your change and the method you
used to achieve it used to achieve it, see the [merge request description format]
(#merge-request-description-format)
1. If the MR changes the UI it should include before and after screenshots 1. If the MR changes the UI it should include before and after screenshots
1. If the MR changes CSS classes please include the list of affected pages 1. If the MR changes CSS classes please include the list of affected pages,
`grep css-class ./app -R` `grep css-class ./app -R`
1. Link any relevant [issues][ce-tracker] in the merge request description and 1. Link any relevant [issues][ce-tracker] in the merge request description and
leave a comment on them with a link back to the MR leave a comment on them with a link back to the MR
...@@ -255,49 +303,30 @@ For examples of feedback on merge requests please look at already ...@@ -255,49 +303,30 @@ For examples of feedback on merge requests please look at already
request feel free to mention one of the Merge Marshalls of the [core team][]. request feel free to mention one of the Merge Marshalls of the [core team][].
Please ensure that your merge request meets the contribution acceptance criteria. Please ensure that your merge request meets the contribution acceptance criteria.
When having your code reviewed and when reviewing merge requests please take the [thoughtbot code review guidelines](https://github.com/thoughtbot/guides/tree/master/code-review) into account. When having your code reviewed and when reviewing merge requests please take the
[thoughtbot code review guidelines](https://github.com/thoughtbot/guides/tree/master/code-review)
into account.
## Definition of done ### Merge request description format
If you contribute to GitLab please know that changes involve more than just Please submit merge requests using the following template in the merge request
code. We have the following [definition of done][]. Please ensure you support description area. Copy-paste it to retain the markdown format.
the feature you contribute through all of these steps.
1. Description explaining the relevancy (see following item) ```
1. Working and clean code that is commented where needed ## What does this MR do?
1. Unit and integration tests that pass on the CI server
1. Documented in the /doc directory
1. Changelog entry added
1. Reviewed and any concerns are addressed
1. Merged by the project lead
1. Added to the release blog article
1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/) if relevant
1. Community questions answered
1. Answers to questions radiated (in docs/wiki/etc.)
If you add a dependency in GitLab (such as an operating system package) please ## Are there points in the code the reviewer needs to double check?
consider updating the following and note the applicability of each in your
merge request:
1. Note the addition in the release blog post (create one if it doesn't exist yet) https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/ ## Why was this MR needed?
1. Upgrade guide, for example https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md
1. Upgrader https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md#2-run-gitlab-upgrade-tool
1. Installation guide https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies
1. GitLab Development Kit https://gitlab.com/gitlab-org/gitlab-development-kit
1. Test suite https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh
1. Omnibus package creator https://gitlab.com/gitlab-org/omnibus-gitlab
## Merge request description format ## What are the relevant issue numbers?
1. What does this MR do? ## Screenshots (if relevant)
1. Are there points in the code the reviewer needs to double check? ```
1. Why was this MR needed?
1. What are the relevant issue numbers?
1. Screenshots (if relevant)
## Contribution acceptance criteria ### Contribution acceptance criteria
1. The change is as small as possible (see the above paragraph for details) 1. The change is as small as possible
1. Include proper tests and make all tests pass (unless it contains a test 1. Include proper tests and make all tests pass (unless it contains a test
exposing a bug in existing code) exposing a bug in existing code)
1. If you suspect a failing CI build is unrelated to your contribution, you may 1. If you suspect a failing CI build is unrelated to your contribution, you may
...@@ -310,20 +339,62 @@ merge request: ...@@ -310,20 +339,62 @@ merge request:
1. Does not break any existing functionality 1. Does not break any existing functionality
1. Fixes one specific issue or implements one specific feature (do not combine 1. Fixes one specific issue or implements one specific feature (do not combine
things, send separate merge requests if needed) things, send separate merge requests if needed)
1. Migrations should do only one thing (eg: either create a table, move data to 1. Migrations should do only one thing (e.g., either create a table, move data
a new table or remove an old table) to aid retrying on failure to a new table or remove an old table) to aid retrying on failure
1. Keeps the GitLab code base clean and well structured 1. Keeps the GitLab code base clean and well structured
1. Contains functionality we think other users will benefit from too 1. Contains functionality we think other users will benefit from too
1. Doesn't add configuration options since they complicate future changes 1. Doesn't add configuration options since they complicate future changes
1. Changes after submitting the merge request should be in separate commits 1. Changes after submitting the merge request should be in separate commits
(no squashing). If necessary, you will be asked to squash when the review is (no squashing). If necessary, you will be asked to squash when the review is
over, before merging. over, before merging.
1. It conforms to the following style guides: 1. It conforms to the [style guides](#style-guides) and the following:
* If your change touches a line that does not follow the style, modify the - If your change touches a line that does not follow the style, modify the
entire line to follow it. This prevents linting tools from generating warnings. entire line to follow it. This prevents linting tools from generating warnings.
* Don't touch neighbouring lines. As an exception, automatic mass - Don't touch neighbouring lines. As an exception, automatic mass
refactoring modifications may leave style non-compliant. refactoring modifications may leave style non-compliant.
## Changes for Stable Releases
Sometimes certain changes have to be added to an existing stable release.
Two examples are bug fixes and performance improvements. In these cases the
corresponding merge request should be updated to have the following:
1. A milestone indicating what release the merge request should be merged into.
1. The label "Pick into Stable"
This makes it easier for release managers to keep track of what still has to be
merged and where changes have to be merged into.
## Definition of done
If you contribute to GitLab please know that changes involve more than just
code. We have the following [definition of done][]. Please ensure you support
the feature you contribute through all of these steps.
1. Description explaining the relevancy (see following item)
1. Working and clean code that is commented where needed
1. Unit and integration tests that pass on the CI server
1. [Documented][doc-styleguide] in the /doc directory
1. Changelog entry added
1. Reviewed and any concerns are addressed
1. Merged by the project lead
1. Added to the release blog article
1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/) if relevant
1. Community questions answered
1. Answers to questions radiated (in docs/wiki/etc.)
If you add a dependency in GitLab (such as an operating system package) please
consider updating the following and note the applicability of each in your
merge request:
1. Note the addition in the release blog post (create one if it doesn't exist yet) https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/
1. Upgrade guide, for example https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md
1. Upgrader https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md#2-run-gitlab-upgrade-tool
1. Installation guide https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies
1. GitLab Development Kit https://gitlab.com/gitlab-org/gitlab-development-kit
1. Test suite https://gitlab.com/gitlab-org/gitlab-ce/blob/master/scripts/prepare_build.sh
1. Omnibus package creator https://gitlab.com/gitlab-org/omnibus-gitlab
## Style guides ## Style guides
1. [Ruby](https://github.com/bbatsov/ruby-style-guide). 1. [Ruby](https://github.com/bbatsov/ruby-style-guide).
...@@ -338,7 +409,7 @@ merge request: ...@@ -338,7 +409,7 @@ merge request:
contributors to enhance security contributors to enhance security
1. [Database Migrations](doc/development/migration_style_guide.md) 1. [Database Migrations](doc/development/migration_style_guide.md)
1. [Markdown](http://www.cirosantilli.com/markdown-styleguide) 1. [Markdown](http://www.cirosantilli.com/markdown-styleguide)
1. [Documentation styleguide](doc/development/doc_styleguide.md) 1. [Documentation styleguide][doc-styleguide]
1. Interface text should be written subjectively instead of objectively. It 1. Interface text should be written subjectively instead of objectively. It
should be the GitLab core team addressing a person. It should be written in should be the GitLab core team addressing a person. It should be written in
present time and never use past tense (has been/was). For example instead present time and never use past tense (has been/was). For example instead
...@@ -379,7 +450,7 @@ reported by emailing `contact@gitlab.com`. ...@@ -379,7 +450,7 @@ reported by emailing `contact@gitlab.com`.
This Code of Conduct is adapted from the [Contributor Covenant][], version 1.1.0, This Code of Conduct is adapted from the [Contributor Covenant][], version 1.1.0,
available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/). available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/).
[core team]: https://about.gitlab.com/core-team/ [core-team]: https://about.gitlab.com/core-team/
[getting help page]: https://about.gitlab.com/getting-help/ [getting help page]: https://about.gitlab.com/getting-help/
[Codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq [Codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs [up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs
...@@ -400,3 +471,4 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor ...@@ -400,3 +471,4 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
[Contributor Covenant]: http://contributor-covenant.org [Contributor Covenant]: http://contributor-covenant.org
[rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout [rss-source]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#source-code-layout
[rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming [rss-naming]: https://github.com/bbatsov/ruby-style-guide/blob/master/README.md#naming
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
...@@ -30,15 +30,16 @@ gem 'omniauth-github', '~> 1.1.1' ...@@ -30,15 +30,16 @@ gem 'omniauth-github', '~> 1.1.1'
gem 'omniauth-gitlab', '~> 1.0.0' gem 'omniauth-gitlab', '~> 1.0.0'
gem 'omniauth-google-oauth2', '~> 0.2.0' gem 'omniauth-google-oauth2', '~> 0.2.0'
gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos gem 'omniauth-kerberos', '~> 0.3.0', group: :kerberos
gem 'omniauth-saml', '~> 1.4.0' gem 'omniauth-saml', '~> 1.4.2'
gem 'omniauth-shibboleth', '~> 1.2.0' gem 'omniauth-shibboleth', '~> 1.2.0'
gem 'omniauth-twitter', '~> 1.2.0' gem 'omniauth-twitter', '~> 1.2.0'
gem 'omniauth_crowd', '~> 2.2.0' gem 'omniauth_crowd', '~> 2.2.0'
gem 'gssapi', group: :kerberos gem 'gssapi', group: :kerberos
gem 'rack-oauth2', '~> 1.2.1' gem 'rack-oauth2', '~> 1.2.1'
# reCAPTCHA protection # Spam and anti-bot protection
gem 'recaptcha', require: 'recaptcha/rails' gem 'recaptcha', require: 'recaptcha/rails'
gem 'akismet', '~> 2.0'
# Two-factor authentication # Two-factor authentication
gem 'devise-two-factor', '~> 2.0.0' gem 'devise-two-factor', '~> 2.0.0'
...@@ -50,7 +51,7 @@ gem "browser", '~> 1.0.0' ...@@ -50,7 +51,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '~> 7.2.23' gem "gitlab_git", '~> 8.1'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
...@@ -309,9 +310,6 @@ group :production do ...@@ -309,9 +310,6 @@ group :production do
gem "gitlab_meta", '7.0' gem "gitlab_meta", '7.0'
end end
gem "newrelic_rpm", '~> 3.9.4.245'
gem 'newrelic-grape'
gem 'octokit', '~> 3.8.0' gem 'octokit', '~> 3.8.0'
gem "mail_room", "~> 0.6.1" gem "mail_room", "~> 0.6.1"
......
...@@ -49,6 +49,7 @@ GEM ...@@ -49,6 +49,7 @@ GEM
addressable (2.3.8) addressable (2.3.8)
after_commit_queue (1.3.0) after_commit_queue (1.3.0)
activerecord (>= 3.0) activerecord (>= 3.0)
akismet (2.0.0)
allocations (1.0.3) allocations (1.0.3)
annotate (2.6.10) annotate (2.6.10)
activerecord (>= 3.2, <= 4.3) activerecord (>= 3.2, <= 4.3)
...@@ -378,7 +379,7 @@ GEM ...@@ -378,7 +379,7 @@ GEM
gitlab-license (0.0.4) gitlab-license (0.0.4)
gitlab_emoji (0.2.0) gitlab_emoji (0.2.0)
gemojione (~> 2.1) gemojione (~> 2.1)
gitlab_git (7.2.24) gitlab_git (8.1.0)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
...@@ -502,10 +503,6 @@ GEM ...@@ -502,10 +503,6 @@ GEM
net-ldap (0.12.1) net-ldap (0.12.1)
net-ssh (3.0.1) net-ssh (3.0.1)
netrc (0.11.0) netrc (0.11.0)
newrelic-grape (2.1.0)
grape
newrelic_rpm
newrelic_rpm (3.9.4.245)
nokogiri (1.6.7.2) nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2) mini_portile2 (~> 2.0.0.rc2)
nprogress-rails (0.1.6.7) nprogress-rails (0.1.6.7)
...@@ -558,9 +555,9 @@ GEM ...@@ -558,9 +555,9 @@ GEM
omniauth-oauth2 (1.3.1) omniauth-oauth2 (1.3.1)
oauth2 (~> 1.0) oauth2 (~> 1.0)
omniauth (~> 1.2) omniauth (~> 1.2)
omniauth-saml (1.4.1) omniauth-saml (1.4.2)
omniauth (~> 1.1) omniauth (~> 1.1)
ruby-saml (~> 1.0.0) ruby-saml (~> 1.1, >= 1.1.1)
omniauth-shibboleth (1.2.1) omniauth-shibboleth (1.2.1)
omniauth (>= 1.0.0) omniauth (>= 1.0.0)
omniauth-twitter (1.2.1) omniauth-twitter (1.2.1)
...@@ -716,7 +713,7 @@ GEM ...@@ -716,7 +713,7 @@ GEM
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
ruby-progressbar (1.7.5) ruby-progressbar (1.7.5)
ruby-saml (1.0.0) ruby-saml (1.1.1)
nokogiri (>= 1.5.10) nokogiri (>= 1.5.10)
uuid (~> 2.3) uuid (~> 2.3)
ruby2ruby (2.2.0) ruby2ruby (2.2.0)
...@@ -908,6 +905,7 @@ DEPENDENCIES ...@@ -908,6 +905,7 @@ DEPENDENCIES
acts-as-taggable-on (~> 3.4) acts-as-taggable-on (~> 3.4)
addressable (~> 2.3.8) addressable (~> 2.3.8)
after_commit_queue after_commit_queue
akismet (~> 2.0)
allocations (~> 1.0) allocations (~> 1.0)
annotate (~> 2.6.0) annotate (~> 2.6.0)
asana (~> 0.4.0) asana (~> 0.4.0)
...@@ -962,7 +960,7 @@ DEPENDENCIES ...@@ -962,7 +960,7 @@ DEPENDENCIES
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab-license (~> 0.0.4) gitlab-license (~> 0.0.4)
gitlab_emoji (~> 0.2.0) gitlab_emoji (~> 0.2.0)
gitlab_git (~> 7.2.23) gitlab_git (~> 8.1)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0) gollum-lib (~> 4.1.0)
...@@ -991,8 +989,6 @@ DEPENDENCIES ...@@ -991,8 +989,6 @@ DEPENDENCIES
nested_form (~> 0.3.2) nested_form (~> 0.3.2)
net-ldap net-ldap
net-ssh (~> 3.0.1) net-ssh (~> 3.0.1)
newrelic-grape
newrelic_rpm (~> 3.9.4.245)
nokogiri (= 1.6.7.2) nokogiri (= 1.6.7.2)
nprogress-rails (~> 0.1.6.7) nprogress-rails (~> 0.1.6.7)
oauth2 (~> 1.0.0) oauth2 (~> 1.0.0)
...@@ -1006,7 +1002,7 @@ DEPENDENCIES ...@@ -1006,7 +1002,7 @@ DEPENDENCIES
omniauth-gitlab (~> 1.0.0) omniauth-gitlab (~> 1.0.0)
omniauth-google-oauth2 (~> 0.2.0) omniauth-google-oauth2 (~> 0.2.0)
omniauth-kerberos (~> 0.3.0) omniauth-kerberos (~> 0.3.0)
omniauth-saml (~> 1.4.0) omniauth-saml (~> 1.4.2)
omniauth-shibboleth (~> 1.2.0) omniauth-shibboleth (~> 1.2.0)
omniauth-twitter (~> 1.2.0) omniauth-twitter (~> 1.2.0)
omniauth_crowd (~> 2.2.0) omniauth_crowd (~> 2.2.0)
......
...@@ -11,7 +11,7 @@ class @AdminEmailSelect ...@@ -11,7 +11,7 @@ class @AdminEmailSelect
group_result = Api.groups query.term, skip_ldap, (groups) -> group_result = Api.groups query.term, skip_ldap, (groups) ->
groups groups
project_result = Api.projects query.term, (projects) -> project_result = Api.projects query.term, 'id', (projects) ->
projects projects
$.when(project_result, group_result).done (projects, groups) -> $.when(project_result, group_result).done (projects, groups) ->
......
...@@ -49,7 +49,7 @@ ...@@ -49,7 +49,7 @@
callback(namespaces) callback(namespaces)
# Return projects list. Filtered by query # Return projects list. Filtered by query
projects: (query, callback) -> projects: (query, order, callback) ->
url = Api.buildUrl(Api.projects_path) url = Api.buildUrl(Api.projects_path)
$.ajax( $.ajax(
...@@ -57,6 +57,7 @@ ...@@ -57,6 +57,7 @@
data: data:
private_token: gon.api_token private_token: gon.api_token
search: query search: query
order_by: order
per_page: 20 per_page: 20
dataType: "json" dataType: "json"
).done (projects) -> ).done (projects) ->
...@@ -96,17 +97,3 @@ ...@@ -96,17 +97,3 @@
dataType: "json" dataType: "json"
).done (groups) -> ).done (groups) ->
callback(groups) callback(groups)
# Return projects list. Filtered by query
projects: (query, callback) ->
project_url = Api.buildUrl(Api.projects_path)
project_query = $.ajax(
url: project_url
data:
private_token: gon.api_token
search: query
per_page: 20
dataType: "json"
).done (projects) ->
callback(projects)
...@@ -215,8 +215,89 @@ $ -> ...@@ -215,8 +215,89 @@ $ ->
$this.attr 'value', $this.val() $this.attr 'value', $this.val()
return return
$(document).on 'keyup', 'input[type="search"]' , (e) -> $(document)
.off 'keyup', 'input[type="search"]'
.on 'keyup', 'input[type="search"]' , (e) ->
$this = $(this) $this = $(this)
$this.attr 'value', $this.val() $this.attr 'value', $this.val()
$(document)
.off 'breakpoint:change'
.on 'breakpoint:change', (e, breakpoint) ->
if breakpoint is 'sm' or breakpoint is 'xs'
$gutterIcon = $('.gutter-toggle').find('i')
if $gutterIcon.hasClass('fa-angle-double-right')
$gutterIcon.closest('a').trigger('click')
$(document)
.off 'click', 'aside .gutter-toggle'
.on 'click', 'aside .gutter-toggle', (e) ->
e.preventDefault()
$this = $(this)
$thisIcon = $this.find 'i'
if $thisIcon.hasClass('fa-angle-double-right')
$thisIcon
.removeClass('fa-angle-double-right')
.addClass('fa-angle-double-left')
$this
.closest('aside')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
$('.page-with-sidebar')
.removeClass('right-sidebar-expanded')
.addClass('right-sidebar-collapsed')
else
$thisIcon
.removeClass('fa-angle-double-left')
.addClass('fa-angle-double-right')
$this
.closest('aside')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
$('.page-with-sidebar')
.removeClass('right-sidebar-collapsed')
.addClass('right-sidebar-expanded')
$.cookie("collapsed_gutter",
$('.right-sidebar')
.hasClass('right-sidebar-collapsed'), { path: '/' })
bootstrapBreakpoint = undefined;
checkBootstrapBreakpoints = ->
if $('.device-xs').is(':visible')
bootstrapBreakpoint = "xs"
else if $('.device-sm').is(':visible')
bootstrapBreakpoint = "sm"
else if $('.device-md').is(':visible')
bootstrapBreakpoint = "md"
else if $('.device-lg').is(':visible')
bootstrapBreakpoint = "lg"
setBootstrapBreakpoints = ->
if $('.device-xs').length
return
$("body")
.append('<div class="device-xs visible-xs"></div>'+
'<div class="device-sm visible-sm"></div>'+
'<div class="device-md visible-md"></div>'+
'<div class="device-lg visible-lg"></div>')
checkBootstrapBreakpoints()
fitSidebarForSize = ->
oldBootstrapBreakpoint = bootstrapBreakpoint
checkBootstrapBreakpoints()
if bootstrapBreakpoint != oldBootstrapBreakpoint
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
checkInitialSidebarSize = ->
if bootstrapBreakpoint is "xs" or "sm"
$(document).trigger('breakpoint:change', [bootstrapBreakpoint])
$(window)
.off "resize"
.on "resize", (e) ->
fitSidebarForSize()
setBootstrapBreakpoints()
checkInitialSidebarSize()
new Aside() new Aside()
class @Dashboard @Dashboard =
constructor: -> init: ->
new ProjectsList() this.initSearch()
initSearch: ->
@timer = null
$("#project-filter-form-field").on('keyup', ->
clearTimeout(@timer)
@timer = setTimeout(Dashboard.filterResults, 500)
)
filterResults: =>
$('.projects-list-holder').fadeTo(250, 0.5)
form = null
form = $("#project-filter-form")
search = $("#project-filter-form-field").val()
project_filter_url = form.attr('action') + '?' + form.serialize()
$.ajax
type: "GET"
url: form.attr('action')
data: form.serialize()
complete: ->
$('.projects-list-holder').fadeTo(250, 1)
success: (data) ->
$('div.projects-list-holder').replaceWith(data.html)
# Change url so if user reload a page - search results are saved
history.replaceState {page: project_filter_url}, document.title, project_filter_url
dataType: "json"
...@@ -58,7 +58,7 @@ class Dispatcher ...@@ -58,7 +58,7 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
MergeRequests.init() MergeRequests.init()
when 'dashboard:show', 'root:show' when 'dashboard:show', 'root:show'
new Dashboard() Dashboard.init()
when 'dashboard:activity' when 'dashboard:activity'
new Activities() new Activities()
when 'dashboard:projects:starred' when 'dashboard:projects:starred'
......
...@@ -65,8 +65,7 @@ class @DropzoneInput ...@@ -65,8 +65,7 @@ class @DropzoneInput
return return
success: (header, response) -> success: (header, response) ->
child = $(dropzone[0]).children("textarea") pasteText response.link.markdown
$(child).val $(child).val() + response.link.markdown + "\n"
return return
error: (temp, errorMessage) -> error: (temp, errorMessage) ->
...@@ -128,6 +127,7 @@ class @DropzoneInput ...@@ -128,6 +127,7 @@ class @DropzoneInput
beforeSelection = $(child).val().substring 0, caretStart beforeSelection = $(child).val().substring 0, caretStart
afterSelection = $(child).val().substring caretEnd, textEnd afterSelection = $(child).val().substring caretEnd, textEnd
$(child).val beforeSelection + text + afterSelection $(child).val beforeSelection + text + afterSelection
child.get(0).setSelectionRange caretStart + text.length, caretEnd + text.length
form_textarea.trigger "input" form_textarea.trigger "input"
getFilename = (e) -> getFilename = (e) ->
......
...@@ -10,19 +10,7 @@ class @IssuableContext ...@@ -10,19 +10,7 @@ class @IssuableContext
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", -> $(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
$(this).submit() $(this).submit()
$('.issuable-details').waitForImages -> $(document).on "click",".edit-link", (e) ->
$('.issuable-affix').on 'affix.bs.affix', ->
$(@).width($(@).outerWidth())
.on 'affixed-top.bs.affix affixed-bottom.bs.affix', ->
$(@).width('')
$('.issuable-affix').affix offset:
top: ->
@top = ($('.issuable-affix').offset().top - 70)
bottom: ->
@bottom = $('.footer').outerHeight(true)
$(".edit-link").click (e) ->
block = $(@).parents('.block') block = $(@).parents('.block')
block.find('.selectbox').show() block.find('.selectbox').show()
block.find('.value').hide() block.find('.value').hide()
......
...@@ -42,3 +42,8 @@ work = -> ...@@ -42,3 +42,8 @@ work = ->
$(document).on('page:fetch', start) $(document).on('page:fetch', start)
$(document).on('page:change', stop) $(document).on('page:change', stop)
$ ->
# Make logo clickable
$('#logo').on 'click', ->
$('#js-shortcuts-home').get(0).click()
...@@ -50,3 +50,19 @@ class @Project ...@@ -50,3 +50,19 @@ class @Project
$('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>") $('#notifications-button').empty().append("<i class='fa fa-bell'></i>" + label + "<i class='fa fa-angle-down'></i>")
$(@).parents('ul').find('li.active').removeClass 'active' $(@).parents('ul').find('li.active').removeClass 'active'
$(@).parent().addClass 'active' $(@).parent().addClass 'active'
@projectSelectDropdown()
projectSelectDropdown: ->
new ProjectSelect()
$('.project-item-select').on 'click', (e) =>
@changeProject $(e.currentTarget).val()
$('.js-projects-dropdown-toggle').on 'click', (e) ->
e.preventDefault()
$('.js-projects-dropdown').select2('open')
changeProject: (url) ->
window.location = url
...@@ -3,6 +3,7 @@ class @ProjectSelect ...@@ -3,6 +3,7 @@ class @ProjectSelect
$('.ajax-project-select').each (i, select) -> $('.ajax-project-select').each (i, select) ->
@groupId = $(select).data('group-id') @groupId = $(select).data('group-id')
@includeGroups = $(select).data('include-groups') @includeGroups = $(select).data('include-groups')
@orderBy = $(select).data('order-by') || 'id'
placeholder = "Search for project" placeholder = "Search for project"
placeholder += " or group" if @includeGroups placeholder += " or group" if @includeGroups
...@@ -28,7 +29,7 @@ class @ProjectSelect ...@@ -28,7 +29,7 @@ class @ProjectSelect
if @groupId if @groupId
Api.groupProjects @groupId, query.term, projectsCallback Api.groupProjects @groupId, query.term, projectsCallback
else else
Api.projects query.term, projectsCallback Api.projects query.term, @orderBy, projectsCallback
id: (project) -> id: (project) ->
project.web_url project.web_url
......
...@@ -22,5 +22,3 @@ class @ProjectsList ...@@ -22,5 +22,3 @@ class @ProjectsList
else else
$(this).show() $(this).show()
uiBox.find("ul.projects-list li.bottom").hide() uiBox.find("ul.projects-list li.bottom").hide()
...@@ -16,12 +16,31 @@ class @ShortcutsIssuable extends ShortcutsNavigation ...@@ -16,12 +16,31 @@ class @ShortcutsIssuable extends ShortcutsNavigation
@replyWithSelectedText() @replyWithSelectedText()
return false return false
) )
Mousetrap.bind('j', =>
@prevIssue()
return false
)
Mousetrap.bind('k', =>
@nextIssue()
return false
)
if isMergeRequest if isMergeRequest
@enabledHelp.push('.hidden-shortcut.merge_requests') @enabledHelp.push('.hidden-shortcut.merge_requests')
else else
@enabledHelp.push('.hidden-shortcut.issues') @enabledHelp.push('.hidden-shortcut.issues')
prevIssue: ->
$prevBtn = $('.prev-btn')
if not $prevBtn.hasClass('disabled')
Turbolinks.visit($prevBtn.attr('href'))
nextIssue: ->
$nextBtn = $('.next-btn')
if not $nextBtn.hasClass('disabled')
Turbolinks.visit($nextBtn.attr('href'))
replyWithSelectedText: -> replyWithSelectedText: ->
if window.getSelection if window.getSelection
selected = window.getSelection().toString() selected = window.getSelection().toString()
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
&.s26 { width: 26px; height: 26px; margin-right: 8px; } &.s26 { width: 26px; height: 26px; margin-right: 8px; }
&.s32 { width: 32px; height: 32px; margin-right: 10px; } &.s32 { width: 32px; height: 32px; margin-right: 10px; }
&.s36 { width: 36px; height: 36px; margin-right: 10px; } &.s36 { width: 36px; height: 36px; margin-right: 10px; }
&.s40 { width: 40px; height: 40px; margin-right: 10px; }
&.s46 { width: 46px; height: 46px; margin-right: 15px; } &.s46 { width: 46px; height: 46px; margin-right: 15px; }
&.s48 { width: 48px; height: 48px; margin-right: 10px; } &.s48 { width: 48px; height: 48px; margin-right: 10px; }
&.s60 { width: 60px; height: 60px; margin-right: 12px; } &.s60 { width: 60px; height: 60px; margin-right: 12px; }
...@@ -40,7 +41,8 @@ ...@@ -40,7 +41,8 @@
&.s16 { font-size: 12px; line-height: 1.33; } &.s16 { font-size: 12px; line-height: 1.33; }
&.s24 { font-size: 14px; line-height: 1.8; } &.s24 { font-size: 14px; line-height: 1.8; }
&.s26 { font-size: 20px; line-height: 1.33; } &.s26 { font-size: 20px; line-height: 1.33; }
&.s32 { font-size: 22px; line-height: 32px; } &.s32 { font-size: 20px; line-height: 32px; }
&.s40 { font-size: 16px; line-height: 40px; }
&.s60 { font-size: 32px; line-height: 60px; } &.s60 { font-size: 32px; line-height: 60px; }
&.s90 { font-size: 36px; line-height: 90px; } &.s90 { font-size: 36px; line-height: 90px; }
&.s110 { font-size: 40px; line-height: 112px; font-weight: 300; } &.s110 { font-size: 40px; line-height: 112px; font-weight: 300; }
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
@include border-radius(3px); @include border-radius(3px);
font-size: $gl-font-size; font-size: $gl-font-size;
font-weight: 500; font-weight: 500;
padding: $gl-vert-padding $gl-padding; padding: $gl-vert-padding $gl-btn-padding;
&:focus, &:focus,
&:active { &:active {
...@@ -82,8 +82,7 @@ ...@@ -82,8 +82,7 @@
&.btn-success, &.btn-success,
&.btn-new, &.btn-new,
&.btn-create, &.btn-create,
&.btn-save, &.btn-save {
&.btn-green {
@include btn-green; @include btn-green;
} }
...@@ -159,7 +158,6 @@ ...@@ -159,7 +158,6 @@
.input-group-btn { .input-group-btn {
.btn { .btn {
@include btn-gray;
@include btn-middle; @include btn-middle;
&:hover { &:hover {
...@@ -186,8 +184,4 @@ ...@@ -186,8 +184,4 @@
border: 1px solid #c6cacf !important; border: 1px solid #c6cacf !important;
background-color: #e4e7ed !important; background-color: #e4e7ed !important;
} }
.btn-green {
@include btn-green
}
} }
...@@ -385,11 +385,11 @@ table { ...@@ -385,11 +385,11 @@ table {
margin-bottom: $gl-padding; margin-bottom: $gl-padding;
} }
.new-project-item-select-holder { .project-item-select-holder {
display: inline-block; display: inline-block;
position: relative; position: relative;
.new-project-item-select { .project-item-select {
position: absolute; position: absolute;
top: 0; top: 0;
right: 0; right: 0;
......
...@@ -73,7 +73,6 @@ header { ...@@ -73,7 +73,6 @@ header {
.title { .title {
margin: 0; margin: 0;
overflow: hidden;
font-size: 19px; font-size: 19px;
line-height: $header-height; line-height: $header-height;
font-weight: normal; font-weight: normal;
...@@ -88,6 +87,22 @@ header { ...@@ -88,6 +87,22 @@ header {
text-decoration: underline; text-decoration: underline;
} }
} }
.dropdown-toggle-caret {
position: relative;
top: -2px;
width: 12px;
line-height: 12px;
margin-left: 5px;
font-size: 10px;
text-align: center;
cursor: pointer;
}
.project-item-select {
right: auto;
left: 0;
}
} }
.navbar-collapse { .navbar-collapse {
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
display: block; display: block;
float: left; float: left;
padding: 0 $gl-padding; padding: 0 $gl-btn-padding;
font-weight: normal; font-weight: normal;
margin-right: 10px; margin-right: 10px;
font-size: $gl-font-size; font-size: $gl-font-size;
......
...@@ -109,7 +109,6 @@ ul.content-list { ...@@ -109,7 +109,6 @@ ul.content-list {
padding: 0; padding: 0;
> li { > li {
padding: $gl-padding 0;
border-color: $table-border-color; border-color: $table-border-color;
color: $gl-gray; color: $gl-gray;
......
...@@ -116,7 +116,7 @@ ...@@ -116,7 +116,7 @@
display: none; display: none;
} }
aside { aside:not(.right-sidebar){
display: none; display: none;
} }
......
...@@ -37,3 +37,93 @@ ...@@ -37,3 +37,93 @@
} }
} }
} }
.top-area {
@include clearfix;
border-bottom: 1px solid #EEE;
.nav-text {
padding-top: 16px;
padding-bottom: 11px;
display: inline-block;
width: 50%;
line-height: 28px;
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) {
width: 100%;
}
}
.nav-links {
display: inline-block;
width: 50%;
margin-bottom: 0px;
border-bottom: none;
/* Small devices (phones, tablets, 768px and lower) */
@media (max-width: $screen-sm-min) {
width: 100%;
}
}
.nav-controls {
width: 50%;
display: inline-block;
float: right;
text-align: right;
padding: 11px 0;
margin-bottom: 0px;
> .dropdown {
margin-right: 10px;
display: inline-block;
}
> .btn {
display: inline-block;
}
> form {
display: inline-block;
}
input {
height: 34px;
display: inline-block;
position: relative;
top: 1px;
margin-right: 10px;
/* Medium devices (desktops, 992px and up) */
@media (min-width: $screen-md-min) { width: 200px; }
/* Large devices (large desktops, 1200px and up) */
@media (min-width: $screen-lg-min) { width: 250px; }
&.input-short {
/* Medium devices (desktops, 992px and up) */
@media (min-width: $screen-md-min) { width: 170px; }
/* Large devices (large desktops, 1200px and up) */
@media (min-width: $screen-lg-min) { width: 210px; }
}
}
/* Hide on extra small devices (phones) */
@media (max-width: $screen-xs-max) {
display: none;
}
/* Small devices (tablets, 768px and lower) */
@media (max-width: $screen-sm-max) {
width: 100%;
text-align: left;
input {
width: 300px;
}
}
}
}
...@@ -45,6 +45,19 @@ ...@@ -45,6 +45,19 @@
overflow: hidden; overflow: hidden;
transition-duration: .3s; transition-duration: .3s;
.home {
z-index: 1;
position: absolute;
left: 0px;
}
#logo {
z-index: 2;
position: absolute;
width: 58px;
cursor: pointer;
}
a { a {
float: left; float: left;
height: $header-height; height: $header-height;
...@@ -70,7 +83,7 @@ ...@@ -70,7 +83,7 @@
width: 158px; width: 158px;
float: left; float: left;
margin: 0; margin: 0;
margin-left: 14px; margin-left: 50px;
font-size: 19px; font-size: 19px;
line-height: 41px; line-height: 41px;
font-weight: normal; font-weight: normal;
...@@ -200,6 +213,14 @@ ...@@ -200,6 +213,14 @@
} }
} }
@mixin expanded-gutter {
padding-right: $gutter_width;
}
@mixin collapsed-gutter {
padding-right: $sidebar_collapsed_width;
}
@mixin collapsed-sidebar { @mixin collapsed-sidebar {
padding-left: $sidebar_collapsed_width; padding-left: $sidebar_collapsed_width;
...@@ -266,6 +287,7 @@ ...@@ -266,6 +287,7 @@
background: #f2f6f7; background: #f2f6f7;
} }
// page is small enough
@media (max-width: $screen-md-max) { @media (max-width: $screen-md-max) {
.page-sidebar-collapsed { .page-sidebar-collapsed {
@include collapsed-sidebar; @include collapsed-sidebar;
...@@ -275,12 +297,32 @@ ...@@ -275,12 +297,32 @@
@include collapsed-sidebar; @include collapsed-sidebar;
} }
.page-gutter {
&.right-sidebar-collapsed {
@include collapsed-gutter;
}
&.right-sidebar-expanded {
@include expanded-gutter;
}
}
.collapse-nav { .collapse-nav {
display: none; display: none;
} }
} }
// page is large enough
@media(min-width: $screen-md-max) { @media(min-width: $screen-md-max) {
.page-gutter {
&.right-sidebar-collapsed {
@include collapsed-gutter;
}
&.right-sidebar-expanded {
@include expanded-gutter;
}
}
.page-sidebar-collapsed { .page-sidebar-collapsed {
@include collapsed-sidebar; @include collapsed-sidebar;
} }
......
...@@ -12,6 +12,9 @@ $gl-font-size: 15px; ...@@ -12,6 +12,9 @@ $gl-font-size: 15px;
$list-font-size: 15px; $list-font-size: 15px;
$sidebar_collapsed_width: 62px; $sidebar_collapsed_width: 62px;
$sidebar_width: 230px; $sidebar_width: 230px;
$gutter_collapsed_width: 62px;
$gutter_width: 290px;
$gutter_inner_width: 258px;
$avatar_radius: 50%; $avatar_radius: 50%;
$code_font_size: 13px; $code_font_size: 13px;
$code_line_height: 1.5; $code_line_height: 1.5;
...@@ -22,9 +25,10 @@ $header-height: 58px; ...@@ -22,9 +25,10 @@ $header-height: 58px;
$fixed-layout-width: 1280px; $fixed-layout-width: 1280px;
$gl-gray: #5a5a5a; $gl-gray: #5a5a5a;
$gl-padding: 16px; $gl-padding: 16px;
$gl-btn-padding: 10px;
$gl-vert-padding: 6px; $gl-vert-padding: 6px;
$gl-padding-top:10px; $gl-padding-top:10px;
$gl-avatar-size: 46px; $gl-avatar-size: 40px;
$secondary-text: #7f8fa4; $secondary-text: #7f8fa4;
$error-exclamation-point: #E62958; $error-exclamation-point: #E62958;
...@@ -36,11 +40,12 @@ $white-light: #FFFFFF; ...@@ -36,11 +40,12 @@ $white-light: #FFFFFF;
$white-normal: #ededed; $white-normal: #ededed;
$white-dark: #ededed; $white-dark: #ededed;
$gray-light: #f7f7f7; $gray-light: #faf9f9;
$gray-normal: #ededed; $gray-normal: #f5f5f5;
$gray-dark: #ededed; $gray-dark: #ededed;
$gray-darkest: #c9c9c9;
$green-light: #31AF64; $green-light: #38ae67;
$green-normal: #2FAA60; $green-normal: #2FAA60;
$green-dark: #2CA05B; $green-dark: #2CA05B;
...@@ -52,7 +57,7 @@ $blue-medium-light: #3498CB; ...@@ -52,7 +57,7 @@ $blue-medium-light: #3498CB;
$blue-medium: #2F8EBF; $blue-medium: #2F8EBF;
$blue-medium-dark: #2D86B4; $blue-medium-dark: #2D86B4;
$orange-light: #FC6443; $orange-light: rgba(252, 109, 38, 0.80);
$orange-normal: #E75E40; $orange-normal: #E75E40;
$orange-dark: #CE5237; $orange-dark: #CE5237;
...@@ -64,8 +69,8 @@ $border-white-light: #F1F2F4; ...@@ -64,8 +69,8 @@ $border-white-light: #F1F2F4;
$border-white-normal: #D6DAE2; $border-white-normal: #D6DAE2;
$border-white-dark: #C6CACF; $border-white-dark: #C6CACF;
$border-gray-light: #d1d1d1; $border-gray-light: rgba(0, 0, 0, 0.06);
$border-gray-normal: #D6DAE2; $border-gray-normal: rgba(0, 0, 0, 0.10);;
$border-gray-dark: #C6CACF; $border-gray-dark: #C6CACF;
$border-green-light: #2FAA60; $border-green-light: #2FAA60;
...@@ -76,7 +81,7 @@ $border-blue-light: #2D9FD8; ...@@ -76,7 +81,7 @@ $border-blue-light: #2D9FD8;
$border-blue-normal: #2897CE; $border-blue-normal: #2897CE;
$border-blue-dark: #258DC1; $border-blue-dark: #258DC1;
$border-orange-light: #ED5C3D; $border-orange-light: #fc6d26;
$border-orange-normal: #CE5237; $border-orange-normal: #CE5237;
$border-orange-dark: #C14E35; $border-orange-dark: #C14E35;
......
...@@ -40,10 +40,6 @@ ...@@ -40,10 +40,6 @@
.avatar { .avatar {
@include border-radius(50%); @include border-radius(50%);
} }
.identicon {
line-height: 46px;
}
} }
.dash-project-access-icon { .dash-project-access-icon {
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
*/ */
.event-item { .event-item {
font-size: $gl-font-size; font-size: $gl-font-size;
padding: $gl-padding 0 $gl-padding ($gl-avatar-size + 15px); padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top);
border-bottom: 1px solid $table-border-color; border-bottom: 1px solid $table-border-color;
color: #7f8fa4; color: #7f8fa4;
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
.event-title, .event-title,
.event-item-timestamp { .event-item-timestamp {
line-height: 44px; line-height: 40px;
} }
} }
...@@ -25,7 +25,7 @@ ...@@ -25,7 +25,7 @@
} }
.avatar { .avatar {
margin-left: -($gl-avatar-size + 15px); margin-left: -($gl-avatar-size + $gl-padding-top);
} }
.event-title { .event-title {
...@@ -41,7 +41,6 @@ ...@@ -41,7 +41,6 @@
margin-right: 174px; margin-right: 174px;
.event-note { .event-note {
margin-top: 5px;
word-wrap: break-word; word-wrap: break-word;
.md { .md {
...@@ -98,8 +97,6 @@ ...@@ -98,8 +97,6 @@
&:last-child { border:none } &:last-child { border:none }
.event_commits { .event_commits {
margin-top: 9px;
li { li {
&.commit { &.commit {
background: transparent; background: transparent;
......
...@@ -6,11 +6,3 @@ ...@@ -6,11 +6,3 @@
font-size: 30px; font-size: 30px;
} }
} }
.explore-trending-block {
.lead {
line-height: 32px;
font-size: 18px;
margin-top: 10px;
}
}
...@@ -26,3 +26,21 @@ ...@@ -26,3 +26,21 @@
font-weight: 600; font-weight: 600;
color: #4c4e54; color: #4c4e54;
} }
.group-row {
&.no-description {
.group-name {
line-height: 44px;
}
}
.stats {
float: right;
line-height: 44px;
color: $gl-gray;
span {
margin-right: 15px;
}
}
}
...@@ -29,21 +29,8 @@ ...@@ -29,21 +29,8 @@
} }
} }
.project-issuable-filter {
.controls {
float: right;
margin-top: 11px;
}
.nav-links {
text-align: left;
}
}
.issuable-details { .issuable-details {
section { section {
border-right: 1px solid $border-white-light;
.issuable-discussion { .issuable-discussion {
margin-right: 1px; margin-right: 1px;
} }
...@@ -73,11 +60,35 @@ ...@@ -73,11 +60,35 @@
.block { .block {
@include clearfix; @include clearfix;
padding: $gl-padding 0; padding: $gl-padding 0;
border-bottom: 1px solid #F0F0F0; border-bottom: 1px solid $border-gray-light;
// This prevents the mess when resizing the sidebar
// of elements repositioning themselves..
width: $gutter_inner_width;
overflow-x: hidden;
// --
&:first-child {
padding-top: 5px;
}
&:last-child { &:last-child {
border: none; border: none;
} }
span {
margin-top: 7px;
display: inline-block;
}
.issuable-count {
}
.gutter-toggle {
margin-left: 20px;
border-left: 1px solid $border-gray-light;
padding-left: 10px;
}
} }
.title { .title {
...@@ -133,3 +144,98 @@ ...@@ -133,3 +144,98 @@
margin-right: 2px; margin-right: 2px;
} }
} }
.right-sidebar {
position: fixed;
top: 58px;
right: 0;
height: 100%;
transition-duration: .3s;
background: $gray-light;
overflow: scroll;
padding: 10px 20px;
&.right-sidebar-expanded {
width: $gutter_width;
hr {
display: none;
}
}
.subscribe-button {
span {
margin-top: 0;
}
}
&.right-sidebar-collapsed {
width: $sidebar_collapsed_width;
padding-top: 0;
overflow-x: hidden;
hr {
margin: 0;
color: $gray-normal;
border-color: $gray-normal;
width: 62px;
margin-left: -20px
}
.block {
border-bottom: none;
padding: 15px 0 0 0;
}
}
.btn {
background: $gray-normal;
border: 1px solid $border-gray-normal;
}
&.right-sidebar-collapsed {
.issuable-count,
.issuable-nav,
.assignee > *,
.milestone > *,
.labels > *,
.participants > *,
.light > *,
.project-reference > * {
display: none;
}
.gutter-toggle {
margin-left: -$gutter_inner_width + 4;
}
.sidebar-collapsed-icon {
display: block;
float: left;
width: 62px;
text-align: center;
margin-left: -19px;
padding-bottom: 10px;
color: #999999;
span {
display: block;
margin-top: 0;
}
}
}
&.right-sidebar-expanded {
.sidebar-collapsed-icon {
display: none;
}
}
}
.detail-page-description {
small {
color: $gray-darkest;
}
}
\ No newline at end of file
...@@ -65,10 +65,6 @@ form.edit-issue { ...@@ -65,10 +65,6 @@ form.edit-issue {
width: 3em; width: 3em;
} }
.merge-request-info {
padding-left: 5px;
}
.merge-request-status { .merge-request-status {
color: $gl-gray; color: $gl-gray;
font-size: 15px; font-size: 15px;
......
...@@ -281,36 +281,6 @@ ...@@ -281,36 +281,6 @@
margin-top: -1px; margin-top: -1px;
} }
.top-area {
border-bottom: 1px solid #EEE;
ul.nav-links {
display: inline-block;
width: 50%;
margin-bottom: 0px;
border-bottom: none;
}
.projects-search-form {
width: 50%;
display: inline-block;
float: right;
padding-top: 11px;
text-align: right;
.btn-green {
margin-left: 10px;
float: right;
}
}
@media (max-width: $screen-xs-max) {
.projects-search-form {
padding-top: 15px;
}
}
}
.fork-namespaces { .fork-namespaces {
.fork-thumbnail { .fork-thumbnail {
text-align: center; text-align: center;
...@@ -386,22 +356,6 @@ pre.light-well { ...@@ -386,22 +356,6 @@ pre.light-well {
border-color: #f1f1f1; border-color: #f1f1f1;
} }
.projects-search-form {
padding: $gl-padding 0;
padding-bottom: 0;
margin-bottom: 0px;
input {
display: inline-block;
width: calc(100% - 151px);
}
.btn {
display: inline-block;
width: 135px;
}
}
.git-empty { .git-empty {
margin: 0 7px 0 7px; margin: 0 7px 0 7px;
...@@ -437,12 +391,11 @@ pre.light-well { ...@@ -437,12 +391,11 @@ pre.light-well {
@include basic-list; @include basic-list;
.project-row { .project-row {
padding: $gl-padding 0;
border-color: $table-border-color; border-color: $table-border-color;
&.no-description { &.no-description {
.project { .project {
line-height: 44px; line-height: 40px;
} }
} }
...@@ -455,12 +408,16 @@ pre.light-well { ...@@ -455,12 +408,16 @@ pre.light-well {
.project-controls { .project-controls {
float: right; float: right;
color: $gl-gray; color: $gl-gray;
line-height: 45px; line-height: 40px;
color: #7f8fa4; color: #7f8fa4;
a:hover { a:hover {
text-decoration: none; text-decoration: none;
} }
> span {
margin-left: 10px;
}
} }
.project-description { .project-description {
...@@ -565,46 +522,6 @@ pre.light-well { ...@@ -565,46 +522,6 @@ pre.light-well {
margin-top: 2px; margin-top: 2px;
} }
/*
* Forks list rendered on Project's forks page
*/
.forks-top-block {
padding: 16px 0;
}
.projects-search-form {
.dropdown-toggle.btn {
margin-top: -3px;
}
&.fork-search-form {
margin: 0;
margin-top: -$gl-padding;
padding-bottom: 0;
input {
/* Small devices (tablets, 768px and up) */
@media (min-width: $screen-sm-min) { width: 180px; }
/* Medium devices (desktops, 992px and up) */
@media (min-width: $screen-md-min) { width: 350px; }
/* Large devices (large desktops, 1200px and up) */
@media (min-width: $screen-lg-min) { width: 400px; }
}
.sort-forks {
width: 160px;
}
.fork-link {
float: right;
margin-left: $gl-padding;
}
}
}
.private-forks-notice .private-fork-icon { .private-forks-notice .private-fork-icon {
i:nth-child(1) { i:nth-child(1) {
color: #2AA056; color: #2AA056;
......
...@@ -81,6 +81,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -81,6 +81,8 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:recaptcha_private_key, :recaptcha_private_key,
:sentry_enabled, :sentry_enabled,
:sentry_dsn, :sentry_dsn,
:akismet_enabled,
:akismet_api_key,
restricted_visibility_levels: [], restricted_visibility_levels: [],
import_sources: [] import_sources: []
) )
......
...@@ -2,7 +2,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController ...@@ -2,7 +2,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
before_action :finder, only: [:edit, :update, :destroy] before_action :finder, only: [:edit, :update, :destroy]
def index def index
@broadcast_messages = BroadcastMessage.reorder("starts_at ASC").page(params[:page]) @broadcast_messages = BroadcastMessage.reorder("ends_at DESC").page(params[:page])
@broadcast_message = BroadcastMessage.new @broadcast_message = BroadcastMessage.new
end end
......
class Admin::SpamLogsController < Admin::ApplicationController
def index
@spam_logs = SpamLog.order(id: :desc).page(params[:page])
end
def destroy
spam_log = SpamLog.find(params[:id])
if params[:remove_user]
spam_log.remove_user
redirect_to admin_spam_logs_path, notice: "User #{spam_log.user.username} was successfully removed."
else
spam_log.destroy
render nothing: true
end
end
end
...@@ -60,6 +60,8 @@ class ApplicationController < ActionController::Base ...@@ -60,6 +60,8 @@ class ApplicationController < ActionController::Base
params[:authenticity_token].presence params[:authenticity_token].presence
elsif params[:private_token].presence elsif params[:private_token].presence
params[:private_token].presence params[:private_token].presence
elsif request.headers['PRIVATE-TOKEN'].present?
request.headers['PRIVATE-TOKEN']
end end
user = user_token && User.find_by_authentication_token(user_token.to_s) user = user_token && User.find_by_authentication_token(user_token.to_s)
...@@ -279,9 +281,10 @@ class ApplicationController < ActionController::Base ...@@ -279,9 +281,10 @@ class ApplicationController < ActionController::Base
} }
end end
def view_to_html_string(partial) def view_to_html_string(partial, locals = {})
render_to_string( render_to_string(
partial, partial,
locals: locals,
layout: false, layout: false,
formats: [:html] formats: [:html]
) )
......
...@@ -3,7 +3,16 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -3,7 +3,16 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
def index def index
@projects = current_user.authorized_projects.sorted_by_activity.non_archived @projects = current_user.authorized_projects.sorted_by_activity.non_archived
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace)
terms = params['filter_projects']
if terms.present?
@projects = @projects.search(terms)
end
@projects = @projects.page(params[:page]).per(PER_PAGE)
@last_push = current_user.recent_push @last_push = current_user.recent_push
respond_to do |format| respond_to do |format|
...@@ -13,6 +22,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -13,6 +22,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
load_events load_events
render layout: false render layout: false
end end
format.json do
render json: {
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end
end end
end end
...@@ -20,6 +34,14 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -20,6 +34,14 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = current_user.starred_projects @projects = current_user.starred_projects
@projects = @projects.includes(:namespace, :forked_from_project, :tags) @projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
terms = params['filter_projects']
if terms.present?
@projects = @projects.search(terms)
end
@projects = @projects.page(params[:page]).per(PER_PAGE)
@last_push = current_user.recent_push @last_push = current_user.recent_push
@groups = [] @groups = []
...@@ -27,8 +49,9 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -27,8 +49,9 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
format.html format.html
format.json do format.json do
load_events render json: {
pager_json("events/_events", @events.count) html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
}
end end
end end
end end
......
...@@ -11,14 +11,14 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -11,14 +11,14 @@ class Explore::ProjectsController < Explore::ApplicationController
end end
def trending def trending
@trending_projects = TrendingProjectsFinder.new.execute(current_user) @projects = TrendingProjectsFinder.new.execute(current_user)
@trending_projects = @trending_projects.non_archived @projects = @projects.non_archived
@trending_projects = @trending_projects.page(params[:page]).per(PER_PAGE) @projects = @projects.page(params[:page]).per(PER_PAGE)
end end
def starred def starred
@starred_projects = ProjectsFinder.new.execute(current_user) @projects = ProjectsFinder.new.execute(current_user)
@starred_projects = @starred_projects.reorder('star_count DESC') @projects = @projects.reorder('star_count DESC')
@starred_projects = @starred_projects.page(params[:page]).per(PER_PAGE) @projects = @projects.page(params[:page]).per(PER_PAGE)
end end
end end
...@@ -41,6 +41,7 @@ class GroupsController < Groups::ApplicationController ...@@ -41,6 +41,7 @@ class GroupsController < Groups::ApplicationController
def show def show
@last_push = current_user.recent_push if current_user @last_push = current_user.recent_push if current_user
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace)
@projects = @projects.page(params[:page]).per(PER_PAGE)
@shared_projects = @group.shared_projects @shared_projects = @group.shared_projects
......
...@@ -30,7 +30,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -30,7 +30,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Do additional LDAP checks for the user filter and EE features # Do additional LDAP checks for the user filter and EE features
if ldap_user.allowed? if ldap_user.allowed?
if @user.otp_required_for_login? if @user.two_factor_enabled?
prompt_for_two_factor(@user) prompt_for_two_factor(@user)
else else
log_audit_event(@user, with: :ldap) log_audit_event(@user, with: :ldap)
......
...@@ -28,6 +28,11 @@ class Projects::ApplicationController < ApplicationController ...@@ -28,6 +28,11 @@ class Projects::ApplicationController < ApplicationController
private private
def apply_diff_view_cookie!
view = params[:view] || cookies[:diff_view]
cookies.permanent[:diff_view] = params[:view] = view if view
end
def builds_enabled def builds_enabled
return render_404 unless @project.builds_enabled? return render_404 unless @project.builds_enabled?
end end
......
...@@ -2,15 +2,13 @@ class Projects::AvatarsController < Projects::ApplicationController ...@@ -2,15 +2,13 @@ class Projects::AvatarsController < Projects::ApplicationController
before_action :project before_action :project
def show def show
@blob = @project.repository.blob_at_branch('master', @project.avatar_in_git) @blob = @repository.blob_at_branch('master', @project.avatar_in_git)
if @blob if @blob
headers['X-Content-Type-Options'] = 'nosniff' headers['X-Content-Type-Options'] = 'nosniff'
send_data( headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
@blob.data, headers['Content-Disposition'] = 'inline'
type: @blob.mime_type, headers['Content-Type'] = @blob.content_type
disposition: 'inline', head :ok # 'render nothing: true' messes up the Content-Type
filename: @blob.name
)
else else
render_404 render_404
end end
......
...@@ -33,6 +33,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -33,6 +33,7 @@ class Projects::BlobController < Projects::ApplicationController
def edit def edit
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
blob.load_all_data!(@repository)
end end
def update def update
...@@ -51,6 +52,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -51,6 +52,7 @@ class Projects::BlobController < Projects::ApplicationController
def preview def preview
@content = params[:content] @content = params[:content]
@blob.load_all_data!(@repository)
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true) diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true)
diff_lines = diffy.diff.scan(/.*\n/)[2..-1] diff_lines = diffy.diff.scan(/.*\n/)[2..-1]
diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines) diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines)
......
...@@ -13,6 +13,8 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -13,6 +13,8 @@ class Projects::CommitController < Projects::ApplicationController
def show def show
return git_not_found! unless @commit return git_not_found! unless @commit
apply_diff_view_cookie!
@line_notes = commit.notes.inline @line_notes = commit.notes.inline
@note = @project.build_commit_note(commit) @note = @project.build_commit_note(commit)
@notes = commit.notes.not_inline.fresh @notes = commit.notes.not_inline.fresh
......
...@@ -57,6 +57,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -57,6 +57,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def diffs def diffs
apply_diff_view_cookie!
@commit = @merge_request.last_commit @commit = @merge_request.last_commit
@base_commit = @merge_request.diff_base_commit @base_commit = @merge_request.diff_base_commit
......
...@@ -15,7 +15,10 @@ class Projects::RawController < Projects::ApplicationController ...@@ -15,7 +15,10 @@ class Projects::RawController < Projects::ApplicationController
if @blob.lfs_pointer? if @blob.lfs_pointer?
send_lfs_object send_lfs_object
else else
stream_data headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
headers['Content-Disposition'] = 'inline'
headers['Content-Type'] = get_blob_type
head :ok # 'render nothing: true' messes up the Content-Type
end end
else else
render_404 render_404
...@@ -34,16 +37,6 @@ class Projects::RawController < Projects::ApplicationController ...@@ -34,16 +37,6 @@ class Projects::RawController < Projects::ApplicationController
end end
end end
def stream_data
type = get_blob_type
send_data(
@blob.data,
type: type,
disposition: 'inline'
)
end
def send_lfs_object def send_lfs_object
lfs_object = find_lfs_object lfs_object = find_lfs_object
......
...@@ -6,6 +6,7 @@ class UsersController < ApplicationController ...@@ -6,6 +6,7 @@ class UsersController < ApplicationController
@contributed_projects = contributed_projects.joined(@user).reject(&:forked?) @contributed_projects = contributed_projects.joined(@user).reject(&:forked?)
@projects = PersonalProjectsFinder.new(@user).execute(current_user) @projects = PersonalProjectsFinder.new(@user).execute(current_user)
@projects = @projects.page(params[:page]).per(PER_PAGE)
@groups = @user.groups.order_id_desc @groups = @user.groups.order_id_desc
......
...@@ -172,18 +172,6 @@ module ApplicationHelper ...@@ -172,18 +172,6 @@ module ApplicationHelper
Gitlab.config.extra Gitlab.config.extra
end end
def search_placeholder
if @project && @project.persisted?
'Search'
elsif @snippet || @snippets || @show_snippets
'Search snippets'
elsif @group && @group.persisted?
'Search in this group'
else
'Search'
end
end
# Render a `time` element with Javascript-based relative date and tooltip # Render a `time` element with Javascript-based relative date and tooltip
# #
# time - Time object # time - Time object
...@@ -296,6 +284,76 @@ module ApplicationHelper ...@@ -296,6 +284,76 @@ module ApplicationHelper
end end
end end
def issuable_link_next(project,issuable)
if project.nil?
nil
elsif current_controller?(:issues)
namespace_project_issue_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid))
elsif current_controller?(:merge_requests)
namespace_project_merge_request_path(project.namespace, project, next_issuable_for(project, issuable.id).try(:iid))
end
end
def issuable_link_prev(project,issuable)
if project.nil?
nil
elsif current_controller?(:issues)
namespace_project_issue_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid))
elsif current_controller?(:merge_requests)
namespace_project_merge_request_path(project.namespace, project, prev_issuable_for(project, issuable.id).try(:iid))
end
end
def issuable_count(entity, project)
if project.nil?
0
elsif current_controller?(:issues)
project.issues.send(entity).count
elsif current_controller?(:merge_requests)
project.merge_requests.send(entity).count
end
end
def next_issuable_for(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id > ?", id).last
elsif current_controller?(:merge_requests)
project.merge_requests.where("id > ?", id).last
end
end
def has_next_issuable?(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id > ?", id).last
elsif current_controller?(:merge_requests)
project.merge_requests.where("id > ?", id).last
end
end
def prev_issuable_for(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id < ?", id).first
elsif current_controller?(:merge_requests)
project.merge_requests.where("id < ?", id).first
end
end
def has_prev_issuable?(project, id)
if project.nil?
nil
elsif current_controller?(:issues)
project.issues.where("id < ?", id).first
elsif current_controller?(:merge_requests)
project.merge_requests.where("id < ?", id).first
end
end
def state_filters_text_for(entity, project) def state_filters_text_for(entity, project)
titles = { titles = {
opened: "Open" opened: "Open"
......
...@@ -27,6 +27,10 @@ module ApplicationSettingsHelper ...@@ -27,6 +27,10 @@ module ApplicationSettingsHelper
current_application_settings.user_oauth_applications current_application_settings.user_oauth_applications
end end
def askimet_enabled?
current_application_settings.akismet_enabled?
end
# Return a group of checkboxes that use Bootstrap's button plugin for a # Return a group of checkboxes that use Bootstrap's button plugin for a
# toggle button effect. # toggle button effect.
def restricted_level_checkboxes(help_block_id) def restricted_level_checkboxes(help_block_id)
......
...@@ -10,8 +10,19 @@ module ExploreHelper ...@@ -10,8 +10,19 @@ module ExploreHelper
options = exist_opts.merge(options) options = exist_opts.merge(options)
path = explore_projects_path path = if explore_controller?
explore_projects_path
elsif current_action?(:starred)
starred_dashboard_projects_path
else
dashboard_projects_path
end
path << "?#{options.to_param}" path << "?#{options.to_param}"
path path
end end
def explore_controller?
controller.class.name.split("::").first == "Explore"
end
end end
...@@ -7,6 +7,8 @@ module LabelsHelper ...@@ -7,6 +7,8 @@ module LabelsHelper
# project - Project object which will be used as the context for the label's # project - Project object which will be used as the context for the label's
# link. If omitted, defaults to `@project`, or the label's own # link. If omitted, defaults to `@project`, or the label's own
# project. # project.
# type - The type of item the link will point to (:issue or
# :merge_request). If omitted, defaults to :issue.
# block - An optional block that will be passed to `link_to`, forming the # block - An optional block that will be passed to `link_to`, forming the
# body of the link element. If omitted, defaults to # body of the link element. If omitted, defaults to
# `render_colored_label`. # `render_colored_label`.
...@@ -23,13 +25,18 @@ module LabelsHelper ...@@ -23,13 +25,18 @@ module LabelsHelper
# # Force the generated link to use a provided project # # Force the generated link to use a provided project
# link_to_label(label, project: Project.last) # link_to_label(label, project: Project.last)
# #
# # Force the generated link to point to merge requests instead of issues
# link_to_label(label, type: :merge_request)
#
# # Customize link body with a block # # Customize link body with a block
# link_to_label(label) { "My Custom Label Text" } # link_to_label(label) { "My Custom Label Text" }
# #
# Returns a String # Returns a String
def link_to_label(label, project: nil, &block) def link_to_label(label, project: nil, type: :issue, &block)
project ||= @project || label.project project ||= @project || label.project
link = namespace_project_issues_path(project.namespace, project, link = send("namespace_project_#{type.to_s.pluralize}_path",
project.namespace,
project,
label_name: label.name) label_name: label.name)
if block_given? if block_given?
......
...@@ -3,6 +3,18 @@ module NavHelper ...@@ -3,6 +3,18 @@ module NavHelper
cookies[:collapsed_nav] == 'true' cookies[:collapsed_nav] == 'true'
end end
def sidebar_gutter_collapsed_class
if cookies[:collapsed_gutter] == 'true'
"right-sidebar-collapsed"
else
"right-sidebar-expanded"
end
end
def sidebar_gutter_collapsed?
cookies[:collapsed_gutter] == 'true'
end
def nav_sidebar_class def nav_sidebar_class
if nav_menu_collapsed? if nav_menu_collapsed?
"sidebar-collapsed" "sidebar-collapsed"
...@@ -19,6 +31,19 @@ module NavHelper ...@@ -19,6 +31,19 @@ module NavHelper
end end
end end
def page_gutter_class
if current_path?('merge_requests#show') ||
current_path?('merge_requests#diffs') ||
current_path?('merge_requests#commits') ||
current_path?('issues#show')
if cookies[:collapsed_gutter] == 'true'
"page-gutter right-sidebar-collapsed"
else
"page-gutter right-sidebar-expanded"
end
end
end
def nav_header_class def nav_header_class
if nav_menu_collapsed? if nav_menu_collapsed?
"header-collapsed" "header-collapsed"
......
...@@ -20,6 +20,12 @@ module ProjectsHelper ...@@ -20,6 +20,12 @@ module ProjectsHelper
end end
end end
def link_to_member_avatar(author, opts = {})
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts)
image_tag(avatar_icon(author, opts[:size]), width: opts[:size], class: "avatar avatar-inline #{"s#{opts[:size]}" if opts[:size]}", alt:'') if opts[:avatar]
end
def link_to_member(project, author, opts = {}) def link_to_member(project, author, opts = {})
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" } default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
opts = default_opts.merge(opts) opts = default_opts.merge(opts)
...@@ -53,15 +59,24 @@ module ProjectsHelper ...@@ -53,15 +59,24 @@ module ProjectsHelper
link_to(simple_sanitize(owner.name), user_path(owner)) link_to(simple_sanitize(owner.name), user_path(owner))
end end
project_link = link_to(simple_sanitize(project.name), project_path(project)) project_link = link_to project_path(project), { class: "project-item-select-holder" } do
link_output = simple_sanitize(project.name)
if current_user
link_output += project_select_tag :project_path,
class: "project-item-select js-projects-dropdown",
data: { include_groups: false, order_by: 'last_activity_at' }
end
link_output
end
project_link += icon "chevron-down", class: "dropdown-toggle-caret js-projects-dropdown-toggle" if current_user
full_title = namespace_link + ' / ' + project_link full_title = namespace_link + ' / ' + project_link
full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url) if name full_title += ' &middot; '.html_safe + link_to(simple_sanitize(name), url) if name
content_tag :span do
full_title full_title
end end
end
def remove_project_message(project) def remove_project_message(project)
"You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?" "You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?"
......
...@@ -70,7 +70,7 @@ module SearchHelper ...@@ -70,7 +70,7 @@ module SearchHelper
# Autocomplete results for the current user's groups # Autocomplete results for the current user's groups
def groups_autocomplete(term, limit = 5) def groups_autocomplete(term, limit = 5)
Group.search(term).limit(limit).map do |group| current_user.authorized_groups.search(term).limit(limit).map do |group|
{ {
label: "group: #{search_result_sanitize(group.name)}", label: "group: #{search_result_sanitize(group.name)}",
url: group_path(group) url: group_path(group)
...@@ -80,7 +80,7 @@ module SearchHelper ...@@ -80,7 +80,7 @@ module SearchHelper
# Autocomplete results for the current user's projects # Autocomplete results for the current user's projects
def projects_autocomplete(term, limit = 5) def projects_autocomplete(term, limit = 5)
ProjectsFinder.new.execute(current_user).search_by_title(term). current_user.authorized_projects.search_by_title(term).
sorted_by_stars.non_archived.limit(limit).map do |p| sorted_by_stars.non_archived.limit(limit).map do |p|
{ {
label: "project: #{search_result_sanitize(p.name_with_namespace)}", label: "project: #{search_result_sanitize(p.name_with_namespace)}",
......
...@@ -10,7 +10,7 @@ class EmailRejectionMailer < BaseMailer ...@@ -10,7 +10,7 @@ class EmailRejectionMailer < BaseMailer
subject: "[Rejected] #{@original_message.subject}" subject: "[Rejected] #{@original_message.subject}"
} }
headers['Message-ID'] = SecureRandom.hex headers['Message-ID'] = "<#{SecureRandom.hex}@#{Gitlab.config.gitlab.host}>"
headers['In-Reply-To'] = @original_message.message_id headers['In-Reply-To'] = @original_message.message_id
headers['References'] = @original_message.message_id headers['References'] = @original_message.message_id
......
...@@ -89,6 +89,10 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -89,6 +89,10 @@ class ApplicationSetting < ActiveRecord::Base
presence: true, presence: true,
if: :sentry_enabled if: :sentry_enabled
validates :akismet_api_key,
presence: true,
if: :akismet_enabled
validates_each :restricted_visibility_levels do |record, attr, value| validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil? unless value.nil?
value.each do |level| value.each do |level|
...@@ -144,7 +148,9 @@ class ApplicationSetting < ActiveRecord::Base ...@@ -144,7 +148,9 @@ class ApplicationSetting < ActiveRecord::Base
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'], max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false, require_two_factor_authentication: false,
two_factor_grace_period: 48 two_factor_grace_period: 48,
recaptcha_enabled: false,
akismet_enabled: false
) )
end end
......
...@@ -205,7 +205,11 @@ module Ci ...@@ -205,7 +205,11 @@ module Ci
end end
def ci_yaml_file def ci_yaml_file
@ci_yaml_file ||= project.repository.blob_at(sha, '.gitlab-ci.yml').data @ci_yaml_file ||= begin
blob = project.repository.blob_at(sha, '.gitlab-ci.yml')
blob.load_all_data!(project.repository)
blob.data
end
rescue rescue
nil nil
end end
......
...@@ -49,7 +49,7 @@ class Event < ActiveRecord::Base ...@@ -49,7 +49,7 @@ class Event < ActiveRecord::Base
scope :code_push, -> { where(action: PUSHED) } scope :code_push, -> { where(action: PUSHED) }
scope :in_projects, ->(projects) do scope :in_projects, ->(projects) do
where(project_id: projects.select(:id).reorder(nil)).recent where(project_id: projects.map(&:id)).recent
end end
scope :with_associations, -> { includes(project: :namespace) } scope :with_associations, -> { includes(project: :namespace) }
......
...@@ -262,7 +262,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -262,7 +262,7 @@ class MergeRequest < ActiveRecord::Base
end end
def work_in_progress? def work_in_progress?
!!(title =~ /\A\[?WIP\]?:? /i) !!(title =~ /\A\[?WIP(\]|:| )/i)
end end
def mergeable? def mergeable?
...@@ -288,7 +288,8 @@ class MergeRequest < ActiveRecord::Base ...@@ -288,7 +288,8 @@ class MergeRequest < ActiveRecord::Base
def can_remove_source_branch?(current_user) def can_remove_source_branch?(current_user)
!source_project.protected_branch?(source_branch) && !source_project.protected_branch?(source_branch) &&
!source_project.root_ref?(source_branch) && !source_project.root_ref?(source_branch) &&
Ability.abilities.allowed?(current_user, :push_code, source_project) Ability.abilities.allowed?(current_user, :push_code, source_project) &&
last_commit == source_project.commit(source_branch)
end end
def mr_and_commit_notes def mr_and_commit_notes
......
...@@ -208,8 +208,11 @@ class Repository ...@@ -208,8 +208,11 @@ class Repository
cache.fetch(:"diverging_commit_counts_#{branch.name}") do cache.fetch(:"diverging_commit_counts_#{branch.name}") do
# Rugged seems to throw a `ReferenceError` when given branch_names rather # Rugged seems to throw a `ReferenceError` when given branch_names rather
# than SHA-1 hashes # than SHA-1 hashes
number_commits_behind = commits_between(branch.target, root_ref_hash).size number_commits_behind = raw_repository.
number_commits_ahead = commits_between(root_ref_hash, branch.target).size count_commits_between(branch.target, root_ref_hash)
number_commits_ahead = raw_repository.
count_commits_between(root_ref_hash, branch.target)
{ behind: number_commits_behind, ahead: number_commits_ahead } { behind: number_commits_behind, ahead: number_commits_ahead }
end end
......
class SpamLog < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
def remove_user
user.block
user.destroy
end
end
class SpamReport < ActiveRecord::Base
belongs_to :user
validates :user, presence: true
end
...@@ -39,6 +39,8 @@ class Tree ...@@ -39,6 +39,8 @@ class Tree
git_repo = repository.raw_repository git_repo = repository.raw_repository
@readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path) @readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path)
@readme.load_all_data!(git_repo)
@readme
end end
def trees def trees
......
...@@ -140,6 +140,7 @@ class User < ActiveRecord::Base ...@@ -140,6 +140,7 @@ class User < ActiveRecord::Base
has_many :approvals, dependent: :destroy has_many :approvals, dependent: :destroy
has_many :approvers, dependent: :destroy has_many :approvers, dependent: :destroy
has_one :abuse_report, dependent: :destroy has_one :abuse_report, dependent: :destroy
has_many :spam_logs, dependent: :destroy
has_many :builds, dependent: :nullify, class_name: 'Ci::Build' has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
......
class CreateSpamLogService < BaseService
def initialize(project, user, params)
super(project, user, params)
end
def execute
spam_params = params.merge({ user_id: @current_user.id,
project_id: @project.id } )
spam_log = SpamLog.new(spam_params)
spam_log.save
spam_log
end
end
...@@ -231,20 +231,37 @@ ...@@ -231,20 +231,37 @@
= f.label :recaptcha_enabled do = f.label :recaptcha_enabled do
= f.check_box :recaptcha_enabled = f.check_box :recaptcha_enabled
Enable reCAPTCHA Enable reCAPTCHA
%span.help-block#recaptcha_help_block Helps preventing bots from creating accounts %span.help-block#recaptcha_help_block Helps prevent bots from creating accounts
.form-group .form-group
= f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2' = f.label :recaptcha_site_key, 'reCAPTCHA Site Key', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.text_field :recaptcha_site_key, class: 'form-control' = f.text_field :recaptcha_site_key, class: 'form-control'
.help-block .help-block
Generate site and private keys here: Generate site and private keys at
%a{ href: 'http://www.google.com/recaptcha', target: '_blank'} http://www.google.com/recaptcha %a{ href: 'http://www.google.com/recaptcha', target: 'blank'} http://www.google.com/recaptcha
.form-group .form-group
= f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2' = f.label :recaptcha_private_key, 'reCAPTCHA Private Key', class: 'control-label col-sm-2'
.col-sm-10 .col-sm-10
= f.text_field :recaptcha_private_key, class: 'form-control' = f.text_field :recaptcha_private_key, class: 'form-control'
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
= f.label :akismet_enabled do
= f.check_box :akismet_enabled
Enable Akismet
%span.help-block#akismet_help_block Helps prevent bots from creating issues
.form-group
= f.label :akismet_api_key, 'Akismet API Key', class: 'control-label col-sm-2'
.col-sm-10
= f.text_field :akismet_api_key, class: 'form-control'
.help-block
Generate API key at
%a{ href: 'http://www.akismet.com', target: 'blank'} http://www.akismet.com
%fieldset %fieldset
%legend Error Reporting and Logging %legend Error Reporting and Logging
%p %p
......
...@@ -34,4 +34,4 @@ ...@@ -34,4 +34,4 @@
= link_to icon('pencil-square-o'), edit_admin_broadcast_message_path(message), title: 'Edit', class: 'btn btn-xs' = link_to icon('pencil-square-o'), edit_admin_broadcast_message_path(message), title: 'Edit', class: 'btn btn-xs'
= link_to icon('times'), admin_broadcast_message_path(message), method: :delete, remote: true, title: 'Remove', class: 'js-remove-tr btn btn-xs btn-danger' = link_to icon('times'), admin_broadcast_message_path(message), method: :delete, remote: true, title: 'Remove', class: 'js-remove-tr btn btn-xs btn-danger'
= paginate @broadcast_messages = paginate @broadcast_messages, theme: 'gitlab'
.project-issuable-filter .top-area
.controls
.pull-left.hidden-xs
- if @all_builds.running_or_pending.any?
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
%ul.nav-links %ul.nav-links
%li{class: ('active' if @scope.nil?)} %li{class: ('active' if @scope.nil?)}
= link_to admin_builds_path do = link_to admin_builds_path do
...@@ -20,7 +15,11 @@ ...@@ -20,7 +15,11 @@
Finished Finished
%span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id)) %span.badge.js-running-count= number_with_delimiter(@all_builds.finished.count(:id))
.gray-content-block .nav-controls
- if @all_builds.running_or_pending.any?
= link_to 'Cancel all', cancel_all_admin_builds_path, data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
.gray-content-block.second-block
#{(@scope || 'running').capitalize} builds #{(@scope || 'running').capitalize} builds
%ul.content-list %ul.content-list
......
...@@ -92,6 +92,11 @@ ...@@ -92,6 +92,11 @@
Rails Rails
%span.pull-right %span.pull-right
#{Rails::VERSION::STRING} #{Rails::VERSION::STRING}
%p
= Gitlab::Database.adapter_name
%span.pull-right
= Gitlab::Database.version
%hr %hr
.row .row
.col-sm-4 .col-sm-4
......
- user = spam_log.user
%tr
%td
= time_ago_with_tooltip(spam_log.created_at)
%td
- if user
= link_to user.name, [:admin, user]
.light.small
Joined #{time_ago_with_tooltip(user.created_at)}
- else
(removed)
%td
= spam_log.source_ip
%td
= spam_log.via_api? ? 'Y' : 'N'
%td
= spam_log.noteable_type
%td
= spam_log.title
%td
= truncate(spam_log.description, length: 100)
%td
- if user
= link_to 'Remove user', admin_spam_log_path(spam_log, remove_user: true),
data: { confirm: "USER #{user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-xs btn-remove"
%td
- if user && !user.blocked?
= link_to 'Block user', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs"
- else
.btn.btn-xs.disabled
Already Blocked
= link_to 'Remove log', [:admin, spam_log], remote: true, method: :delete, class: "btn btn-xs btn-close js-remove-tr"
- page_title "Spam Logs"
%h3.page-title Spam Logs
%hr
- if @spam_logs.present?
.table-holder
%table.table
%thead
%tr
%th Date
%th User
%th Source IP
%th API?
%th Type
%th Title
%th Description
%th Primary Action
%th
= render @spam_logs
= paginate @spam_logs
- else
%h4 There are no Spam Logs
%ul.nav-links .top-area
%ul.nav-links
= nav_link(page: dashboard_groups_path) do = nav_link(page: dashboard_groups_path) do
= link_to dashboard_groups_path, title: 'Your groups', data: {placement: 'right'} do = link_to dashboard_groups_path, title: 'Your groups' do
Your Groups Your Groups
= nav_link(page: explore_groups_path) do = nav_link(page: explore_groups_path) do
= link_to explore_groups_path, title: 'Explore groups', data: {placement: 'bottom'} do = link_to explore_groups_path, title: 'Explore groups' do
Explore Groups Explore Groups
- if current_user.can_create_group?
.nav-controls
= link_to new_group_path, class: "btn btn-new" do
= icon('plus')
New Group
...@@ -8,13 +8,15 @@ ...@@ -8,13 +8,15 @@
= nav_link(page: starred_dashboard_projects_path) do = nav_link(page: starred_dashboard_projects_path) do
= link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do = link_to starred_dashboard_projects_path, title: 'Starred Projects', data: {placement: 'right'} do
Starred Projects Starred Projects
= nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path], html_options: { class: 'hidden-xs' }) do = nav_link(page: [explore_root_path, trending_explore_projects_path, starred_explore_projects_path, explore_projects_path]) do
= link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do = link_to explore_root_path, title: 'Explore', data: {placement: 'right'} do
Explore Projects Explore Projects
.projects-search-form .nav-controls
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name...', class: 'projects-list-filter form-control hidden-xs', spellcheck: false = form_tag request.original_url, method: :get, class: 'project-filter-form', id: 'project-filter-form' do |f|
= search_field_tag :filter_projects, params[:filter_projects], placeholder: 'Filter by name...', class: 'project-filter-form-field form-control input-short', spellcheck: false, id: 'project-filter-form-field'
= render 'explore/projects/dropdown'
- if current_user.can_create_project? - if current_user.can_create_project?
= link_to new_project_path, class: 'btn btn-green' do = link_to new_project_path, class: 'btn btn-new' do
%i.fa.fa-plus = icon('plus')
New Project New Project
...@@ -2,15 +2,6 @@ ...@@ -2,15 +2,6 @@
- header_title "Groups", dashboard_groups_path - header_title "Groups", dashboard_groups_path
= render 'dashboard/groups_head' = render 'dashboard/groups_head'
.gray-content-block
- if current_user.can_create_group?
%span.pull-right.hidden-xs
= link_to new_group_path, class: "btn btn-new" do
%i.fa.fa-plus
New Group
.oneline
Group members have access to all group projects.
%ul.content-list %ul.content-list
- @group_members.each do |group_member| - @group_members.each do |group_member|
- group = group_member.group - group = group_member.group
......
...@@ -4,20 +4,15 @@ ...@@ -4,20 +4,15 @@
- if current_user - if current_user
= auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues") = auto_discovery_link_tag(:atom, issues_dashboard_url(format: :atom, private_token: current_user.private_token), title: "#{current_user.name} issues")
.project-issuable-filter .top-area
.controls = render 'shared/issuable/nav', type: :issues
.pull-left .nav-controls
- if current_user - if current_user
.hidden-xs.pull-left
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do = link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
%i.fa.fa-rss = icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
.gray-content-block.second-block
List all issues from all projects you have access to.
.prepend-top-default .prepend-top-default
= render 'shared/issues' = render 'shared/issues'
- page_title "Merge Requests" - page_title "Merge Requests"
- header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id) - header_title "Merge Requests", merge_requests_dashboard_path(assignee_id: current_user.id)
.project-issuable-filter .top-area
.controls = render 'shared/issuable/nav', type: :merge_requests
.nav-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request" = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
= render 'shared/issuable/filter', type: :merge_requests = render 'shared/issuable/filter', type: :merge_requests
.gray-content-block.second-block
List all merge requests from all projects you have access to.
.prepend-top-default .prepend-top-default
= render 'shared/merge_requests' = render 'shared/merge_requests'
- page_title "Milestones" - page_title "Milestones"
- header_title "Milestones", dashboard_milestones_path - header_title "Milestones", dashboard_milestones_path
.project-issuable-filter .top-area
.controls
= render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
= render 'shared/milestones_filter' = render 'shared/milestones_filter'
.gray-content-block .nav-controls
List all milestones from all projects you have access to. = render 'shared/new_project_item_select', path: 'milestones/new', label: "New Milestone", include_groups: true
.milestones .milestones
%ul.content-list %ul.content-list
......
.projects-list-holder .projects-list-holder
= render 'shared/projects/list', projects: @projects, ci: true = render 'shared/projects/list', projects: @projects, ci: true
:javascript
Dashboard.init()
...@@ -3,8 +3,8 @@ ...@@ -3,8 +3,8 @@
.event-item-timestamp .event-item-timestamp
#{time_ago_with_tooltip(event.created_at)} #{time_ago_with_tooltip(event.created_at)}
= cache [event, current_application_settings, "v2.1"] do = cache [event, current_application_settings, "v2.2"] do
= image_tag avatar_icon(event.author_email, 46), class: "avatar s46", alt:'' = image_tag avatar_icon(event.author_email, 40), class: "avatar s40", alt:''
- if event.created_project? - if event.created_project?
= render "events/event/created_project", event: event = render "events/event/created_project", event: event
- elsif event.push? - elsif event.push?
......
...@@ -3,19 +3,13 @@ ...@@ -3,19 +3,13 @@
%span.light %span.light
- if @sort.present? - if @sort.present?
= sort_options_hash[@sort] = sort_options_hash[@sort]
- elsif current_page?(trending_explore_projects_path) || current_page?(explore_root_path)
Trending projects
- elsif current_page?(starred_explore_projects_path)
Most stars
- else - else
= sort_title_recently_created = sort_title_recently_updated
%b.caret %b.caret
%ul.dropdown-menu %ul.dropdown-menu
%li %li
= link_to trending_explore_projects_path do = link_to explore_projects_filter_path(sort: sort_value_name) do
Trending projects = sort_title_name
= link_to starred_explore_projects_path do
Most stars
= link_to explore_projects_filter_path(sort: sort_value_recently_created) do = link_to explore_projects_filter_path(sort: sort_value_recently_created) do
= sort_title_recently_created = sort_title_recently_created
= link_to explore_projects_filter_path(sort: sort_value_oldest_created) do = link_to explore_projects_filter_path(sort: sort_value_oldest_created) do
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
= form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f| = form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f|
.form-group .form-group
= search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search", spellcheck: false = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input", id: "projects_search", spellcheck: false
= hidden_field_tag :sort, @sort
.form-group .form-group
= button_tag 'Search', class: "btn" = button_tag 'Search', class: "btn"
...@@ -46,4 +47,3 @@ ...@@ -46,4 +47,3 @@
= link_to explore_projects_filter_path(tag: tag.name) do = link_to explore_projects_filter_path(tag: tag.name) do
%i.fa.fa-tag %i.fa.fa-tag
= tag.name = tag.name
= render 'explore/projects/dropdown'
%ul.nav-links
= nav_link(page: [trending_explore_projects_path, explore_root_path]) do
= link_to trending_explore_projects_path do
Trending
= nav_link(page: starred_explore_projects_path) do
= link_to starred_explore_projects_path do
Most stars
= nav_link(page: explore_projects_path) do
= link_to explore_projects_path do
All
...@@ -6,7 +6,10 @@ ...@@ -6,7 +6,10 @@
- else - else
= render 'explore/head' = render 'explore/head'
.gray-content-block.clearfix.second-block .top-area
= render 'explore/projects/nav'
.gray-content-block.second-block.clearfix
= render 'filter' = render 'filter'
= render 'projects', projects: @projects = render 'projects', projects: @projects
= paginate @projects, theme: "gitlab"
...@@ -6,12 +6,5 @@ ...@@ -6,12 +6,5 @@
- else - else
= render 'explore/head' = render 'explore/head'
.explore-trending-block = render 'explore/projects/nav'
.gray-content-block.second-block = render 'projects', projects: @projects
.pull-right
= render 'explore/projects/dropdown'
.oneline
%i.fa.fa-star
See most starred projects
= render 'projects', projects: @starred_projects
= paginate @starred_projects, theme: 'gitlab'
...@@ -6,11 +6,5 @@ ...@@ -6,11 +6,5 @@
- else - else
= render 'explore/head' = render 'explore/head'
.explore-trending-block = render 'explore/projects/nav'
.gray-content-block.second-block = render 'projects', projects: @projects
.pull-right
= render 'explore/projects/dropdown'
.oneline
%i.fa.fa-comments-o
See most discussed projects for last month
= render 'projects', projects: @trending_projects
.projects-list-holder .projects-list-holder.prepend-top-default
.projects-search-form
.input-group .input-group
= search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control', spellcheck: false
- if can? current_user, :create_projects, @group - if can? current_user, :create_projects, @group
%span.input-group-btn %span.input-group-btn
= link_to new_project_path(namespace_id: @group.id), class: 'btn btn-green' do = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-new' do
%i.fa.fa-plus = icon('plus')
New Project New Project
= render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false, skip_namespace: true = render 'shared/projects/list', projects: @projects, projects_limit: 20, stars: false, skip_namespace: true
...@@ -4,17 +4,15 @@ ...@@ -4,17 +4,15 @@
- if current_user - if current_user
= auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues") = auto_discovery_link_tag(:atom, issues_group_url(@group, format: :atom, private_token: current_user.private_token), title: "#{@group.name} issues")
.project-issuable-filter .top-area
.controls = render 'shared/issuable/nav', type: :issues
.pull-left .nav-controls
- if current_user - if current_user
.hidden-xs.pull-left
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do = link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
%i.fa.fa-rss = icon('rss')
= render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue" = render 'shared/new_project_item_select', path: 'issues/new', label: "New Issue"
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
.gray-content-block.second-block .gray-content-block.second-block
Only issues from Only issues from
......
- page_title "Merge Requests" - page_title "Merge Requests"
- header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group)) - header_title group_title(@group, "Merge Requests", merge_requests_group_path(@group))
.project-issuable-filter .top-area
.controls = render 'shared/issuable/nav', type: :merge_requests
.nav-controls
= render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request" = render 'shared/new_project_item_select', path: 'merge_requests/new', label: "New Merge Request"
= render 'shared/issuable/filter', type: :merge_requests = render 'shared/issuable/filter', type: :merge_requests
.gray-content-block.second-block .gray-content-block.second-block
Only merge requests from Only merge requests from
......
- page_title "Milestones" - page_title "Milestones"
- header_title group_title(@group, "Milestones", group_milestones_path(@group)) - header_title group_title(@group, "Milestones", group_milestones_path(@group))
.project-issuable-filter .top-area
.controls = render 'shared/milestones_filter'
.nav-controls
- if can?(current_user, :admin_milestones, @group) - if can?(current_user, :admin_milestones, @group)
.pull-right
%span.pull-right.hidden-xs
= link_to new_group_milestone_path(@group), class: "btn btn-new" do = link_to new_group_milestone_path(@group), class: "btn btn-new" do
= icon('plus') = icon('plus')
New Milestone New Milestone
= render 'shared/milestones_filter'
.gray-content-block .gray-content-block
Only milestones from Only milestones from
%strong #{@group.name} %strong #{@group.name}
......
...@@ -40,10 +40,6 @@ ...@@ -40,10 +40,6 @@
%td.shortcut %td.shortcut
.key enter .key enter
%td Open Selection %td Open Selection
%tr
%td.shortcut
.key t
%td Go to finding file
%tbody %tbody
%tr %tr
%th %th
...@@ -161,6 +157,10 @@ ...@@ -161,6 +157,10 @@
.key s .key s
%td %td
Go to snippets Go to snippets
%tr
%td.shortcut
.key t
%td Go to finding file
.col-lg-4 .col-lg-4
%table.shortcut-mappings %table.shortcut-mappings
%tbody{ class: 'hidden-shortcut network', style: 'display:none' } %tbody{ class: 'hidden-shortcut network', style: 'display:none' }
......
...@@ -138,8 +138,32 @@ ...@@ -138,8 +138,32 @@
%h2#navs Navigation %h2#navs Navigation
%h4
%code .top-area
%p Holder for top page navigation. Includes navigation, search field, sorting and button
.example
.top-area
%ul.nav-links
%li.active
%a Open
%li
%a Closed
.nav-controls
= text_field_tag 'sample', nil, class: 'form-control'
.dropdown
%button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'}
%span Sort by name
%b.caret
%ul.dropdown-menu
%li
%a Sort by date
= link_to 'New issue', '#', class: 'btn btn-new'
%h4 %h4
%code .nav-links %code .nav-links
%p Only nav links without button and search
.example .example
%ul.nav-links %ul.nav-links
%li.active %li.active
......
.page-with-sidebar{ class: page_sidebar_class } .page-with-sidebar{ class: "#{page_sidebar_class} #{page_gutter_class}" }
= render "layouts/broadcast" = render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo .header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do %a#logo
= brand_header_logo = brand_header_logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
.gitlab-text-container .gitlab-text-container
%h3 GitLab %h3 GitLab
......
.search .search
= form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f| = form_tag search_path, method: :get, class: 'navbar-form pull-left' do |f|
= search_field_tag "search", nil, placeholder: search_placeholder, class: "search-input form-control", spellcheck: false = search_field_tag "search", nil, placeholder: 'Search', class: "search-input form-control", spellcheck: false
= hidden_field_tag :group_id, @group.try(:id) = hidden_field_tag :group_id, @group.try(:id)
- if @project && @project.persisted? - if @project && @project.persisted?
= hidden_field_tag :project_id, @project.id = hidden_field_tag :project_id, @project.id
......
...@@ -2,8 +2,9 @@ ...@@ -2,8 +2,9 @@
= render "layouts/broadcast" = render "layouts/broadcast"
.sidebar-wrapper.nicescroll{ class: nav_sidebar_class } .sidebar-wrapper.nicescroll{ class: nav_sidebar_class }
.header-logo .header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do %a#logo
= brand_header_logo = brand_header_logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home' do
.gitlab-text-container .gitlab-text-container
%h3 GitLab %h3 GitLab
......
%ul.nav.nav-sidebar %ul.nav.nav-sidebar
= nav_link(controller: :dashboard, html_options: {class: 'home'}) do = nav_link(controller: :dashboard, html_options: {class: 'home'}) do
= link_to admin_root_path, title: "Stats" do = link_to admin_root_path, title: 'Overview' do
= icon('dashboard fw') = icon('dashboard fw')
%span %span
Overview Overview
...@@ -25,13 +25,13 @@ ...@@ -25,13 +25,13 @@
%span %span
Deploy Keys Deploy Keys
= nav_link path: ['runners#index', 'runners#show'] do = nav_link path: ['runners#index', 'runners#show'] do
= link_to admin_runners_path do = link_to admin_runners_path, title: 'Runners' do
= icon('cog fw') = icon('cog fw')
%span %span
Runners Runners
%span.count= number_with_delimiter(Ci::Runner.count(:all)) %span.count= number_with_delimiter(Ci::Runner.count(:all))
= nav_link path: 'builds#index' do = nav_link path: 'builds#index' do
= link_to admin_builds_path do = link_to admin_builds_path, title: 'Builds' do
= icon('link fw') = icon('link fw')
%span %span
Builds Builds
...@@ -92,6 +92,14 @@ ...@@ -92,6 +92,14 @@
Abuse Reports Abuse Reports
%span.count= number_with_delimiter(AbuseReport.count(:all)) %span.count= number_with_delimiter(AbuseReport.count(:all))
- if askimet_enabled?
= nav_link(controller: :spam_logs) do
= link_to admin_spam_logs_path, title: "Spam Logs" do
= icon('exclamation-triangle fw')
%span
Spam Logs
%span.count= number_with_delimiter(SpamLog.count(:all))
= nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do
= link_to admin_application_settings_path, title: 'Settings' do = link_to admin_application_settings_path, title: 'Settings' do
= icon('cogs fw') = icon('cogs fw')
......
.file-content.image_file .file-content.image_file
%img{ src: "data:#{blob.mime_type};base64,#{Base64.encode64(blob.data)}"} %img{ src: namespace_project_raw_path(@project.namespace, @project, @id)}
- blob.load_all_data!(@repository)
- if markup?(blob.name) - if markup?(blob.name)
.file-content.wiki .file-content.wiki
= render_markup(blob.name, blob.data) = render_markup(blob.name, blob.data)
......
- page_title "Builds" - page_title "Builds"
= render "header_title" = render "header_title"
.project-issuable-filter .top-area
.controls
- if can?(current_user, :manage_builds, @project)
.pull-left.hidden-xs
- if @all_builds.running_or_pending.any?
= link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project),
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
= link_to ci_lint_path, class: 'btn btn-default' do
= icon('wrench')
%span CI Lint
%ul.nav-links %ul.nav-links
%li{class: ('active' if @scope.nil?)} %li{class: ('active' if @scope.nil?)}
= link_to project_builds_path(@project) do = link_to project_builds_path(@project) do
...@@ -32,6 +21,16 @@ ...@@ -32,6 +21,16 @@
%span.badge.js-running-count %span.badge.js-running-count
= number_with_delimiter(@all_builds.finished.count(:id)) = number_with_delimiter(@all_builds.finished.count(:id))
.nav-controls
- if can?(current_user, :manage_builds, @project)
- if @all_builds.running_or_pending.any?
= link_to 'Cancel running', cancel_all_namespace_project_builds_path(@project.namespace, @project),
data: { confirm: 'Are you sure?' }, class: 'btn btn-danger', method: :post
= link_to ci_lint_path, class: 'btn btn-default' do
= icon('wrench')
%span CI Lint
.gray-content-block .gray-content-block
#{(@scope || 'running').capitalize} builds from this project #{(@scope || 'running').capitalize} builds from this project
......
.gray-content-block.top-block.clearfix.white.forks-top-block .top-area
.pull-left .nav-text
- public_count = @public_forks.size - public_count = @public_forks.size
- protected_count = @protected_forks.size - protected_count = @protected_forks.size
- full_count_title = "#{public_count} public and #{protected_count} private" - full_count_title = "#{public_count} public and #{protected_count} private"
== #{pluralize(@all_forks.size, 'fork')}: #{full_count_title} == #{pluralize(@all_forks.size, 'fork')}: #{full_count_title}
.pull-right .nav-controls
.projects-search-form.fork-search-form = search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter form-control input-short',
= search_field_tag :filter_projects, nil, placeholder: 'Search forks', class: 'projects-list-filter form-control',
spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' } spellcheck: false, data: { 'filter-selector' => 'span.namespace-name' }
.dropdown.inline.prepend-left-10 .dropdown
%button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'} %button.dropdown-toggle.btn.sort-forks{type: 'button', 'data-toggle' => 'dropdown'}
%span.light sort: %span.light sort:
- if @sort.present? - if @sort.present?
...@@ -30,13 +29,12 @@ ...@@ -30,13 +29,12 @@
= link_to page_filter_path(sort: sort_value_oldest_updated, without: excluded_filters) do = link_to page_filter_path(sort: sort_value_oldest_updated, without: excluded_filters) do
= sort_title_oldest_updated = sort_title_oldest_updated
.fork-link.inline
- if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 - 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: 'pull-right btn btn-new' do = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-new' do
= icon('code-fork fw') = icon('code-fork fw')
Fork Fork
- else - else
= link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'pull-right btn btn-new' do = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-new' do
= icon('code-fork fw') = icon('code-fork fw')
Fork Fork
......
...@@ -5,22 +5,19 @@ ...@@ -5,22 +5,19 @@
- if current_user - if current_user
= auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues") = auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues")
.project-issuable-filter .top-area
.controls = render 'shared/issuable/nav', type: :issues
.pull-left .nav-controls
- if current_user - if current_user
.hidden-xs.pull-left
= link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do
%i.fa.fa-rss = icon('rss')
= render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project) = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
- if can? current_user, :create_issue, @project - if can? current_user, :create_issue, @project
= link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(:id) }), class: "btn btn-new", title: "New Issue", id: "new_issue_link" do
%i.fa.fa-plus = icon('plus')
New Issue New Issue
= render 'shared/issuable/filter', type: :issues = render 'shared/issuable/filter', type: :issues
.issues-holder .issues-holder
= render "issues" = render "issues"
...@@ -15,11 +15,6 @@ ...@@ -15,11 +15,6 @@
opened by #{link_to_member(@project, @issue.author, size: 24)} opened by #{link_to_member(@project, @issue.author, size: 24)}
&middot; &middot;
= time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago') = time_ago_with_tooltip(@issue.created_at, placement: 'bottom', html_class: 'issue_created_ago')
- if @issue.updated_at != @issue.created_at
%span
&middot;
= icon('edit', title: 'edited')
= time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
.pull-right .pull-right
- if can?(current_user, :create_issue, @project) - if can?(current_user, :create_issue, @project)
...@@ -46,6 +41,10 @@ ...@@ -46,6 +41,10 @@
= markdown(@issue.description, cache_key: [@issue, "description"]) = markdown(@issue.description, cache_key: [@issue, "description"])
%textarea.hidden.js-task-list-field %textarea.hidden.js-task-list-field
= @issue.description = @issue.description
- if @issue.updated_at != @issue.created_at
%small
Edited
= time_ago_with_tooltip(@issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
.merge-requests .merge-requests
= render 'merge_requests' = render 'merge_requests'
...@@ -54,11 +53,8 @@ ...@@ -54,11 +53,8 @@
= render 'votes/votes_block', votable: @issue = render 'votes/votes_block', votable: @issue
.row .row
%section.col-md-9 %section.col-md-12
.issuable-discussion .issuable-discussion
= render 'projects/issues/discussion' = render 'projects/issues/discussion'
%aside.col-md-3 = render 'shared/issuable/sidebar', issuable: @issue
= render 'shared/issuable/sidebar', issuable: @issue \ No newline at end of file
= render 'shared/show_aside'
$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}"); $('aside.right-sidebar')[0].outerHTML = "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @issue)}";
$('.issuable-sidebar').parent().effect('highlight') $('aside.right-sidebar').effect('highlight');
new Issue(); new Issue();
\ No newline at end of file
- page_title "Labels" - page_title "Labels"
= render "header_title" = render "header_title"
.gray-content-block.top-block .top-area
.nav-text
Labels can be applied to issues and merge requests.
.nav-controls
- if can? current_user, :admin_label, @project - if can? current_user, :admin_label, @project
= link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do = link_to new_namespace_project_label_path(@project.namespace, @project), class: "btn btn-new" do
= icon('plus') = icon('plus')
New label New label
.oneline
Labels can be applied to issues and merge requests.
.labels .labels
- if @labels.present? - if @labels.present?
......
...@@ -53,7 +53,7 @@ ...@@ -53,7 +53,7 @@
- if merge_request.labels.any? - if merge_request.labels.any?
&nbsp; &nbsp;
- merge_request.labels.each do |label| - merge_request.labels.each do |label|
= link_to_label(label, project: merge_request.project) = link_to_label(label, project: merge_request.project, type: 'merge_request')
- if merge_request.tasks? - if merge_request.tasks?
&nbsp; &nbsp;
%span.task-status %span.task-status
......
...@@ -70,12 +70,9 @@ ...@@ -70,12 +70,9 @@
= render 'votes/votes_block', votable: @merge_request = render 'votes/votes_block', votable: @merge_request
.row .row
%section.col-md-9 %section.col-md-12
.issuable-discussion .issuable-discussion
= render "projects/merge_requests/discussion" = render "projects/merge_requests/discussion"
%aside.col-md-3
= render 'shared/issuable/sidebar', issuable: @merge_request
= render 'shared/show_aside'
#commits.commits.tab-pane #commits.commits.tab-pane
- # This tab is always loaded via AJAX - # This tab is always loaded via AJAX
...@@ -87,6 +84,8 @@ ...@@ -87,6 +84,8 @@
.mr-loading-status .mr-loading-status
= spinner = spinner
= render 'shared/issuable/sidebar', issuable: @merge_request
:javascript :javascript
var merge_request; var merge_request;
......
...@@ -2,16 +2,19 @@ ...@@ -2,16 +2,19 @@
= render "header_title" = render "header_title"
= render 'projects/last_push' = render 'projects/last_push'
.project-issuable-filter
.controls .top-area
= render 'shared/issuable/nav', type: :merge_requests
.nav-controls
= 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)
- merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project)) - merge_project = can?(current_user, :create_merge_request, @project) ? @project : (current_user && current_user.fork_of(@project))
- if merge_project - if merge_project
.pull-left.hidden-xs
= link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do = link_to new_namespace_project_merge_request_path(merge_project.namespace, merge_project), class: "btn btn-new", title: "New Merge Request" do
%i.fa.fa-plus = icon('plus')
New Merge Request New Merge Request
= render 'shared/issuable/filter', type: :merge_requests
= render 'shared/issuable/filter', type: :merge_requests
.merge-requests-holder .merge-requests-holder
= render 'merge_requests' = render 'merge_requests'
...@@ -10,3 +10,8 @@ ...@@ -10,3 +10,8 @@
= markdown(@merge_request.description, cache_key: [@merge_request, "description"]) = markdown(@merge_request.description, cache_key: [@merge_request, "description"])
%textarea.hidden.js-task-list-field %textarea.hidden.js-task-list-field
= @merge_request.description = @merge_request.description
- if @merge_request.updated_at != @merge_request.created_at
%small
Edited
= time_ago_with_tooltip(@merge_request.updated_at, placement: 'bottom')
...@@ -8,11 +8,6 @@ ...@@ -8,11 +8,6 @@
opened by #{link_to_member(@project, @merge_request.author, size: 24)} opened by #{link_to_member(@project, @merge_request.author, size: 24)}
&middot; &middot;
= time_ago_with_tooltip(@merge_request.created_at) = time_ago_with_tooltip(@merge_request.created_at)
- if @merge_request.updated_at != @merge_request.created_at
%span
&middot;
= icon('edit', title: 'edited')
= time_ago_with_tooltip(@merge_request.updated_at, placement: 'bottom')
.issue-btn-group.pull-right .issue-btn-group.pull-right
- if can?(current_user, :update_merge_request, @merge_request) - if can?(current_user, :update_merge_request, @merge_request)
......
$('.issuable-sidebar').html("#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}"); $('aside.right-sidebar')[0].outerHTML= "#{escape_javascript(render 'shared/issuable/sidebar', issuable: @merge_request)}";
$('.issuable-sidebar').parent().effect('highlight') $('aside.right-sidebar').effect('highlight')
merge_request = new MergeRequest(); merge_request = new MergeRequest();
...@@ -2,17 +2,14 @@ ...@@ -2,17 +2,14 @@
= render "header_title" = render "header_title"
.project-issuable-filter .top-area
.controls
- if can?(current_user, :admin_milestone, @project)
= link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do
%i.fa.fa-plus
New Milestone
= render 'shared/milestones_filter' = render 'shared/milestones_filter'
.gray-content-block .nav-controls
Milestone allows you to group issues and set due date for it - if can?(current_user, :admin_milestone, @project)
= link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "btn btn-new", title: "New Milestone" do
= icon('plus')
New Milestone
.milestones .milestones
%ul.content-list %ul.content-list
......
...@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear ...@@ -4,7 +4,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear
xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml" xml.link href: namespace_project_url(@project.namespace, @project, format: :atom, private_token: current_user.try(:private_token)), rel: "self", type: "application/atom+xml"
xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html" xml.link href: namespace_project_url(@project.namespace, @project), rel: "alternate", type: "text/html"
xml.id namespace_project_url(@project.namespace, @project) xml.id namespace_project_url(@project.namespace, @project)
xml.updated @events[0].updated_at.xmlschema if @events[0? xml.updated @events[0].updated_at.xmlschema if @events[0]
@events.each do |event| @events.each do |event|
event_to_atom(xml, event) event_to_atom(xml, event)
......
.project-issuable-filter .top-area
.controls
- if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
%i.fa.fa-plus
New Page
= render 'projects/wikis/new'
%ul.nav-links %ul.nav-links
= nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do
= link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home)
...@@ -17,3 +9,11 @@ ...@@ -17,3 +9,11 @@
= nav_link(path: 'wikis#git_access') do = nav_link(path: 'wikis#git_access') do
= link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do = link_to namespace_project_wikis_git_access_path(@project.namespace, @project) do
Git Access Git Access
.nav-controls
- if can?(current_user, :create_wiki, @project)
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
= icon('plus')
New Page
= render 'projects/wikis/new'
.file-content.code.js-syntax-highlight .file-content.code.js-syntax-highlight
.line-numbers .line-numbers
- if blob.data.present? - if blob.data.present?
- blob.data.lines.each_index do |index| - blob.data.each_line.each_with_index do |_, index|
- offset = defined?(first_line_number) ? first_line_number : 1 - offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset - i = index + offset
-# We're not using `link_to` because it is too slow once we get to thousands of lines. -# We're not using `link_to` because it is too slow once we get to thousands of lines.
......
.milestones-filters %ul.nav-links
%ul.nav-links
%li{class: ("active" if params[:state].blank? || params[:state] == 'opened')} %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')}
= link_to milestones_filter_path(state: 'opened') do = link_to milestones_filter_path(state: 'opened') do
Open Open
......
- if @projects.any? - if @projects.any?
.prepend-left-10.new-project-item-select-holder .prepend-left-10.project-item-select-holder
= project_select_tag :project_path, class: "new-project-item-select", data: { include_groups: local_assigns[:include_groups] } = project_select_tag :project_path, class: "project-item-select", data: { include_groups: local_assigns[:include_groups], order_by: 'last_activity_at' }
%a.btn.btn-new.new-project-item-select-button %a.btn.btn-new.new-project-item-select-button
= icon('plus') = icon('plus')
= local_assigns[:label] = local_assigns[:label]
...@@ -8,12 +8,12 @@ ...@@ -8,12 +8,12 @@
:javascript :javascript
$('.new-project-item-select-button').on('click', function() { $('.new-project-item-select-button').on('click', function() {
$('.new-project-item-select').select2('open'); $('.project-item-select').select2('open');
}); });
var relativePath = '#{local_assigns[:path]}'; var relativePath = '#{local_assigns[:path]}';
$('.new-project-item-select').on('click', function() { $('.project-item-select').on('click', function() {
window.location = $(this).val() + '/' + relativePath; window.location = $(this).val() + '/' + relativePath;
}); });
......
- group_member = local_assigns[:group_member] - group_member = local_assigns[:group_member]
%li - css_class = '' unless local_assigns[:css_class]
- css_class += " no-description" if group.description.blank?
%li.group-row{ class: css_class }
- if group_member - if group_member
.controls.hidden-xs .controls.hidden-xs
- if can?(current_user, :admin_group, group) - if can?(current_user, :admin_group, group)
...@@ -9,7 +12,16 @@ ...@@ -9,7 +12,16 @@
= link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Leave this group' do
%i.fa.fa-sign-out %i.fa.fa-sign-out
= image_tag group_icon(group), class: "avatar s46 hidden-xs" .stats
%span
= icon('home')
= number_with_delimiter(group.projects.count)
%span
= icon('users')
= number_with_delimiter(group.users.count)
= image_tag group_icon(group), class: "avatar s40 hidden-xs"
= link_to group, class: 'group-name' do = link_to group, class: 'group-name' do
%span.item-title= group.name %span.item-title= group.name
...@@ -17,5 +29,6 @@ ...@@ -17,5 +29,6 @@
as as
%span #{group_member.human_access} %span #{group_member.human_access}
%div.light - if group.description.present?
#{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")} .light
= markdown(group.description, pipeline: :description)
.issues-filters .issues-filters
.issues-state-filters .issues-details-filters.gray-content-block.second-block
%ul.nav-links
- if defined?(type) && type == :merge_requests
- page_context_word = 'merge requests'
- else
- page_context_word = 'issues'
%li{class: ("active" if params[:state] == 'opened')}
= link_to page_filter_path(state: 'opened'), title: "Filter by #{page_context_word} that are currently opened." do
#{state_filters_text_for(:opened, @project)}
- if defined?(type) && type == :merge_requests
%li{class: ("active" if params[:state] == 'merged')}
= link_to page_filter_path(state: 'merged'), title: 'Filter by merge requests that are currently merged.' do
#{state_filters_text_for(:merged, @project)}
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed'), title: 'Filter by merge requests that are currently closed and unmerged.' do
#{state_filters_text_for(:closed, @project)}
- else
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed'), title: 'Filter by issues that are currently closed.' do
#{state_filters_text_for(:closed, @project)}
%li{class: ("active" if params[:state] == 'all')}
= link_to page_filter_path(state: 'all'), title: "Show all #{page_context_word}." do
#{state_filters_text_for(:all, @project)}
.issues-details-filters.gray-content-block
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do
- if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project) - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
.check-all-holder .check-all-holder
......
%ul.nav-links.issues-state-filters
- if defined?(type) && type == :merge_requests
- page_context_word = 'merge requests'
- else
- page_context_word = 'issues'
%li{class: ("active" if params[:state] == 'opened')}
= link_to page_filter_path(state: 'opened'), title: "Filter by #{page_context_word} that are currently opened." do
#{state_filters_text_for(:opened, @project)}
- if defined?(type) && type == :merge_requests
%li{class: ("active" if params[:state] == 'merged')}
= link_to page_filter_path(state: 'merged'), title: 'Filter by merge requests that are currently merged.' do
#{state_filters_text_for(:merged, @project)}
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed'), title: 'Filter by merge requests that are currently closed and unmerged.' do
#{state_filters_text_for(:closed, @project)}
- else
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed'), title: 'Filter by issues that are currently closed.' do
#{state_filters_text_for(:closed, @project)}
%li{class: ("active" if params[:state] == 'all')}
= link_to page_filter_path(state: 'all'), title: "Show all #{page_context_word}." do
#{state_filters_text_for(:all, @project)}
.block.participants .block.participants
.sidebar-collapsed-icon
= icon('users')
%span
= participants.count
.title .title
= pluralize participants.count, "participant" = pluralize participants.count, "participant"
- participants.each do |participant| - participants.each do |participant|
......
= form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do = form_tag(path, method: :get, id: "issue_search_form", class: 'issue-search-form') do
.append-right-10.hidden-xs.hidden-sm = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by name ...', class: 'form-control issue_search search-text-input input-short', spellcheck: false }
= search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by name ...', class: 'form-control issue_search search-text-input', spellcheck: false }
= hidden_field_tag :state, params['state'] = hidden_field_tag :state, params['state']
= hidden_field_tag :scope, params['scope'] = hidden_field_tag :scope, params['scope']
= hidden_field_tag :assignee_id, params['assignee_id'] = hidden_field_tag :assignee_id, params['assignee_id']
......
.issuable-sidebar.issuable-affix %aside.right-sidebar{ class: sidebar_gutter_collapsed_class }
.issuable-sidebar
.block
%span.issuable-count.pull-left
= issuable.iid
of
= issuable_count(:all, @project)
%span.pull-right
%a.gutter-toggle{href: '#'}
- if sidebar_gutter_collapsed?
= icon('angle-double-left')
- else
= icon('angle-double-right')
.issuable-nav.pull-right.btn-group{role: 'group', "aria-label" => '...'}
- if has_prev_issuable?(@project, issuable.id)
= link_to 'Prev', issuable_link_prev(@project, issuable), class: 'btn btn-default prev-btn'
- else
%a.btn.btn-default.disabled{href: '#'}
Prev
- if has_next_issuable?(@project, issuable.id)
= link_to 'Next', issuable_link_next(@project, issuable), class: 'btn btn-default next-btn'
- else
%a.btn.btn-default.disabled{href: '#'}
Next
= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f| = form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
.block.assignee .block.assignee
.sidebar-collapsed-icon
- if issuable.assignee
= link_to_member_avatar(issuable.assignee, size: 24)
- else
= icon('user')
.title .title
%label %label
Assignee Assignee
...@@ -20,6 +49,13 @@ ...@@ -20,6 +49,13 @@
= 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, current_user: true, first_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, current_user: true, first_user: true)
.block.milestone .block.milestone
.sidebar-collapsed-icon
= icon('balance-scale')
%span
- if issuable.milestone
= issuable.milestone.title
- else
No
.title .title
%label %label
Milestone Milestone
...@@ -41,7 +77,11 @@ ...@@ -41,7 +77,11 @@
= f.submit class: 'btn hide' = f.submit class: 'btn hide'
- if issuable.project.labels.any? - if issuable.project.labels.any?
.block .block.labels
.sidebar-collapsed-icon
= icon('tags')
%span
= issuable.labels.count
.title .title
%label Labels %label Labels
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project) - if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
...@@ -50,7 +90,7 @@ ...@@ -50,7 +90,7 @@
.value.issuable-show-labels .value.issuable-show-labels
- if issuable.labels.any? - if issuable.labels.any?
- issuable.labels.each do |label| - issuable.labels.each do |label|
= link_to_label(label) = link_to_label(label, type: issuable.to_ability_name)
- else - else
.light None .light None
.selectbox .selectbox
...@@ -75,10 +115,12 @@ ...@@ -75,10 +115,12 @@
{ class: 'select2 js-select2', data: { placeholder: "Select weight" }} { class: 'select2 js-select2', data: { placeholder: "Select weight" }}
= render "shared/issuable/participants", participants: issuable.participants(current_user) = render "shared/issuable/participants", participants: issuable.participants(current_user)
%hr
- if current_user - if current_user
- subscribed = issuable.subscribed?(current_user) - subscribed = issuable.subscribed?(current_user)
.block.light .block.light
.sidebar-collapsed-icon
= icon('rss')
.title .title
%label.light Notifications %label.light Notifications
- subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed' - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
...@@ -91,7 +133,9 @@ ...@@ -91,7 +133,9 @@
You're receiving notifications because you're subscribed to this thread. You're receiving notifications because you're subscribed to this thread.
- project_ref = cross_project_reference(@project, issuable) - project_ref = cross_project_reference(@project, issuable)
.block .block.project-reference
.sidebar-collapsed-icon
= icon('clipboard')
.title .title
.cross-project-reference .cross-project-reference
%span %span
......
...@@ -8,18 +8,22 @@ ...@@ -8,18 +8,22 @@
- show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true - show_last_commit_as_description = false unless local_assigns[:show_last_commit_as_description] == true
%ul.projects-list %ul.projects-list
- if projects.any?
- projects.each_with_index do |project, i| - projects.each_with_index do |project, i|
- css_class = (i >= projects_limit) ? 'hide' : nil - css_class = (i >= projects_limit) ? 'hide' : nil
= render "shared/projects/project", project: project, skip_namespace: skip_namespace, = render "shared/projects/project", project: project, skip_namespace: skip_namespace,
avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar, avatar: avatar, stars: stars, css_class: css_class, ci: ci, use_creator_avatar: use_creator_avatar,
forks: forks, show_last_commit_as_description: show_last_commit_as_description forks: forks, show_last_commit_as_description: show_last_commit_as_description
- if projects.size > projects_limit - if projects.size > projects_limit && projects.kind_of?(Array)
%li.bottom.center %li.bottom.center
.light .light
#{projects_limit} of #{pluralize(projects.count, 'project')} displayed. #{projects_limit} of #{pluralize(projects.count, 'project')} displayed.
= link_to '#', class: 'js-expand' do = link_to '#', class: 'js-expand' do
Show all Show all
= paginate projects, theme: "gitlab" if !projects.kind_of?(Array)
- else
%h3 No projects found
:javascript :javascript
new ProjectsList(); new ProjectsList();
...@@ -16,9 +16,9 @@ ...@@ -16,9 +16,9 @@
- if avatar - if avatar
.dash-project-avatar .dash-project-avatar
- if use_creator_avatar - if use_creator_avatar
= image_tag avatar_icon(project.creator.email, 46), class: "avatar s46", alt:'' = image_tag avatar_icon(project.creator.email, 40), class: "avatar s40", alt:''
- else - else
= project_icon(project, alt: '', class: 'avatar project-avatar s46') = project_icon(project, alt: '', class: 'avatar project-avatar s40')
%span.project-full-name %span.project-full-name
%span.namespace-name %span.namespace-name
- if project.namespace && !skip_namespace - if project.namespace && !skip_namespace
...@@ -29,8 +29,8 @@ ...@@ -29,8 +29,8 @@
.project-controls .project-controls
- if ci_commit - if ci_commit
%span
= render_ci_status(ci_commit) = render_ci_status(ci_commit)
&nbsp;
- if forks - if forks
%span %span
= icon('code-fork') = icon('code-fork')
......
<%= ENV['RAILS_ENV'] %>: <%= ENV['RAILS_ENV'] %>:
## Connection information
# Please be aware that the DATABASE_URL environment variable will take
# precedence over the following 6 parameters. For more information, see
# doc/administration/environment_variables.md
adapter: <%= ENV['GITLAB_DATABASE_ADAPTER'] || 'postgresql' %> adapter: <%= ENV['GITLAB_DATABASE_ADAPTER'] || 'postgresql' %>
encoding: <%= ENV['GITLAB_DATABASE_ENCODING'] || 'unicode' %>
database: <%= ENV['GITLAB_DATABASE_DATABASE'] || "gitlab_#{ENV['RAILS_ENV']}" %> database: <%= ENV['GITLAB_DATABASE_DATABASE'] || "gitlab_#{ENV['RAILS_ENV']}" %>
pool: <%= ENV['GITLAB_DATABASE_POOL'] || '10' %>
username: <%= ENV['GITLAB_DATABASE_USERNAME'] || 'root' %> username: <%= ENV['GITLAB_DATABASE_USERNAME'] || 'root' %>
password: <%= ENV['GITLAB_DATABASE_PASSWORD'] || '' %> password: <%= ENV['GITLAB_DATABASE_PASSWORD'] || '' %>
host: <%= ENV['GITLAB_DATABASE_HOST'] || 'localhost' %> host: <%= ENV['GITLAB_DATABASE_HOST'] || 'localhost' %>
port: <%= ENV['GITLAB_DATABASE_PORT'] || '5432' %> port: <%= ENV['GITLAB_DATABASE_PORT'] || '5432' %>
## Behavior information
# The following parameters will be used even if you're using the DATABASE_URL
# environment variable.
encoding: <%= ENV['GITLAB_DATABASE_ENCODING'] || 'unicode' %>
pool: <%= ENV['GITLAB_DATABASE_POOL'] || '10' %>
...@@ -49,12 +49,14 @@ if Gitlab::Metrics.enabled? ...@@ -49,12 +49,14 @@ if Gitlab::Metrics.enabled?
config.instrument_instance_methods(Gitlab::Shell) config.instrument_instance_methods(Gitlab::Shell)
config.instrument_methods(Gitlab::Git) config.instrument_methods(Gitlab::Git)
config.instrument_instance_methods(Gitlab::Git::Repository)
Gitlab::Git.constants.each do |name| Gitlab::Git.constants.each do |name|
const = Gitlab::Git.const_get(name) const = Gitlab::Git.const_get(name)
config.instrument_methods(const) if const.is_a?(Module) next unless const.is_a?(Module)
config.instrument_methods(const)
config.instrument_instance_methods(const)
end end
Dir[Rails.root.join('app', 'finders', '*.rb')].each do |path| Dir[Rails.root.join('app', 'finders', '*.rb')].each do |path|
...@@ -62,6 +64,16 @@ if Gitlab::Metrics.enabled? ...@@ -62,6 +64,16 @@ if Gitlab::Metrics.enabled?
config.instrument_instance_methods(const) config.instrument_instance_methods(const)
end end
[
:Blame, :Branch, :BranchCollection, :Blob, :Commit, :Diff, :Repository,
:Tag, :TagCollection, :Tree
].each do |name|
const = Rugged.const_get(name)
config.instrument_methods(const)
config.instrument_instance_methods(const)
end
end end
GC::Profiler.enable GC::Profiler.enable
......
# New Relic configuration file
#
# This file is here to make sure the New Relic gem stays
# quiet by default.
#
# To enable and configure New Relic, please use
# environment variables, e.g. NEW_RELIC_ENABLED=true
production:
enabled: false
development:
enabled: false
test:
enabled: false
...@@ -217,6 +217,8 @@ Rails.application.routes.draw do ...@@ -217,6 +217,8 @@ Rails.application.routes.draw do
resources :git_hooks, only: [:index, :update] resources :git_hooks, only: [:index, :update]
resources :abuse_reports, only: [:index, :destroy] resources :abuse_reports, only: [:index, :destroy]
resources :spam_logs, only: [:index, :destroy]
resources :applications resources :applications
resources :groups, constraints: { id: /[^\/]+/ } do resources :groups, constraints: { id: /[^\/]+/ } do
......
class AddAkismetToApplicationSettings < ActiveRecord::Migration
def change
change_table :application_settings do |t|
t.boolean :akismet_enabled, default: false
t.string :akismet_api_key
end
end
end
class CreateSpamLogs < ActiveRecord::Migration
def change
create_table :spam_logs do |t|
t.integer :user_id
t.string :source_ip
t.string :user_agent
t.boolean :via_api
t.integer :project_id
t.string :noteable_type
t.string :title
t.text :description
t.timestamps null: false
end
end
end
class RemoveDotAtomPathEndingOfProjects < ActiveRecord::Migration
include Gitlab::ShellAdapter
class ProjectPath
attr_reader :old_path, :id, :namespace_path
def initialize(old_path, id, namespace_path, namespace_id)
@old_path = old_path
@id = id
@namespace_path = namespace_path
@namespace_id = namespace_id
end
def clean_path
@_clean_path ||= PathCleaner.clean(@old_path, @namespace_id)
end
end
class PathCleaner
def initialize(path, namespace_id)
@namespace_id = namespace_id
@path = path
end
def self.clean(*args)
new(*args).clean
end
def clean
path = cleaned_path
count = 0
while path_exists?(path)
path = "#{cleaned_path}#{count}"
count += 1
end
path
end
private
def cleaned_path
@_cleaned_path ||= @path.gsub(/\.atom\z/, '-atom')
end
def path_exists?(path)
Project.find_by_path_and_namespace_id(path, @namespace_id)
end
end
def projects_with_dot_atom
select_all("SELECT p.id, p.path, n.path as namespace_path, n.id as namespace_id FROM projects p inner join namespaces n on n.id = p.namespace_id WHERE lower(p.path) LIKE '%.atom'")
end
def up
projects_with_dot_atom.each do |project|
project_path = ProjectPath.new(project['path'], project['id'], project['namespace_path'], project['namespace_id'])
clean_path(project_path) if rename_project_repo(project_path)
end
end
private
def clean_path(project_path)
execute "UPDATE projects SET path = #{sanitize(project_path.clean_path)} WHERE id = #{project_path.id}"
end
def rename_project_repo(project_path)
old_path_with_namespace = File.join(project_path.namespace_path, project_path.old_path)
new_path_with_namespace = File.join(project_path.namespace_path, project_path.clean_path)
gitlab_shell.mv_repository("#{old_path_with_namespace}.wiki", "#{new_path_with_namespace}.wiki")
gitlab_shell.mv_repository(old_path_with_namespace, new_path_with_namespace)
rescue
false
end
def sanitize(value)
ActiveRecord::Base.connection.quote(value)
end
end
...@@ -77,6 +77,8 @@ ActiveRecord::Schema.define(version: 20160204190809) do ...@@ -77,6 +77,8 @@ ActiveRecord::Schema.define(version: 20160204190809) do
t.integer "metrics_sample_interval", default: 15 t.integer "metrics_sample_interval", default: 15
t.boolean "sentry_enabled", default: false t.boolean "sentry_enabled", default: false
t.string "sentry_dsn" t.string "sentry_dsn"
t.boolean "akismet_enabled", default: false
t.string "akismet_api_key"
end end
create_table "approvals", force: :cascade do |t| create_table "approvals", force: :cascade do |t|
...@@ -882,6 +884,19 @@ ActiveRecord::Schema.define(version: 20160204190809) do ...@@ -882,6 +884,19 @@ ActiveRecord::Schema.define(version: 20160204190809) do
add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree
add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree
create_table "spam_logs", force: :cascade do |t|
t.integer "user_id"
t.string "source_ip"
t.string "user_agent"
t.boolean "via_api"
t.integer "project_id"
t.string "noteable_type"
t.string "title"
t.text "description"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "subscriptions", force: :cascade do |t| create_table "subscriptions", force: :cascade do |t|
t.integer "user_id" t.integer "user_id"
t.integer "subscribable_id" t.integer "subscribable_id"
......
# Environment Variables # Environment Variables
## Introduction GitLab exposes certain environment variables which can be used to override
their defaults values.
Commonly people configure GitLab via the gitlab.rb configuration file in the Omnibus package. People usually configure GitLab via `/etc/gitlab/gitlab.rb` for Omnibus
installations, or `gitlab.yml` for installations from source.
But if you prefer to use environment variables we allow that too. Below you will find the supported environment variables which you can use to
override certain values.
## Supported environment variables ## Supported environment variables
Variable | Type | Explanation Variable | Type | Description
-------- | ---- | ----------- -------- | ---- | -----------
GITLAB_ROOT_PASSWORD | string | sets the password for the `root` user on installation `GITLAB_ROOT_PASSWORD` | string | Sets the password for the `root` user on installation
GITLAB_HOST | url | hostname of the GitLab server includes http or https `GITLAB_HOST` | string | The full URL of the GitLab server (including `http://` or `https://`)
RAILS_ENV | production / development / staging / test | Rails environment `RAILS_ENV` | string | The Rails environment; can be one of `production`, `development`, `staging` or `test`
DATABASE_URL | url | For example: postgresql://localhost/blog_development?pool=5 `DATABASE_URL` | string | The database URL; is of the form: `postgresql://localhost/blog_development`
GITLAB_EMAIL_FROM | email | Email address used in the "From" field in mails sent by GitLab `GITLAB_EMAIL_FROM` | string | The e-mail address used in the "From" field in e-mails sent by GitLab
GITLAB_EMAIL_DISPLAY_NAME | string | Name used in the "From" field in mails sent by GitLab `GITLAB_EMAIL_DISPLAY_NAME` | string | The name used in the "From" field in e-mails sent by GitLab
GITLAB_EMAIL_REPLY_TO | email | Email address used in the "Reply-To" field in mails sent by GitLab `GITLAB_EMAIL_REPLY_TO` | string | The e-mail address used in the "Reply-To" field in e-mails sent by GitLab
GITLAB_UNICORN_MEMORY_MIN | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer `GITLAB_UNICORN_MEMORY_MIN` | integer | The minimum memory threshold (in bytes) for the Unicorn worker killer
GITLAB_UNICORN_MEMORY_MAX | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer `GITLAB_UNICORN_MEMORY_MAX` | integer | The maximum memory threshold (in bytes) for the Unicorn worker killer
## Complete database variables ## Complete database variables
As explained in the [Heroku documentation](https://devcenter.heroku.com/articles/rails-database-connection-behavior) the DATABASE_URL doesn't let you set: The recommended way of specifying your database connection information is to set
the `DATABASE_URL` environment variable. This variable only holds connection
- adapter information (`adapter`, `database`, `username`, `password`, `host` and `port`),
- database but not behavior information (`encoding`, `pool`). If you don't want to use
- username `DATABASE_URL` and/or want to set database behavior information, you will have
- password to either:
- host
- port - copy our template file: `cp config/database.yml.env config/database.yml`, or
- set a value for some `GITLAB_DATABASE_XXX` variables
To do so please `cp config/database.yml.env config/database.yml` and use the following variables:
The list of `GITLAB_DATABASE_XXX` variables that you can set is:
Variable | Default
--- | --- Variable | Default value | Overridden by `DATABASE_URL`?
GITLAB_DATABASE_ADAPTER | postgresql -------- | ------------- | -----------------------------
GITLAB_DATABASE_ENCODING | unicode `GITLAB_DATABASE_ADAPTER` | `postgresql` (for MySQL use `mysql2`) | Yes
GITLAB_DATABASE_DATABASE | gitlab_#{ENV['RAILS_ENV'] `GITLAB_DATABASE_DATABASE` | `gitlab_#{ENV['RAILS_ENV']` | Yes
GITLAB_DATABASE_POOL | 10 `GITLAB_DATABASE_USERNAME` | `root` | Yes
GITLAB_DATABASE_USERNAME | root `GITLAB_DATABASE_PASSWORD` | None | Yes
GITLAB_DATABASE_PASSWORD | `GITLAB_DATABASE_HOST` | `localhost` | Yes
GITLAB_DATABASE_HOST | localhost `GITLAB_DATABASE_PORT` | `5432` | Yes
GITLAB_DATABASE_PORT | 5432 `GITLAB_DATABASE_ENCODING` | `unicode` | No
`GITLAB_DATABASE_POOL` | `10` | No
## Adding more variables ## Adding more variables
We welcome merge requests to make more settings configurable via variables. We welcome merge requests to make more settings configurable via variables.
Please make changes in the file config/initializers/1_settings.rb Please make changes in the `config/initializers/1_settings.rb` file and stick
Please stick to the naming scheme "GITLAB_#{name 1_settings.rb in upper case}". to the naming scheme `GITLAB_#{name in 1_settings.rb in upper case}`.
## Omnibus configuration ## Omnibus configuration
It's possible to preconfigure the GitLab image by adding the environment variable: `GITLAB_OMNIBUS_CONFIG` to docker run command. It's possible to preconfigure the GitLab docker image by adding the environment
variable `GITLAB_OMNIBUS_CONFIG` to the `docker run` command.
For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://doc.gitlab.com/omnibus/docker/#preconfigure-docker-container). For more information see the ['preconfigure-docker-container' section in the Omnibus documentation](http://doc.gitlab.com/omnibus/docker/#preconfigure-docker-container).
# Adding deploy keys to multiple projects # Adding deploy keys to multiple projects
If you want to easily add the same deploy key to multiple projects in the same group, this can be achieved quite easily with the API. If you want to easily add the same deploy key to multiple projects in the same
group, this can be achieved quite easily with the API.
First, find the ID of the projects you're interested in, by either listing all projects: First, find the ID of the projects you're interested in, by either listing all
projects:
``` ```
curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/projects curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/projects
``` ```
Or finding the id of a group and then listing all projects in that group: Or finding the ID of a group and then listing all projects in that group:
``` ```
curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/groups curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups
# For group 1234: # For group 1234:
curl --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/groups/1234 curl -H 'PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK' https://gitlab.example.com/api/v3/groups/1234
``` ```
With those IDs, add the same deploy key to all: With those IDs, add the same deploy key to all:
``` ```
for project_id in 321 456 987; do for project_id in 321 456 987; do
curl -X POST --data '{"title": "my key", "key": "ssh-rsa AAAA..."}' --header 'PRIVATE-TOKEN: abcdef' https://gitlab.com/api/v3/projects/${project_id}/keys curl -X POST -H "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" -H "Content-Type: application/json" \
--data '{"title": "my key", "key": "ssh-rsa AAAA..."}' https://gitlab.example.com/api/v3/projects/${project_id}/keys
done done
``` ```
# Session # Session
Login to get private token You can login with both GitLab and LDAP credentials in order to obtain the
private token.
``` ```
POST /session POST /session
``` ```
Parameters: | Attribute | Type | Required | Description |
| ---------- | ------- | -------- | -------- |
| `login` | string | yes | The username of the user|
| `email` | string | yes if login is not provided | The email of the user |
| `password` | string | yes | The password of the user |
- `login` (required) - The login of user ```bash
- `email` (required if login missing) - The email of user curl -X POST "https://gitlab.example.com/api/v3/session?login=john_smith&password=strongpassw0rd"
- `password` (required) - Valid password ```
**You can login with both GitLab and LDAP credentials now**
Example response:
```json ```json
{ {
"id": 1,
"username": "john_smith",
"email": "john@example.com",
"name": "John Smith", "name": "John Smith",
"private_token": "dd34asd13as", "username": "john_smith",
"blocked": false, "id": 32,
"created_at": "2012-05-23T08:00:58Z", "state": "active",
"avatar_url": null,
"created_at": "2015-01-29T21:07:19.440Z",
"is_admin": true,
"bio": null, "bio": null,
"skype": "", "skype": "",
"linkedin": "", "linkedin": "",
"twitter": "", "twitter": "",
"website_url": "", "website_url": "",
"dark_scheme": false, "email": "john@example.com",
"theme_id": 1, "theme_id": 1,
"is_admin": false, "color_scheme_id": 1,
"projects_limit": 10,
"current_sign_in_at": "2015-07-07T07:10:58.392Z",
"identities": [],
"can_create_group": true, "can_create_group": true,
"can_create_team": true, "can_create_project": true,
"can_create_project": true "two_factor_enabled": false,
"private_token": "9koXpg98eAheJpvBs5tK"
} }
``` ```
...@@ -18,7 +18,7 @@ GET /ci/projects ...@@ -18,7 +18,7 @@ GET /ci/projects
Returns: Returns:
```json ```json
[ [
{ {
"id" : 271, "id" : 271,
"name" : "gitlabhq", "name" : "gitlabhq",
......
...@@ -97,7 +97,7 @@ image: php:5.6 ...@@ -97,7 +97,7 @@ image: php:5.6
before_script: before_script:
# Install dependencies # Install dependencies
- ci/docker_install.sh > /dev/null - bash ci/docker_install.sh > /dev/null
test:app: test:app:
script: script:
...@@ -112,7 +112,7 @@ with a different docker image version and the runner will do the rest: ...@@ -112,7 +112,7 @@ with a different docker image version and the runner will do the rest:
```yaml ```yaml
before_script: before_script:
# Install dependencies # Install dependencies
- ci/docker_install.sh > /dev/null - bash ci/docker_install.sh > /dev/null
# We test PHP5.6 # We test PHP5.6
test:5.6: test:5.6:
......
...@@ -393,8 +393,12 @@ The above script will: ...@@ -393,8 +393,12 @@ The above script will:
### artifacts ### artifacts
_**Note:** Introduced in GitLab Runner v0.7.0. Also, the Windows shell executor _**Note:** Introduced in GitLab Runner v0.7.0 for non-Windows platforms._
does not currently support artifact uploads._
_**Note:** Limited Windows support was added in GitLab Runner v.1.0.0.
Currently not all executors are supported._
_**Note:** Build artifacts are only collected for successful builds._
`artifacts` is used to specify list of files and directories which should be `artifacts` is used to specify list of files and directories which should be
attached to build after success. Below are some examples. attached to build after success. Below are some examples.
......
...@@ -16,7 +16,7 @@ Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that ...@@ -16,7 +16,7 @@ Here, `%{issue_ref}` is a complex regular expression defined inside GitLab, that
For example: For example:
``` ```
git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#2). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23." git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes group/otherproject#22). This commit is also related to #17 and fixes #18, #19 and https://gitlab.example.com/group/otherproject/issues/23."
``` ```
will close `#18`, `#19`, `#20`, and `#21` in the project this commit is pushed to, as well as `#22` and `#23` in group/otherproject. `#17` won't be closed as it does not match the pattern. It also works with multiline commit messages. will close `#18`, `#19`, `#20`, and `#21` in the project this commit is pushed to, as well as `#22` and `#23` in group/otherproject. `#17` won't be closed as it does not match the pattern. It also works with multiline commit messages.
......
...@@ -120,6 +120,17 @@ Inside the document: ...@@ -120,6 +120,17 @@ Inside the document:
`http://doc.gitlab.com/ce/administration/restart_gitlab.html`. `http://doc.gitlab.com/ce/administration/restart_gitlab.html`.
Replace `reconfigure` with `restart` where appropriate. Replace `reconfigure` with `restart` where appropriate.
## Installation guide
- **Ruby:**
In [step 2 of the installation guide](../install/installation.md#2-ruby),
we install Ruby from source. Whenever there is a new version that needs to
be updated, remember to change it throughout the codeblock and also replace
the sha256sum (it can be found in the [downloads page][ruby-dl] of the Ruby
website).
[ruby-dl]: https://www.ruby-lang.org/en/downloads/ "Ruby download website"
## API ## API
Here is a list of must-have items. Use them in the exact order that appears Here is a list of must-have items. Use them in the exact order that appears
......
...@@ -124,7 +124,7 @@ Download Ruby and compile it: ...@@ -124,7 +124,7 @@ Download Ruby and compile it:
mkdir /tmp/ruby && cd /tmp/ruby mkdir /tmp/ruby && cd /tmp/ruby
curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.4.tar.gz curl -O --progress https://cache.ruby-lang.org/pub/ruby/2.2/ruby-2.2.4.tar.gz
echo 'e2e195a4a58133e3ad33b955c829bb536fa3c075 ruby-2.2.4.tar.gz' | shasum -c - && tar xzf ruby-2.2.4.tar.gz echo 'b6eff568b48e0fda76e5a36333175df049b204e91217aa32a65153cc0cdcb761 ruby-2.2.4.tar.gz' | sha256sum -c - && tar xzf ruby-2.2.4.tar.gz
cd ruby-2.2.4 cd ruby-2.2.4
./configure --disable-install-rdoc ./configure --disable-install-rdoc
make make
...@@ -267,6 +267,9 @@ sudo usermod -aG redis git ...@@ -267,6 +267,9 @@ sudo usermod -aG redis git
sudo chmod -R u+rwX tmp/pids/ sudo chmod -R u+rwX tmp/pids/
sudo chmod -R u+rwX tmp/sockets/ sudo chmod -R u+rwX tmp/sockets/
# Create the public/uploads/ directory
sudo -u git -H mkdir public/uploads/
# Make sure GitLab can write to the public/uploads/ directory # Make sure GitLab can write to the public/uploads/ directory
sudo chmod -R u+rwX public/uploads sudo chmod -R u+rwX public/uploads
...@@ -358,7 +361,7 @@ GitLab Shell is an SSH access and repository management software developed speci ...@@ -358,7 +361,7 @@ GitLab Shell is an SSH access and repository management software developed speci
cd /home/git cd /home/git
sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-workhorse.git
cd gitlab-workhorse cd gitlab-workhorse
sudo -u git -H git checkout 0.6.2 sudo -u git -H git checkout 0.6.3
sudo -u git -H make sudo -u git -H make
### Initialize Database and Activate Advanced Features ### Initialize Database and Activate Advanced Features
......
...@@ -66,7 +66,7 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim ...@@ -66,7 +66,7 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim
You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab! You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab!
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 advice.
- 1GB RAM + 1GB swap supports up to 100 users but it will be slow - 1GB RAM + 1GB swap supports up to 100 users but it will be slow
- **2GB RAM** is the **recommended** memory size and supports up to 100 users - **2GB RAM** is the **recommended** memory size and supports up to 100 users
- 4GB RAM supports up to 1,000 users - 4GB RAM supports up to 1,000 users
......
...@@ -17,6 +17,7 @@ See the documentation below for details on how to configure these services. ...@@ -17,6 +17,7 @@ See the documentation below for details on how to configure these services.
- [OAuth2 provider](oauth_provider.md) OAuth2 application creation - [OAuth2 provider](oauth_provider.md) OAuth2 application creation
- [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages - [Gmail actions buttons](gmail_action_buttons_for_gitlab.md) Adds GitLab actions to messages
- [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users - [reCAPTCHA](recaptcha.md) Configure GitLab to use Google reCAPTCHA for new users
- [Akismet](akismet.md) Configure Akismet to stop spam
GitLab Enterprise Edition contains [advanced Jenkins support][jenkins]. GitLab Enterprise Edition contains [advanced Jenkins support][jenkins].
......
# Akismet
GitLab leverages [Akismet](http://akismet.com) to protect against spam. Currently
GitLab uses Akismet to prevent users who are not members of a project from
creating spam via the GitLab API. Detected spam will be rejected, and
an entry in the "Spam Log" section in the Admin page will be created.
Privacy note: GitLab submits the user's IP and user agent to Akismet. Note that
adding a user to a project will disable the Akismet check and prevent this
from happening.
## Configuration
To use Akismet:
1. Go to the URL: https://akismet.com/account/
2. Sign-in or create a new account.
3. Click on "Show" to reveal the API key.
4. Go to Applications Settings on Admin Area (`admin/application_settings`)
5. Check the `Enable Akismet` checkbox
6. Fill in the API key from step 3.
7. Save the configuration.
![Screenshot of Akismet settings](img/akismet_settings.png)
## GitLab as OAuth2 authentication service provider # GitLab as OAuth2 authentication service provider
This document is about using GitLab as an OAuth authentication service provider to sign into other services. This document is about using GitLab as an OAuth authentication service provider
If you want to use other OAuth authentication service providers to sign into GitLab please see the [OAuth2 client documentation](../api/oauth2.md) to sign in to other services.
OAuth2 provides client applications a 'secure delegated access' to server resources on behalf of a resource owner. Or you can allow users to sign in to your application with their GitLab.com account. If you want to use other OAuth authentication service providers to sign in to
In fact OAuth allows to issue access token to third-party clients by an authorization server, GitLab, please see the [OAuth2 client documentation](../api/oauth2.md).
with the approval of the resource owner, or end-user.
Mostly, OAuth2 is using for SSO (Single sign-on). But you can find a lot of different usages for this functionality.
For example, our feature 'GitLab Importer' is using OAuth protocol to give an access to repositories without sharing user credentials to GitLab.com account.
Also GitLab.com application can be used for authentication to your GitLab instance if needed [GitLab OmniAuth](gitlab.md).
GitLab has two ways to add new OAuth2 application to an instance, you can add application as regular user and through admin area. So GitLab actually can have an instance-wide and a user-wide applications. There is no defferences between them except the different permission levels. ## Introduction to OAuth
### Adding application through profile [OAuth] provides to client applications a 'secure delegated access' to server
Go to your profile section 'Application' and press button 'New Application' resources on behalf of a resource owner. In fact, OAuth allows an authorization
server to issue access tokens to third-party clients with the approval of the
resource owner, or the end-user.
![applications](img/oauth_provider_user_wide_applications.png) OAuth is mostly used as a Single Sign-On service (SSO), but you can find a
lot of different uses for this functionality. For example, you can allow users
to sign in to your application with their GitLab.com account, or GitLab.com
can be used for authentication to your GitLab instance
(see [GitLab OmniAuth](gitlab.md)).
After this you will see application form, where "Name" is arbitrary name, "Redirect URI" is URL in your app where users will be sent after authorization on GitLab.com. The 'GitLab Importer' feature is also using the OAuth protocol to give access
to repositories without sharing user credentials to your GitLab.com account.
![application_form](img/oauth_provider_application_form.png) ---
### Authorized application GitLab supports two ways of adding a new OAuth2 application to an instance. You
Every application you authorized will be shown in your "Authorized application" sections. can either add an application as a regular user or add it in the admin area.
What this means is that GitLab can actually have instance-wide and a user-wide
applications. There is no difference between them except for the different
permission levels they are set (user/admin).
![authorized_application](img/oauth_provider_authorized_application.png) ## Adding an application through the profile
At any time you can revoke access just clicking button "Revoke" In order to add a new application via your profile, navigate to
**Profile Settings > Applications** and select **New Application**.
### OAuth applications in admin area ![New OAuth application](img/oauth_provider_user_wide_applications.png)
If you want to create application that does not belong to certain user you can create it from admin area ---
![admin_application](img/oauth_provider_admin_application.png) In the application form, enter a **Name** (arbitrary), and make sure to set up
correctly the **Redirect URI** which is the URL where users will be sent after
they authorize with GitLab.
![New OAuth application form](img/oauth_provider_application_form.png)
---
When you hit **Submit** you will be provided with the application ID and
the application secret which you can then use with your application that
connects to GitLab.
![OAuth application ID and secret](img/oauth_provider_application_id_secret.png)
---
## OAuth applications in the admin area
To create an application that does not belong to a certain user, you can create
it from the admin area.
![OAuth admin_applications](img/oauth_provider_admin_application.png)
---
## Authorized applications
Every application you authorized to use your GitLab credentials will be shown
in the **Authorized applications** section under **Profile Settings > Applications**.
![Authorized_applications](img/oauth_provider_authorized_application.png)
---
As you can see, the default scope `api` is used, which is the only scope that
GitLab supports so far. At any time you can revoke any access by just clicking
**Revoke**.
[oauth]: http://oauth.net/2/ "OAuth website"
...@@ -424,24 +424,24 @@ will point the link to `wikis/style` when the link is inside of a wiki markdown ...@@ -424,24 +424,24 @@ will point the link to `wikis/style` when the link is inside of a wiki markdown
Here's our logo (hover to see the title text): Here's our logo (hover to see the title text):
Inline-style: Inline-style:
![alt text](assets/logo-white.png) ![alt text](assets/logo.svg)
Reference-style: Reference-style:
![alt text1][logo] ![alt text1][logo]
[logo]: assets/logo-white.png [logo]: assets/logo.svg
Here's our logo: Here's our logo:
Inline-style: Inline-style:
![alt text](/assets/logo-white.png) ![alt text](/assets/logo.svg)
Reference-style: Reference-style:
![alt text][logo] ![alt text][logo]
[logo]: /assets/logo-white.png [logo]: /assets/logo.svg
## Blockquotes ## Blockquotes
......
# GitLab JIRA integration # GitLab JIRA integration
_**Note:**
Full JIRA integration was previously exclusive to GitLab Enterprise Edition.
With [GitLab 8.3 forward][8_3_post], this feature in now [backported][jira-ce]
to GitLab Community Edition as well._
---
GitLab can be configured to interact with [JIRA Core] either using an GitLab can be configured to interact with [JIRA Core] either using an
on-premises instance or the SaaS solution that Atlassian offers. Configuration on-premises instance or the SaaS solution that Atlassian offers. Configuration
happens via username and password on a per-project basis. Connecting to a JIRA happens via username and password on a per-project basis. Connecting to a JIRA
...@@ -210,3 +217,5 @@ You can see from the above image that there are four references to GitLab: ...@@ -210,3 +217,5 @@ You can see from the above image that there are four references to GitLab:
[services-templates]: ../project_services/services_templates.md "Services templates documentation" [services-templates]: ../project_services/services_templates.md "Services templates documentation"
[JIRA Core]: https://www.atlassian.com/software/jira/core "The JIRA Core website" [JIRA Core]: https://www.atlassian.com/software/jira/core "The JIRA Core website"
[jira-ce]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/2146 "MR - Backport JIRA service"
[8_3_post]: https://about.gitlab.com/2015/12/22/gitlab-8-3-released/ "GitLab 8.3 release post"
...@@ -5,6 +5,12 @@ ...@@ -5,6 +5,12 @@
An SSH key allows you to establish a secure connection between your An SSH key allows you to establish a secure connection between your
computer and GitLab. Before generating an SSH key in your shell, check if your system computer and GitLab. Before generating an SSH key in your shell, check if your system
already has one by running the following command: already has one by running the following command:
**Windows Command Line:**
```bash
type %userprofile%\.ssh\id_rsa.pub
```
**GNU/Linux/Mac/PowerShell:**
```bash ```bash
cat ~/.ssh/id_rsa.pub cat ~/.ssh/id_rsa.pub
``` ```
...@@ -25,6 +31,12 @@ press enter to use the default. If you use a different name, the key will not ...@@ -25,6 +31,12 @@ press enter to use the default. If you use a different name, the key will not
be used automatically. be used automatically.
Use the command below to show your public key: Use the command below to show your public key:
**Windows Command Line:**
```bash
type %userprofile%\.ssh\id_rsa.pub
```
**GNU/Linux/Mac/PowerShell:**
```bash ```bash
cat ~/.ssh/id_rsa.pub cat ~/.ssh/id_rsa.pub
``` ```
...@@ -36,9 +48,14 @@ with your username and host. ...@@ -36,9 +48,14 @@ with your username and host.
To copy your public key to the clipboard, use the code below. Depending on your To copy your public key to the clipboard, use the code below. Depending on your
OS you'll need to use a different command: OS you'll need to use a different command:
**Windows:** **Windows Command Line:**
```bash
type %userprofile%\.ssh\id_rsa.pub | clip
```
**Windows PowerShell:**
```bash ```bash
clip < ~/.ssh/id_rsa.pub cat ~/.ssh/id_rsa.pub | clip
``` ```
**Mac:** **Mac:**
......
...@@ -14,6 +14,12 @@ possible to edit the label text and color. The characters `?`, `&` and `,` are ...@@ -14,6 +14,12 @@ possible to edit the label text and color. The characters `?`, `&` and `,` are
no longer allowed however so those will be removed from your tags during the no longer allowed however so those will be removed from your tags during the
database migrations for GitLab 7.2. database migrations for GitLab 7.2.
## Stash changes
If you [deleted the vendors folder during your original installation](https://github.com/gitlabhq/gitlabhq/issues/4883#issuecomment-31108431), [you will get an error](https://gitlab.com/gitlab-org/gitlab-ce/issues/1494) when you attempt to rebuild the assets in step 7. To avoid this, stash the changes in your GitLab working copy before starting:
git stash
## 0. Stop server ## 0. Stop server
sudo service gitlab stop sudo service gitlab stop
......
Feature: Admin spam logs
Background:
Given I sign in as an admin
And spam logs exist
Scenario: Browse spam logs
When I visit spam logs page
Then I should see list of spam logs
class Spinach::Features::AdminSpamLogs < Spinach::FeatureSteps
include SharedAuthentication
include SharedPaths
include SharedAdmin
step 'I should see list of spam logs' do
expect(page).to have_content('Spam Logs')
expect(page).to have_content spam_log.source_ip
expect(page).to have_content spam_log.noteable_type
expect(page).to have_content 'N'
expect(page).to have_content spam_log.title
expect(page).to have_content truncate(spam_log.description)
expect(page).to have_link('Remove user')
expect(page).to have_link('Block user')
end
step 'spam logs exist' do
create(:spam_log)
end
def spam_log
@spam_log ||= SpamLog.first
end
def truncate(description)
"#{spam_log.description[0...97]}..."
end
end
...@@ -13,7 +13,7 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps ...@@ -13,7 +13,7 @@ class Spinach::Features::ProjectBuildsSummary < Spinach::FeatureSteps
end end
step 'I see button to CI Lint' do step 'I see button to CI Lint' do
page.within('.controls') do page.within('.nav-controls') do
ci_lint_tool_link = page.find_link('CI Lint') ci_lint_tool_link = page.find_link('CI Lint')
expect(ci_lint_tool_link[:href]).to eq ci_lint_path expect(ci_lint_tool_link[:href]).to eq ci_lint_path
end end
......
...@@ -52,7 +52,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps ...@@ -52,7 +52,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end end
step 'I should see raw file content' do step 'I should see raw file content' do
expect(source).to eq sample_blob.data expect(source).to eq '' # Body is filled in by gitlab-workhorse
end end
step 'I click button "Edit"' do step 'I click button "Edit"' do
......
...@@ -211,6 +211,10 @@ module SharedPaths ...@@ -211,6 +211,10 @@ module SharedPaths
visit admin_application_settings_path visit admin_application_settings_path
end end
step 'I visit spam logs page' do
visit admin_spam_logs_path
end
step 'I visit applications page' do step 'I visit applications page' do
visit admin_applications_path visit admin_applications_path
end end
......
...@@ -58,9 +58,11 @@ module API ...@@ -58,9 +58,11 @@ module API
commit = user_project.commit(ref) commit = user_project.commit(ref)
not_found! 'Commit' unless commit not_found! 'Commit' unless commit
blob = user_project.repository.blob_at(commit.sha, file_path) repo = user_project.repository
blob = repo.blob_at(commit.sha, file_path)
if blob if blob
blob.load_all_data!(repo)
status(200) status(200)
{ {
...@@ -72,7 +74,7 @@ module API ...@@ -72,7 +74,7 @@ module API
ref: ref, ref: ref,
blob_id: blob.id, blob_id: blob.id,
commit_id: commit.id, commit_id: commit.id,
last_commit_id: user_project.repository.last_commit_for_path(commit.sha, file_path).id last_commit_id: repo.last_commit_for_path(commit.sha, file_path).id
} }
else else
not_found! 'File' not_found! 'File'
......
...@@ -30,7 +30,7 @@ module API ...@@ -30,7 +30,7 @@ module API
end end
def sudo_identifier() def sudo_identifier()
identifier ||= params[SUDO_PARAM] ||= env[SUDO_HEADER] identifier ||= params[SUDO_PARAM] || env[SUDO_HEADER]
# Regex for integers # Regex for integers
if !!(identifier =~ /^[0-9]+$/) if !!(identifier =~ /^[0-9]+$/)
...@@ -344,12 +344,22 @@ module API ...@@ -344,12 +344,22 @@ module API
def pagination_links(paginated_data) def pagination_links(paginated_data)
request_url = request.url.split('?').first request_url = request.url.split('?').first
request_params = params.clone
request_params[:per_page] = paginated_data.limit_value
links = [] links = []
links << %(<#{request_url}?page=#{paginated_data.current_page - 1}&per_page=#{paginated_data.limit_value}>; rel="prev") unless paginated_data.first_page?
links << %(<#{request_url}?page=#{paginated_data.current_page + 1}&per_page=#{paginated_data.limit_value}>; rel="next") unless paginated_data.last_page? request_params[:page] = paginated_data.current_page - 1
links << %(<#{request_url}?page=1&per_page=#{paginated_data.limit_value}>; rel="first") links << %(<#{request_url}?#{request_params.to_query}>; rel="prev") unless paginated_data.first_page?
links << %(<#{request_url}?page=#{paginated_data.total_pages}&per_page=#{paginated_data.limit_value}>; rel="last")
request_params[:page] = paginated_data.current_page + 1
links << %(<#{request_url}?#{request_params.to_query}>; rel="next") unless paginated_data.last_page?
request_params[:page] = 1
links << %(<#{request_url}?#{request_params.to_query}>; rel="first")
request_params[:page] = paginated_data.total_pages
links << %(<#{request_url}?#{request_params.to_query}>; rel="last")
links.join(', ') links.join(', ')
end end
......
...@@ -3,6 +3,8 @@ module API ...@@ -3,6 +3,8 @@ module API
class Issues < Grape::API class Issues < Grape::API
before { authenticate! } before { authenticate! }
helpers ::Gitlab::AkismetHelper
helpers do helpers do
def filter_issues_state(issues, state) def filter_issues_state(issues, state)
case state case state
...@@ -19,6 +21,17 @@ module API ...@@ -19,6 +21,17 @@ module API
def filter_issues_milestone(issues, milestone) def filter_issues_milestone(issues, milestone)
issues.includes(:milestone).where('milestones.title' => milestone) issues.includes(:milestone).where('milestones.title' => milestone)
end end
def create_spam_log(project, current_user, attrs)
params = attrs.merge({
source_ip: env['REMOTE_ADDR'],
user_agent: env['HTTP_USER_AGENT'],
noteable_type: 'Issue',
via_api: true
})
::CreateSpamLogService.new(project, current_user, params).execute
end
end end
resource :issues do resource :issues do
...@@ -114,7 +127,15 @@ module API ...@@ -114,7 +127,15 @@ module API
render_api_error!({ labels: errors }, 400) render_api_error!({ labels: errors }, 400)
end end
issue = ::Issues::CreateService.new(user_project, current_user, attrs).execute project = user_project
text = [attrs[:title], attrs[:description]].reject(&:blank?).join("\n")
if check_for_spam?(project, current_user) && is_spam?(env, current_user, text)
create_spam_log(project, current_user, attrs)
render_api_error!({ error: 'Spam detected' }, 400)
end
issue = ::Issues::CreateService.new(project, current_user, attrs).execute
if issue.valid? if issue.valid?
# Find or create labels and attach to issue. Labels are valid because # Find or create labels and attach to issue. Labels are valid because
......
...@@ -57,7 +57,7 @@ module API ...@@ -57,7 +57,7 @@ module API
not_found! "File" unless blob not_found! "File" unless blob
content_type 'text/plain' content_type 'text/plain'
present blob.data header *Gitlab::Workhorse.send_git_blob(repo, blob)
end end
# Get a raw blob contents by blob sha # Get a raw blob contents by blob sha
...@@ -83,7 +83,7 @@ module API ...@@ -83,7 +83,7 @@ module API
env['api.format'] = :txt env['api.format'] = :txt
content_type blob.mime_type content_type blob.mime_type
present blob.data header *Gitlab::Workhorse.send_git_blob(repo, blob)
end end
# Get a an archive of the repository # Get a an archive of the repository
......
module Backup module Backup
class Manager class Manager
def pack def pack
# Make sure there is a connection
ActiveRecord::Base.connection.reconnect!
# saving additional informations # saving additional informations
s = {} s = {}
s[:db_version] = "#{ActiveRecord::Migrator.current_version}" s[:db_version] = "#{ActiveRecord::Migrator.current_version}"
......
...@@ -8,14 +8,7 @@ module Banzai ...@@ -8,14 +8,7 @@ module Banzai
# Extends HTML::Pipeline::SanitizationFilter with a custom whitelist. # Extends HTML::Pipeline::SanitizationFilter with a custom whitelist.
class SanitizationFilter < HTML::Pipeline::SanitizationFilter class SanitizationFilter < HTML::Pipeline::SanitizationFilter
def whitelist def whitelist
# Descriptions are more heavily sanitized, allowing only a few elements.
# See http://git.io/vkuAN
if context[:inline_sanitization]
whitelist = LIMITED
whitelist[:elements] -= %w(pre code img ol ul li)
else
whitelist = super whitelist = super
end
customize_whitelist(whitelist) customize_whitelist(whitelist)
......
...@@ -4,9 +4,20 @@ module Banzai ...@@ -4,9 +4,20 @@ module Banzai
def self.transform_context(context) def self.transform_context(context)
super(context).merge( super(context).merge(
# SanitizationFilter # SanitizationFilter
inline_sanitization: true whitelist: whitelist
) )
end end
private
def self.whitelist
# Descriptions are more heavily sanitized, allowing only a few elements.
# See http://git.io/vkuAN
whitelist = Banzai::Filter::SanitizationFilter::LIMITED
whitelist[:elements] -= %w(pre code img ol ul li)
whitelist
end
end end
end end
end end
module Gitlab
module AkismetHelper
def akismet_enabled?
current_application_settings.akismet_enabled
end
def akismet_client
@akismet_client ||= ::Akismet::Client.new(current_application_settings.akismet_api_key,
Gitlab.config.gitlab.url)
end
def check_for_spam?(project, user)
akismet_enabled? && !project.team.member?(user)
end
def is_spam?(environment, user, text)
client = akismet_client
ip_address = environment['REMOTE_ADDR']
user_agent = environment['HTTP_USER_AGENT']
params = {
type: 'comment',
text: text,
created_at: DateTime.now,
author: user.name,
author_email: user.email,
referrer: environment['HTTP_REFERER'],
}
begin
is_spam, is_blatant = client.check(ip_address, user_agent, params)
is_spam || is_blatant
rescue => e
Rails.logger.error("Unable to connect to Akismet: #{e}, skipping check")
false
end
end
end
end
...@@ -61,7 +61,7 @@ module Gitlab ...@@ -61,7 +61,7 @@ module Gitlab
# new_path - new project path with namespace # new_path - new project path with namespace
# #
# Ex. # Ex.
# mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git") # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new")
# #
def mv_repository(path, new_path) def mv_repository(path, new_path)
Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project', Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project',
......
...@@ -7,8 +7,8 @@ module Gitlab ...@@ -7,8 +7,8 @@ module Gitlab
settings = nil settings = nil
if connect_to_db? if connect_to_db?
settings = ApplicationSetting.current settings = ::ApplicationSetting.current
settings ||= ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration? settings ||= ::ApplicationSetting.create_from_defaults unless ActiveRecord::Migrator.needs_migration?
end end
settings || fake_application_settings settings || fake_application_settings
...@@ -34,7 +34,8 @@ module Gitlab ...@@ -34,7 +34,8 @@ module Gitlab
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'], shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
max_artifacts_size: Settings.artifacts['max_size'], max_artifacts_size: Settings.artifacts['max_size'],
require_two_factor_authentication: false, require_two_factor_authentication: false,
two_factor_grace_period: 48 two_factor_grace_period: 48,
akismet_enabled: false
) )
end end
......
module Gitlab module Gitlab
module Database module Database
def self.adapter_name
connection.adapter_name
end
def self.mysql? def self.mysql?
ActiveRecord::Base.connection.adapter_name.downcase == 'mysql2' adapter_name.downcase == 'mysql2'
end end
def self.postgresql? def self.postgresql?
ActiveRecord::Base.connection.adapter_name.downcase == 'postgresql' adapter_name.downcase == 'postgresql'
end
def self.version
database_version.match(/\A(?:PostgreSQL |)([^\s]+).*\z/)[1]
end end
def true_value def true_value
case ActiveRecord::Base.connection.adapter_name.downcase if self.class.postgresql?
when 'postgresql'
"'t'" "'t'"
else else
1 1
...@@ -18,12 +25,31 @@ module Gitlab ...@@ -18,12 +25,31 @@ module Gitlab
end end
def false_value def false_value
case ActiveRecord::Base.connection.adapter_name.downcase if self.class.postgresql?
when 'postgresql'
"'f'" "'f'"
else else
0 0
end end
end end
private
def self.connection
ActiveRecord::Base.connection
end
def self.database_version
row = connection.execute("SELECT VERSION()").first
if postgresql?
row['version']
else
row.first
end
end
def connection
self.class.connection
end
end end
end end
...@@ -8,6 +8,7 @@ module Gitlab ...@@ -8,6 +8,7 @@ module Gitlab
blob = repository.blob_at(ref, file_name) blob = repository.blob_at(ref, file_name)
return [] unless blob return [] unless blob
blob.load_all_data!(repository)
highlight(file_name, blob.data).lines.map!(&:html_safe) highlight(file_name, blob.data).lines.map!(&:html_safe)
end end
......
...@@ -34,29 +34,29 @@ module Gitlab ...@@ -34,29 +34,29 @@ module Gitlab
def project_path_regex def project_path_regex
@project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git)\z/.freeze @project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(?<!\.git|\.atom)\z/.freeze
end end
def project_path_regex_message def project_path_regex_message
"can contain only letters, digits, '_', '-' and '.'. " \ "can contain only letters, digits, '_', '-' and '.'. " \
"Cannot start with '-' or end in '.git'" \ "Cannot start with '-', end in '.git' or end in '.atom'" \
end end
def file_name_regex def file_name_regex
@file_name_regex ||= /\A[a-zA-Z0-9_\-\.]*\z/.freeze @file_name_regex ||= /\A[a-zA-Z0-9_\-\.\@]*\z/.freeze
end end
def file_name_regex_message def file_name_regex_message
"can contain only letters, digits, '_', '-' and '.'. " "can contain only letters, digits, '_', '-', '@' and '.'. "
end end
def file_path_regex def file_path_regex
@file_path_regex ||= /\A[a-zA-Z0-9_\-\.\/]*\z/.freeze @file_path_regex ||= /\A[a-zA-Z0-9_\-\.\/\@]*\z/.freeze
end end
def file_path_regex_message def file_path_regex_message
"can contain only letters, digits, '_', '-' and '.'. Separate directories with a '/'. " "can contain only letters, digits, '_', '-', '@' and '.'. Separate directories with a '/'. "
end end
......
require 'base64'
require 'json'
module Gitlab
class Workhorse
class << self
def send_git_blob(repository, blob)
params_hash = {
'RepoPath' => repository.path_to_repo,
'BlobId' => blob.id,
}
params = Base64.urlsafe_encode64(JSON.dump(params_hash))
[
'Gitlab-Workhorse-Send-Data',
"git-blob:#{params}",
]
end
end
end
end
...@@ -38,7 +38,7 @@ web_server_pid_path="$pid_path/unicorn.pid" ...@@ -38,7 +38,7 @@ web_server_pid_path="$pid_path/unicorn.pid"
sidekiq_pid_path="$pid_path/sidekiq.pid" sidekiq_pid_path="$pid_path/sidekiq.pid"
mail_room_enabled=false mail_room_enabled=false
mail_room_pid_path="$pid_path/mail_room.pid" mail_room_pid_path="$pid_path/mail_room.pid"
gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse && pwd) gitlab_workhorse_dir=$(cd $app_root/../gitlab-workhorse 2> /dev/null && pwd)
gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid" gitlab_workhorse_pid_path="$pid_path/gitlab-workhorse.pid"
gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $rails_socket -documentRoot $app_root/public" gitlab_workhorse_options="-listenUmask 0 -listenNetwork unix -listenAddr $socket_path/gitlab-workhorse.socket -authBackend http://127.0.0.1:8080 -authSocket $rails_socket -documentRoot $app_root/public"
gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log" gitlab_workhorse_log="$app_root/log/gitlab-workhorse.log"
...@@ -49,7 +49,7 @@ test -f /etc/default/gitlab && . /etc/default/gitlab ...@@ -49,7 +49,7 @@ 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 [ `whoami` != "$app_user" ]; then if [ `whoami` != "$app_user" ]; then
eval su - "$app_user" -s $shell_path -c $(echo \")$0 "$@"$(echo \"); exit; eval su - "$app_user" -c $(echo \")$shell_path -l -c \'$0 "$@"\'$(echo \"); exit;
fi fi
# Switch to the gitlab path, exit on failure. # Switch to the gitlab path, exit on failure.
...@@ -219,7 +219,7 @@ start_gitlab() { ...@@ -219,7 +219,7 @@ start_gitlab() {
echo "The Unicorn web server already running with pid $wpid, not restarting." echo "The Unicorn web server already running with pid $wpid, not restarting."
else else
# Remove old socket if it exists # Remove old socket if it exists
rm -f "$socket_path"/gitlab.socket 2>/dev/null rm -f "$rails_socket" 2>/dev/null
# Start the web server # Start the web server
RAILS_ENV=$RAILS_ENV bin/web start RAILS_ENV=$RAILS_ENV bin/web start
fi fi
......
require 'spec_helper'
describe Admin::SpamLogsController do
let(:admin) { create(:admin) }
let(:user) { create(:user) }
let!(:first_spam) { create(:spam_log, user: user) }
let!(:second_spam) { create(:spam_log, user: user) }
before do
sign_in(admin)
end
describe '#index' do
it 'lists all spam logs' do
get :index
expect(response.status).to eq(200)
end
end
describe '#destroy' do
it 'removes only the spam log when removing log' do
expect { delete :destroy, id: first_spam.id }.to change { SpamLog.count }.by(-1)
expect(User.find(user.id)).to be_truthy
expect(response.status).to eq(200)
end
it 'removes user and his spam logs when removing the user' do
delete :destroy, id: first_spam.id, remove_user: true
expect(flash[:notice]).to eq "User #{user.username} was successfully removed."
expect(response.status).to eq(302)
expect(SpamLog.count).to eq(0)
expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
...@@ -199,6 +199,32 @@ describe Projects::MergeRequestsController do ...@@ -199,6 +199,32 @@ describe Projects::MergeRequestsController do
end end
end end
describe 'GET diffs with view' do
def go(extra_params = {})
params = {
namespace_id: project.namespace.to_param,
project_id: project.to_param,
id: merge_request.iid
}
get :diffs, params.merge(extra_params)
end
it 'saves the preferred diff view in a cookie' do
go view: 'parallel'
expect(response.cookies['diff_view']).to eq('parallel')
end
it 'assigns :view param based on cookie' do
request.cookies['diff_view'] = 'parallel'
go
expect(controller.params[:view]).to eq 'parallel'
end
end
describe 'GET commits' do describe 'GET commits' do
def go(format: 'html') def go(format: 'html')
get :commits, get :commits,
......
...@@ -86,6 +86,14 @@ describe ProjectsController do ...@@ -86,6 +86,14 @@ describe ProjectsController do
end end
end end
end end
context "when the url contains .atom" do
let(:public_project_with_dot_atom) { build(:project, :public, name: 'my.atom', path: 'my.atom') }
it 'expect an error creating the project' do
expect(public_project_with_dot_atom).not_to be_valid
end
end
end end
describe "#destroy" do describe "#destroy" do
......
# == Schema Information
#
# Table name: builds
#
# id :integer not null, primary key
# project_id :integer
# status :string(255)
# finished_at :datetime
# trace :text
# created_at :datetime
# updated_at :datetime
# started_at :datetime
# runner_id :integer
# commit_id :integer
# coverage :float
# commands :text
# job_id :integer
# name :string(255)
# deploy :boolean default(FALSE)
# options :text
# allow_failure :boolean default(FALSE), not null
# stage :string(255)
# trigger_request_id :integer
#
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do FactoryGirl.define do
factory :ci_build, class: Ci::Build do factory :ci_build, class: Ci::Build do
name 'test' name 'test'
...@@ -65,5 +38,20 @@ FactoryGirl.define do ...@@ -65,5 +38,20 @@ FactoryGirl.define do
build.trace = 'BUILD TRACE' build.trace = 'BUILD TRACE'
end end
end end
trait :artifacts do
after(:create) do |build, _|
build.artifacts_file =
fixture_file_upload(Rails.root +
'spec/fixtures/ci_build_artifacts.zip',
'application/zip')
build.artifacts_metadata =
fixture_file_upload(Rails.root +
'spec/fixtures/ci_build_artifacts_metadata.gz',
'application/x-gzip')
build.save!
end
end
end end
end end
# Read about factories at https://github.com/thoughtbot/factory_girl
FactoryGirl.define do
factory :spam_log do
user
source_ip { FFaker::Internet.ip_v4_address }
noteable_type 'Issue'
title { FFaker::Lorem.sentence }
description { FFaker::Lorem.paragraph(5) }
end
end
...@@ -18,7 +18,7 @@ describe 'Admin Builds' do ...@@ -18,7 +18,7 @@ describe 'Admin Builds' do
visit admin_builds_path visit admin_builds_path
expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page.all('.build-link').size).to eq(4) expect(page.all('.build-link').size).to eq(4)
expect(page).to have_link 'Cancel all' expect(page).to have_link 'Cancel all'
end end
...@@ -28,7 +28,7 @@ describe 'Admin Builds' do ...@@ -28,7 +28,7 @@ describe 'Admin Builds' do
it 'shows a message' do it 'shows a message' do
visit admin_builds_path visit admin_builds_path
expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') expect(page).to have_selector('.nav-links li.active', text: 'All')
expect(page).to have_content 'No builds to show' expect(page).to have_content 'No builds to show'
expect(page).not_to have_link 'Cancel all' expect(page).not_to have_link 'Cancel all'
end end
...@@ -44,7 +44,7 @@ describe 'Admin Builds' do ...@@ -44,7 +44,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :running) visit admin_builds_path(scope: :running)
expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running') expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page.find('.build-link')).to have_content(build1.id) expect(page.find('.build-link')).to have_content(build1.id)
expect(page.find('.build-link')).not_to have_content(build2.id) expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).not_to have_content(build3.id) expect(page.find('.build-link')).not_to have_content(build3.id)
...@@ -58,7 +58,7 @@ describe 'Admin Builds' do ...@@ -58,7 +58,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :running) visit admin_builds_path(scope: :running)
expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running') expect(page).to have_selector('.nav-links li.active', text: 'Running')
expect(page).to have_content 'No builds to show' expect(page).to have_content 'No builds to show'
expect(page).not_to have_link 'Cancel all' expect(page).not_to have_link 'Cancel all'
end end
...@@ -74,7 +74,7 @@ describe 'Admin Builds' do ...@@ -74,7 +74,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :finished) visit admin_builds_path(scope: :finished)
expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished') expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page.find('.build-link')).not_to have_content(build1.id) expect(page.find('.build-link')).not_to have_content(build1.id)
expect(page.find('.build-link')).not_to have_content(build2.id) expect(page.find('.build-link')).not_to have_content(build2.id)
expect(page.find('.build-link')).to have_content(build3.id) expect(page.find('.build-link')).to have_content(build3.id)
...@@ -88,7 +88,7 @@ describe 'Admin Builds' do ...@@ -88,7 +88,7 @@ describe 'Admin Builds' do
visit admin_builds_path(scope: :finished) visit admin_builds_path(scope: :finished)
expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished') expect(page).to have_selector('.nav-links li.active', text: 'Finished')
expect(page).to have_content 'No builds to show' expect(page).to have_content 'No builds to show'
expect(page).to have_link 'Cancel all' expect(page).to have_link 'Cancel all'
end end
......
...@@ -18,7 +18,7 @@ describe "Builds" do ...@@ -18,7 +18,7 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project, scope: :running) visit namespace_project_builds_path(@project.namespace, @project, scope: :running)
end end
it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'Running') } it { expect(page).to have_selector('.nav-links li.active', text: 'Running') }
it { expect(page).to have_link 'Cancel running' } it { expect(page).to have_link 'Cancel running' }
it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.ref }
...@@ -31,7 +31,7 @@ describe "Builds" do ...@@ -31,7 +31,7 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project, scope: :finished) visit namespace_project_builds_path(@project.namespace, @project, scope: :finished)
end end
it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'Finished') } it { expect(page).to have_selector('.nav-links li.active', text: 'Finished') }
it { expect(page).to have_content 'No builds to show' } it { expect(page).to have_content 'No builds to show' }
it { expect(page).to have_link 'Cancel running' } it { expect(page).to have_link 'Cancel running' }
end end
...@@ -42,7 +42,7 @@ describe "Builds" do ...@@ -42,7 +42,7 @@ describe "Builds" do
visit namespace_project_builds_path(@project.namespace, @project) visit namespace_project_builds_path(@project.namespace, @project)
end end
it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') } it { expect(page).to have_selector('.nav-links li.active', text: 'All') }
it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.ref }
it { expect(page).to have_content @build.name } it { expect(page).to have_content @build.name }
...@@ -57,7 +57,7 @@ describe "Builds" do ...@@ -57,7 +57,7 @@ describe "Builds" do
click_link "Cancel running" click_link "Cancel running"
end end
it { expect(page).to have_selector('.project-issuable-filter li.active', text: 'All') } it { expect(page).to have_selector('.nav-links li.active', text: 'All') }
it { expect(page).to have_content 'canceled' } it { expect(page).to have_content 'canceled' }
it { expect(page).to have_content @build.short_sha } it { expect(page).to have_content @build.short_sha }
it { expect(page).to have_content @build.ref } it { expect(page).to have_content @build.ref }
......
...@@ -86,6 +86,25 @@ feature 'Project', feature: true do ...@@ -86,6 +86,25 @@ feature 'Project', feature: true do
end end
end end
describe 'project title' do
include WaitForAjax
let(:user) { create(:user) }
let(:project) { create(:project, namespace: user.namespace) }
before do
login_with(user)
project.team.add_user(user, Gitlab::Access::MASTER)
visit namespace_project_path(project.namespace, project)
end
it 'click toggle and show dropdown', js: true do
find('.js-projects-dropdown-toggle').click
wait_for_ajax
expect(page).to have_css('.select2-results li', count: 1)
end
end
def remove_with_confirm(button_text, confirm_with) def remove_with_confirm(button_text, confirm_with)
click_button button_text click_button button_text
fill_in 'confirm_name_input', with: confirm_with fill_in 'confirm_name_input', with: confirm_with
......
...@@ -11,34 +11,31 @@ describe LabelsHelper do ...@@ -11,34 +11,31 @@ describe LabelsHelper do
end end
it 'uses the instance variable' do it 'uses the instance variable' do
expect(label).not_to receive(:project) expect(link_to_label(label)).to match %r{<a href="/#{@project.to_reference}/issues\?label_name=#{label.name}">.*</a>}
link_to_label(label)
end end
end end
context 'without @project set' do context 'without @project set' do
it "uses the label's project" do it "uses the label's project" do
expect(label).to receive(:project).and_return(project) expect(link_to_label(label)).to match %r{<a href="/#{label.project.to_reference}/issues\?label_name=#{label.name}">.*</a>}
link_to_label(label)
end end
end end
context 'with a named project argument' do context 'with a project argument' do
it 'uses the provided project' do let(:another_project) { double('project', namespace: 'foo3', to_param: 'bar3') }
arg = double('project')
expect(arg).to receive(:namespace).and_return('foo')
expect(arg).to receive(:to_param).and_return('foo')
link_to_label(label, project: arg) it 'links to merge requests page' do
expect(link_to_label(label, project: another_project)).to match %r{<a href="/foo3/bar3/issues\?label_name=#{label.name}">.*</a>}
end
end end
it 'takes precedence over other types' do context 'with a type argument' do
@project = project ['issue', :issue, 'merge_request', :merge_request].each do |type|
expect(@project).not_to receive(:namespace) context "set to #{type}" do
expect(label).not_to receive(:project) it 'links to correct page' do
expect(link_to_label(label, type: type)).to match %r{<a href="/#{label.project.to_reference}/#{type.to_s.pluralize}\?label_name=#{label.name}">.*</a>}
arg = double('project', namespace: 'foo', to_param: 'foo') end
link_to_label(label, project: arg) end
end end
end end
......
...@@ -42,9 +42,9 @@ describe SearchHelper do ...@@ -42,9 +42,9 @@ describe SearchHelper do
expect(search_autocomplete_opts(project.name).size).to eq(1) expect(search_autocomplete_opts(project.name).size).to eq(1)
end end
it "includes the public group" do it "should not include the public group" do
group = create(:group) group = create(:group)
expect(search_autocomplete_opts(group.name).size).to eq(1) expect(search_autocomplete_opts(group.name).size).to eq(0)
end end
context "with a current project" do context "with a current project" do
......
%h1.title
%a
GitLab Org
%a.project-item-select-holder{href: "/gitlab-org/gitlab-test"}
GitLab Test
%input#project_path.project-item-select.js-projects-dropdown.ajax-project-select{type: "hidden", name: "project_path", "data-include-groups" => "false"}
%i.fa.chevron-down.dropdown-toggle-caret.js-projects-dropdown-toggle
[{"id":9,"description":"","default_branch":null,"tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:root/test.git","http_url_to_repo":"http://localhost:3000/root/test.git","web_url":"http://localhost:3000/root/test","owner":{"name":"Administrator","username":"root","id":1,"state":"active","avatar_url":"http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80\u0026d=identicon","web_url":"http://localhost:3000/u/root"},"name":"test","name_with_namespace":"Administrator / test","path":"test","path_with_namespace":"root/test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-14T19:08:05.364Z","last_activity_at":"2016-01-14T19:08:07.418Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":1,"name":"root","path":"root","owner_id":1,"created_at":"2016-01-13T20:19:44.439Z","updated_at":"2016-01-13T20:19:44.439Z","description":"","avatar":null},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":0,"permissions":{"project_access":null,"group_access":null}},{"id":8,"description":"Voluptatem quae nulla eius numquam ullam voluptatibus quia modi.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:h5bp/html5-boilerplate.git","http_url_to_repo":"http://localhost:3000/h5bp/html5-boilerplate.git","web_url":"http://localhost:3000/h5bp/html5-boilerplate","name":"Html5 Boilerplate","name_with_namespace":"H5bp / Html5 Boilerplate","path":"html5-boilerplate","path_with_namespace":"h5bp/html5-boilerplate","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:57.525Z","last_activity_at":"2016-01-13T20:27:57.280Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":5,"name":"H5bp","path":"h5bp","owner_id":null,"created_at":"2016-01-13T20:19:57.239Z","updated_at":"2016-01-13T20:19:57.239Z","description":"Tempore accusantium possimus aut libero.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":10,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":7,"description":"Modi odio mollitia dolorem qui.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:twitter/typeahead-js.git","http_url_to_repo":"http://localhost:3000/twitter/typeahead-js.git","web_url":"http://localhost:3000/twitter/typeahead-js","name":"Typeahead.Js","name_with_namespace":"Twitter / Typeahead.Js","path":"typeahead-js","path_with_namespace":"twitter/typeahead-js","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:56.212Z","last_activity_at":"2016-01-13T20:27:51.496Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":6,"description":"Omnis asperiores ipsa et beatae quidem necessitatibus quia.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:twitter/flight.git","http_url_to_repo":"http://localhost:3000/twitter/flight.git","web_url":"http://localhost:3000/twitter/flight","name":"Flight","name_with_namespace":"Twitter / Flight","path":"flight","path_with_namespace":"twitter/flight","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:54.754Z","last_activity_at":"2016-01-13T20:27:50.502Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":4,"name":"Twitter","path":"twitter","owner_id":null,"created_at":"2016-01-13T20:19:54.480Z","updated_at":"2016-01-13T20:19:54.480Z","description":"Id voluptatem ipsa maiores omnis repudiandae et et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":4,"permissions":{"project_access":null,"group_access":{"access_level":10,"notification_level":3}}},{"id":5,"description":"Voluptatem commodi voluptate placeat architecto beatae illum dolores fugiat.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-test.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-test.git","web_url":"http://localhost:3000/gitlab-org/gitlab-test","name":"Gitlab Test","name_with_namespace":"Gitlab Org / Gitlab Test","path":"gitlab-test","path_with_namespace":"gitlab-org/gitlab-test","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:53.202Z","last_activity_at":"2016-01-13T20:27:41.626Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":4,"description":"Aut molestias quas est ut aperiam officia quod libero.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-shell.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-shell.git","web_url":"http://localhost:3000/gitlab-org/gitlab-shell","name":"Gitlab Shell","name_with_namespace":"Gitlab Org / Gitlab Shell","path":"gitlab-shell","path_with_namespace":"gitlab-org/gitlab-shell","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:51.882Z","last_activity_at":"2016-01-13T20:27:35.678Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":20,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":3,"description":"Excepturi molestiae quia repellendus omnis est illo illum eligendi.","default_branch":"master","tag_list":[],"public":true,"archived":false,"visibility_level":20,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ci.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ci.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ci","name":"Gitlab Ci","name_with_namespace":"Gitlab Org / Gitlab Ci","path":"gitlab-ci","path_with_namespace":"gitlab-org/gitlab-ci","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:50.346Z","last_activity_at":"2016-01-13T20:27:30.115Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":3,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}},{"id":2,"description":"Adipisci quaerat dignissimos enim sed ipsam dolorem quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":10,"ssh_url_to_repo":"phil@localhost:gitlab-org/gitlab-ce.git","http_url_to_repo":"http://localhost:3000/gitlab-org/gitlab-ce.git","web_url":"http://localhost:3000/gitlab-org/gitlab-ce","name":"Gitlab Ce","name_with_namespace":"Gitlab Org / Gitlab Ce","path":"gitlab-ce","path_with_namespace":"gitlab-org/gitlab-ce","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:49.065Z","last_activity_at":"2016-01-13T20:26:58.454Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":3,"name":"Gitlab Org","path":"gitlab-org","owner_id":null,"created_at":"2016-01-13T20:19:48.851Z","updated_at":"2016-01-13T20:19:48.851Z","description":"Magni mollitia quod quidem soluta nesciunt impedit.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":{"access_level":30,"notification_level":3},"group_access":{"access_level":50,"notification_level":3}}},{"id":1,"description":"Vel voluptatem maxime saepe ex quia.","default_branch":"master","tag_list":[],"public":false,"archived":false,"visibility_level":0,"ssh_url_to_repo":"phil@localhost:documentcloud/underscore.git","http_url_to_repo":"http://localhost:3000/documentcloud/underscore.git","web_url":"http://localhost:3000/documentcloud/underscore","name":"Underscore","name_with_namespace":"Documentcloud / Underscore","path":"underscore","path_with_namespace":"documentcloud/underscore","issues_enabled":true,"merge_requests_enabled":true,"wiki_enabled":true,"builds_enabled":true,"snippets_enabled":false,"created_at":"2016-01-13T20:19:45.862Z","last_activity_at":"2016-01-13T20:25:03.106Z","shared_runners_enabled":true,"creator_id":1,"namespace":{"id":2,"name":"Documentcloud","path":"documentcloud","owner_id":null,"created_at":"2016-01-13T20:19:44.464Z","updated_at":"2016-01-13T20:19:44.464Z","description":"Aut impedit perferendis fuga et ipsa repellat cupiditate et.","avatar":{"url":null}},"avatar_url":null,"star_count":0,"forks_count":0,"open_issues_count":5,"permissions":{"project_access":null,"group_access":{"access_level":50,"notification_level":3}}}]
#= require select2
#= require api
#= require project_select
#= require project
window.gon = {}
window.gon.api_version = 'v3'
describe 'Project Title', ->
fixture.preload('project_title.html')
fixture.preload('projects.json')
beforeEach ->
fixture.load('project_title.html')
@project = new Project()
spyOn(@project, 'changeProject').and.callFake (url) ->
window.current_project_url = url
describe 'project list', ->
beforeEach =>
@projects_data = fixture.load('projects.json')[0]
spyOn(jQuery, 'ajax').and.callFake (req) =>
expect(req.url).toBe('/api/v3/projects.json')
d = $.Deferred()
d.resolve @projects_data
d.promise()
it 'to show on toggle click', =>
$('.js-projects-dropdown-toggle').click()
expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(true)
expect($('.ajax-project-dropdown li').length).toBe(@projects_data.length)
it 'hide dropdown', ->
$("#select2-drop-mask").click()
expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(false)
it 'change project when clicking item', ->
$('.js-projects-dropdown-toggle').click()
$('.ajax-project-dropdown li:nth-child(2)').trigger('mouseup')
expect($('.title .select2-container').hasClass('select2-dropdown-open')).toBe(false)
expect(window.current_project_url).toBe('http://localhost:3000/h5bp/html5-boilerplate')
...@@ -177,26 +177,4 @@ describe Banzai::Filter::SanitizationFilter, lib: true do ...@@ -177,26 +177,4 @@ describe Banzai::Filter::SanitizationFilter, lib: true do
expect(act.to_html).to eq exp expect(act.to_html).to eq exp
end end
end end
context 'when inline_sanitization is true' do
it 'uses a stricter whitelist' do
doc = filter('<h1>Description</h1>', inline_sanitization: true)
expect(doc.to_html.strip).to eq 'Description'
end
%w(pre code img ol ul li).each do |elem|
it "removes '#{elem}' elements" do
act = "<#{elem}>Description</#{elem}>"
expect(filter(act, inline_sanitization: true).to_html.strip).
to eq 'Description'
end
end
%w(b i strong em a ins del sup sub p).each do |elem|
it "still allows '#{elem}' elements" do
exp = act = "<#{elem}>Description</#{elem}>"
expect(filter(act, inline_sanitization: true).to_html).to eq exp
end
end
end
end end
require 'rails_helper'
describe Banzai::Pipeline::DescriptionPipeline do
def parse(html)
# When we pass HTML to Redcarpet, it gets wrapped in `p` tags...
# ...except when we pass it pre-wrapped text. Rabble rabble.
unwrap = !html.start_with?('<p>')
output = described_class.to_html(html, project: spy)
output.gsub!(%r{\A<p>(.*)</p>(.*)\z}, '\1\2') if unwrap
output
end
it 'uses a limited whitelist' do
doc = parse('# Description')
expect(doc.strip).to eq 'Description'
end
%w(pre code img ol ul li).each do |elem|
it "removes '#{elem}' elements" do
act = "<#{elem}>Description</#{elem}>"
expect(parse(act).strip).to eq 'Description'
end
end
%w(b i strong em a ins del sup sub p).each do |elem|
it "still allows '#{elem}' elements" do
exp = act = "<#{elem}>Description</#{elem}>"
expect(parse(act).strip).to eq exp
end
end
end
require 'spec_helper'
describe Gitlab::AkismetHelper, type: :helper do
let(:project) { create(:project) }
let(:user) { create(:user) }
before do
allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
current_application_settings.akismet_enabled = true
current_application_settings.akismet_api_key = '12345'
end
describe '#check_for_spam?' do
it 'returns true for non-member' do
expect(helper.check_for_spam?(project, user)).to eq(true)
end
it 'returns false for member' do
project.team << [user, :guest]
expect(helper.check_for_spam?(project, user)).to eq(false)
end
end
describe '#is_spam?' do
it 'returns true for spam' do
environment = {
'REMOTE_ADDR' => '127.0.0.1',
'HTTP_USER_AGENT' => 'Test User Agent'
}
allow_any_instance_of(::Akismet::Client).to receive(:check).and_return([true, true])
expect(helper.is_spam?(environment, user, 'Is this spam?')).to eq(true)
end
end
end
...@@ -14,4 +14,24 @@ describe Gitlab::Database, lib: true do ...@@ -14,4 +14,24 @@ describe Gitlab::Database, lib: true do
it { is_expected.to satisfy { |val| val == true || val == false } } it { is_expected.to satisfy { |val| val == true || val == false } }
end end
describe '.version' do
context "on mysql" do
it "extracts the version number" do
allow(described_class).to receive(:database_version).
and_return("5.7.12-standard")
expect(described_class.version).to eq '5.7.12-standard'
end
end
context "on postgresql" do
it "extracts the version number" do
allow(described_class).to receive(:database_version).
and_return("PostgreSQL 9.4.4 on x86_64-apple-darwin14.3.0")
expect(described_class.version).to eq '9.4.4'
end
end
end
end end
...@@ -21,4 +21,12 @@ describe Gitlab::Regex, lib: true do ...@@ -21,4 +21,12 @@ describe Gitlab::Regex, lib: true do
it { expect('Dash – is this').to match(Gitlab::Regex.project_name_regex) } it { expect('Dash – is this').to match(Gitlab::Regex.project_name_regex) }
it { expect('?gitlab').not_to match(Gitlab::Regex.project_name_regex) } it { expect('?gitlab').not_to match(Gitlab::Regex.project_name_regex) }
end end
describe 'file name regex' do
it { expect('foo@bar').to match(Gitlab::Regex.file_name_regex) }
end
describe 'file path regex' do
it { expect('foo@/bar').to match(Gitlab::Regex.file_path_regex) }
end
end end
...@@ -188,6 +188,11 @@ describe MergeRequest, models: true do ...@@ -188,6 +188,11 @@ describe MergeRequest, models: true do
expect(subject).to be_work_in_progress expect(subject).to be_work_in_progress
end end
it "detects the '[WIP]' prefix" do
subject.title = "[WIP]#{subject.title}"
expect(subject).to be_work_in_progress
end
it "doesn't detect WIP for words starting with WIP" do it "doesn't detect WIP for words starting with WIP" do
subject.title = "Wipwap #{subject.title}" subject.title = "Wipwap #{subject.title}"
expect(subject).not_to be_work_in_progress expect(subject).not_to be_work_in_progress
...@@ -250,9 +255,15 @@ describe MergeRequest, models: true do ...@@ -250,9 +255,15 @@ describe MergeRequest, models: true do
expect(subject.can_remove_source_branch?(user2)).to be_falsey expect(subject.can_remove_source_branch?(user2)).to be_falsey
end end
it "is can be removed in all other cases" do it "can be removed if the last commit is the head of the source branch" do
allow(subject.source_project).to receive(:commit).and_return(subject.last_commit)
expect(subject.can_remove_source_branch?(user)).to be_truthy expect(subject.can_remove_source_branch?(user)).to be_truthy
end end
it "cannot be removed if the last commit is not also the head of the source branch" do
expect(subject.can_remove_source_branch?(user)).to be_falsey
end
end end
describe "#reset_merge_when_build_succeeds" do describe "#reset_merge_when_build_succeeds" do
......
require 'spec_helper'
describe SpamLog, models: true do
describe 'associations' do
it { is_expected.to belong_to(:user) }
end
describe 'validations' do
it { is_expected.to validate_presence_of(:user) }
end
describe '#remove_user' do
it 'blocks the user' do
spam_log = build(:spam_log)
expect { spam_log.remove_user }.to change { spam_log.user.blocked? }.to(true)
end
it 'removes the user' do
spam_log = build(:spam_log)
expect { spam_log.remove_user }.to change { User.count }.by(-1)
end
end
end
...@@ -91,6 +91,7 @@ describe User, models: true do ...@@ -91,6 +91,7 @@ describe User, models: true do
it { is_expected.to have_many(:assigned_merge_requests).dependent(:destroy) } it { is_expected.to have_many(:assigned_merge_requests).dependent(:destroy) }
it { is_expected.to have_many(:identities).dependent(:destroy) } it { is_expected.to have_many(:identities).dependent(:destroy) }
it { is_expected.to have_one(:abuse_report) } it { is_expected.to have_one(:abuse_report) }
it { is_expected.to have_many(:spam_logs).dependent(:destroy) }
end end
describe 'validations' do describe 'validations' do
......
...@@ -46,10 +46,10 @@ describe API::API, api: true do ...@@ -46,10 +46,10 @@ describe API::API, api: true do
expect(json_response.first['title']).to eq(issue.title) expect(json_response.first['title']).to eq(issue.title)
end end
it "should add pagination headers" do it "should add pagination headers and keep query params" do
get api("/issues?per_page=3", user) get api("/issues?state=closed&per_page=3", user)
expect(response.headers['Link']).to eq( expect(response.headers['Link']).to eq(
'<http://www.example.com/api/v3/issues?page=1&per_page=3>; rel="first", <http://www.example.com/api/v3/issues?page=1&per_page=3>; rel="last"' '<http://www.example.com/api/v3/issues?page=1&per_page=3&private_token=%s&state=closed>; rel="first", <http://www.example.com/api/v3/issues?page=1&per_page=3&private_token=%s&state=closed>; rel="last"' % [user.private_token, user.private_token]
) )
end end
...@@ -241,6 +241,37 @@ describe API::API, api: true do ...@@ -241,6 +241,37 @@ describe API::API, api: true do
end end
end end
describe 'POST /projects/:id/issues with spam filtering' do
before do
Grape::Endpoint.before_each do |endpoint|
allow(endpoint).to receive(:check_for_spam?).and_return(true)
allow(endpoint).to receive(:is_spam?).and_return(true)
end
end
let(:params) do
{
title: 'new issue',
description: 'content here',
labels: 'label, label2'
}
end
it "should not create a new project issue" do
expect { post api("/projects/#{project.id}/issues", user), params }.not_to change(Issue, :count)
expect(response.status).to eq(400)
expect(json_response['message']).to eq({ "error" => "Spam detected" })
spam_logs = SpamLog.all
expect(spam_logs.count).to eq(1)
expect(spam_logs[0].title).to eq('new issue')
expect(spam_logs[0].description).to eq('content here')
expect(spam_logs[0].user).to eq(user)
expect(spam_logs[0].noteable_type).to eq('Issue')
expect(spam_logs[0].project_id).to eq(project.id)
end
end
describe "PUT /projects/:id/issues/:issue_id to update only title" do describe "PUT /projects/:id/issues/:issue_id to update only title" do
it "should update a project issue" do it "should update a project issue" do
put api("/projects/#{project.id}/issues/#{issue.id}", user), put api("/projects/#{project.id}/issues/#{issue.id}", user),
......
...@@ -151,8 +151,8 @@ describe Ci::API::API do ...@@ -151,8 +151,8 @@ describe Ci::API::API do
context "Artifacts" do context "Artifacts" do
let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') } let(:file_upload) { fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') }
let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') } let(:file_upload2) { fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/gif') }
let(:commit) { FactoryGirl.create(:ci_commit, project: project) } let(:commit) { create(:ci_commit, project: project) }
let(:build) { FactoryGirl.create(:ci_build, commit: commit, runner_id: runner.id) } let(:build) { create(:ci_build, commit: commit, runner_id: runner.id) }
let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") } let(:authorize_url) { ci_api("/builds/#{build.id}/artifacts/authorize") }
let(:post_url) { ci_api("/builds/#{build.id}/artifacts") } let(:post_url) { ci_api("/builds/#{build.id}/artifacts") }
let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") } let(:delete_url) { ci_api("/builds/#{build.id}/artifacts") }
...@@ -160,12 +160,10 @@ describe Ci::API::API do ...@@ -160,12 +160,10 @@ describe Ci::API::API do
let(:headers) { { "GitLab-Workhorse" => "1.0" } } let(:headers) { { "GitLab-Workhorse" => "1.0" } }
let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token) } let(:headers_with_token) { headers.merge(Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token) }
before { build.run! }
describe "POST /builds/:id/artifacts/authorize" do describe "POST /builds/:id/artifacts/authorize" do
context "should authorize posting artifact to running build" do context "should authorize posting artifact to running build" do
before do
build.run!
end
it "using token as parameter" do it "using token as parameter" do
post authorize_url, { token: build.token }, headers post authorize_url, { token: build.token }, headers
expect(response.status).to eq(200) expect(response.status).to eq(200)
...@@ -180,10 +178,6 @@ describe Ci::API::API do ...@@ -180,10 +178,6 @@ describe Ci::API::API do
end end
context "should fail to post too large artifact" do context "should fail to post too large artifact" do
before do
build.run!
end
it "using token as parameter" do it "using token as parameter" do
stub_application_setting(max_artifacts_size: 0) stub_application_setting(max_artifacts_size: 0)
post authorize_url, { token: build.token, filesize: 100 }, headers post authorize_url, { token: build.token, filesize: 100 }, headers
...@@ -197,8 +191,8 @@ describe Ci::API::API do ...@@ -197,8 +191,8 @@ describe Ci::API::API do
end end
end end
context "should get denied" do context 'token is invalid' do
it do it 'should respond with forbidden'do
post authorize_url, { token: 'invalid', filesize: 100 } post authorize_url, { token: 'invalid', filesize: 100 }
expect(response.status).to eq(403) expect(response.status).to eq(403)
end end
...@@ -206,17 +200,13 @@ describe Ci::API::API do ...@@ -206,17 +200,13 @@ describe Ci::API::API do
end end
describe "POST /builds/:id/artifacts" do describe "POST /builds/:id/artifacts" do
context "Disable sanitizer" do context "disable sanitizer" do
before do before do
# by configuring this path we allow to pass temp file from any path # by configuring this path we allow to pass temp file from any path
allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return('/') allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return('/')
end end
context "should post artifact to running build" do context "should post artifact to running build" do
before do
build.run!
end
it "uses regual file post" do it "uses regual file post" do
upload_artifacts(file_upload, headers_with_token, false) upload_artifacts(file_upload, headers_with_token, false)
expect(response.status).to eq(201) expect(response.status).to eq(201)
...@@ -244,10 +234,7 @@ describe Ci::API::API do ...@@ -244,10 +234,7 @@ describe Ci::API::API do
let(:stored_artifacts_file) { build.reload.artifacts_file.file } let(:stored_artifacts_file) { build.reload.artifacts_file.file }
let(:stored_metadata_file) { build.reload.artifacts_metadata.file } let(:stored_metadata_file) { build.reload.artifacts_metadata.file }
before do before { post(post_url, post_data, headers_with_token) }
build.run!
post(post_url, post_data, headers_with_token)
end
context 'post data accelerated by workhorse is correct' do context 'post data accelerated by workhorse is correct' do
let(:post_data) do let(:post_data) do
...@@ -257,11 +244,8 @@ describe Ci::API::API do ...@@ -257,11 +244,8 @@ describe Ci::API::API do
'metadata.name' => metadata.original_filename } 'metadata.name' => metadata.original_filename }
end end
it 'responds with valid status' do
expect(response.status).to eq(201)
end
it 'stores artifacts and artifacts metadata' do it 'stores artifacts and artifacts metadata' do
expect(response.status).to eq(201)
expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename) expect(stored_artifacts_file.original_filename).to eq(artifacts.original_filename)
expect(stored_metadata_file.original_filename).to eq(metadata.original_filename) expect(stored_metadata_file.original_filename).to eq(metadata.original_filename)
end end
...@@ -282,56 +266,42 @@ describe Ci::API::API do ...@@ -282,56 +266,42 @@ describe Ci::API::API do
end end
end end
context "artifacts file is too large" do
context "should fail to post too large artifact" do it "should fail to post too large artifact" do
before do
build.run!
end
it do
stub_application_setting(max_artifacts_size: 0) stub_application_setting(max_artifacts_size: 0)
upload_artifacts(file_upload, headers_with_token) upload_artifacts(file_upload, headers_with_token)
expect(response.status).to eq(413) expect(response.status).to eq(413)
end end
end end
context "should fail to post artifacts without file" do context "artifacts post request does not contain file" do
before do it "should fail to post artifacts without file" do
build.run!
end
it do
post post_url, {}, headers_with_token post post_url, {}, headers_with_token
expect(response.status).to eq(400) expect(response.status).to eq(400)
end end
end end
context "should fail to post artifacts without GitLab-Workhorse" do context 'GitLab Workhorse is not configured' do
before do it "should fail to post artifacts without GitLab-Workhorse" do
build.run!
end
it do
post post_url, { token: build.token }, {} post post_url, { token: build.token }, {}
expect(response.status).to eq(403) expect(response.status).to eq(403)
end end
end end
end end
context "should fail to post artifacts for outside of tmp path" do context "artifacts are being stored outside of tmp path" do
before do before do
# by configuring this path we allow to pass file from @tmpdir only # by configuring this path we allow to pass file from @tmpdir only
# but all temporary files are stored in system tmp directory # but all temporary files are stored in system tmp directory
@tmpdir = Dir.mktmpdir @tmpdir = Dir.mktmpdir
allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return(@tmpdir) allow(ArtifactUploader).to receive(:artifacts_upload_path).and_return(@tmpdir)
build.run!
end end
after do after do
FileUtils.remove_entry @tmpdir FileUtils.remove_entry @tmpdir
end end
it do it "should fail to post artifacts for outside of tmp path" do
upload_artifacts(file_upload, headers_with_token) upload_artifacts(file_upload, headers_with_token)
expect(response.status).to eq(400) expect(response.status).to eq(400)
end end
...@@ -349,35 +319,39 @@ describe Ci::API::API do ...@@ -349,35 +319,39 @@ describe Ci::API::API do
end end
end end
describe "DELETE /builds/:id/artifacts" do describe 'DELETE /builds/:id/artifacts' do
before do let(:build) { create(:ci_build, :artifacts) }
build.run! before { delete delete_url, token: build.token }
post delete_url, token: build.token, file: file_upload
end
it "should delete artifact build" do it 'should remove build artifacts' do
build.success
delete delete_url, token: build.token
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(build.artifacts_file.exists?).to be_falsy
expect(build.artifacts_metadata.exists?).to be_falsy
end end
end end
describe "GET /builds/:id/artifacts" do describe 'GET /builds/:id/artifacts' do
before do before { get get_url, token: build.token }
build.run!
context 'build has artifacts' do
let(:build) { create(:ci_build, :artifacts) }
let(:download_headers) do
{ 'Content-Transfer-Encoding'=>'binary',
'Content-Disposition'=>'attachment; filename=ci_build_artifacts.zip' }
end end
it "should download artifact" do it 'should download artifact' do
build.update_attributes(artifacts_file: file_upload)
get get_url, token: build.token
expect(response.status).to eq(200) expect(response.status).to eq(200)
expect(response.headers).to include download_headers
end
end end
it "should fail to download if no artifact uploaded" do context 'build does not has artifacts' do
get get_url, token: build.token it 'should respond with not found' do
expect(response.status).to eq(404) expect(response.status).to eq(404)
end end
end end
end end
end end
end
end end
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