Merge branch 'master' of https://gitlab.com/gitlab-org/gitlab-ce into git-archive-refactor
This commit is contained in:
commit
a0ccb0731b
345 changed files with 5711 additions and 2083 deletions
139
.gitlab-ci.yml
139
.gitlab-ci.yml
|
@ -5,6 +5,11 @@ services:
|
|||
- postgres:latest
|
||||
- redis:latest
|
||||
|
||||
cache:
|
||||
key: "ruby22"
|
||||
paths:
|
||||
- vendor
|
||||
|
||||
variables:
|
||||
MYSQL_ALLOW_EMPTY_PASSWORD: "1"
|
||||
|
||||
|
@ -137,23 +142,145 @@ bundler:audit:
|
|||
|
||||
# Ruby 2.1 jobs
|
||||
|
||||
spec:ruby21:
|
||||
spec:feature:ruby21:
|
||||
image: ruby:2.1
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake assets:precompile 2>/dev/null
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
|
||||
cache:
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
only:
|
||||
- master
|
||||
|
||||
spinach:ruby21:
|
||||
spec:api:ruby21:
|
||||
image: ruby:2.1
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
|
||||
cache:
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:models:ruby21:
|
||||
image: ruby:2.1
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:models
|
||||
cache:
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:lib:ruby21:
|
||||
image: ruby:2.1
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:lib
|
||||
cache:
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:services:ruby21:
|
||||
image: ruby:2.1
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:services
|
||||
cache:
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spec:benchmark:ruby21:
|
||||
image: ruby:2.1
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test bundle exec rake spec:benchmark
|
||||
cache:
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
allow_failure: true
|
||||
|
||||
spec:other:ruby21:
|
||||
image: ruby:2.1
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
|
||||
cache:
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:project:half:ruby21:
|
||||
image: ruby:2.1
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:half
|
||||
cache:
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:project:rest:ruby21:
|
||||
image: ruby:2.1
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project:rest
|
||||
cache:
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
spinach:other:ruby21:
|
||||
image: ruby:2.1
|
||||
only:
|
||||
- master
|
||||
script:
|
||||
- RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
|
||||
cache:
|
||||
key: "ruby21"
|
||||
paths:
|
||||
- vendor
|
||||
tags:
|
||||
- ruby
|
||||
- mysql
|
||||
|
||||
|
|
55
CHANGELOG
55
CHANGELOG
|
@ -1,24 +1,73 @@
|
|||
Please view this file on the master branch, on stable branches it's out of date.
|
||||
|
||||
v 8.5.0 (unreleased)
|
||||
- Fix duplicate "me" in tooltip of the "thumbsup" awards Emoji (Stan Hu)
|
||||
- Cache various Repository methods to improve performance (Yorick Peterse)
|
||||
- Fix duplicated branch creation/deletion Web hooks/service notifications when using Web UI (Stan Hu)
|
||||
- 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 an option to supply root email through an environmental variable (Koichiro Mikami)
|
||||
- Ignore binary files in code search to prevent Error 500 (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
|
||||
- Add option to include the sender name in body of Notify email (Jason Lee)
|
||||
- New UI for pagination
|
||||
- Don't prevent sign out when 2FA enforcement is enabled and user hasn't yet
|
||||
set it up
|
||||
- API: Added "merge_requests/:merge_request_id/closes_issues" (Gal Schlezinger)
|
||||
- Fix diff comments loaded by AJAX to load comment with diff in discussion tab
|
||||
- Fix relative links in other markup formats (Ben Boeckel)
|
||||
- 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
|
||||
- Increase project import timeout to 15 minutes
|
||||
- Be more permissive with email address validation: it only has to contain a single '@'
|
||||
- Display 404 error on group not found
|
||||
- Track project import failure
|
||||
- Support Two-factor Authentication for LDAP users
|
||||
- Display database type and version in Administration dashboard
|
||||
- Allow limited Markdown in Broadcast Messages
|
||||
- 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)
|
||||
- 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
|
||||
- Add `avatar_url`, `description`, `git_ssh_url`, `git_http_url`, `path_with_namespace`
|
||||
and `default_branch` in `project` in push, issue, merge-request and note webhooks data (Kirill Zaitsev)
|
||||
- Deprecate the `ssh_url` in favor of `git_ssh_url` and `http_url` in favor of `git_http_url`
|
||||
in `project` for push, issue, merge-request and note webhooks data (Kirill Zaitsev)
|
||||
- Deprecate the `repository` key in push, issue, merge-request and note webhooks data, use `project` instead (Kirill Zaitsev)
|
||||
- API: Expose MergeRequest#merge_status (Andrei Dziahel)
|
||||
- Revert "Add IP check against DNSBLs at account sign-up"
|
||||
- Actually use the `skip_merges` option in Repository#commits (Tony Chu)
|
||||
- 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/...". 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
|
||||
- Support Akismet spam checking for creation of issues via API (Stan Hu)
|
||||
- API: Allow to set or update a merge-request's milestone (Kirill Skachkov)
|
||||
- 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
|
||||
- Don't process cross-reference notes from forks
|
||||
- Fix: init.d script not working on OS X
|
||||
- Faster snippet search
|
||||
- Title for milestones should be unique (Zeger-Jan van de Weg)
|
||||
- Validate correctness of maximum attachment size application setting
|
||||
- Replaces "Create merge request" link with one to the "Merge Request" when one exists
|
||||
- Fix CI builds badge, add a new link to builds badge, deprecate the old one
|
||||
- Fix broken link to project in build notification emails
|
||||
|
||||
v 8.4.4
|
||||
- Update omniauth-saml gem to 1.4.2
|
||||
- Prevent long-running backup tasks from timing out the database connection
|
||||
- Add a Project setting to allow guests to view build logs (defaults to true)
|
||||
- Sort project milestones by due date including issue editor (Oliver Rogers / Orih)
|
||||
|
||||
v 8.4.3
|
||||
- Increase lfs_objects size column to 8-byte integer to allow files larger
|
||||
|
@ -27,6 +76,9 @@ v 8.4.3
|
|||
- Fix highlighting in blame view
|
||||
- Update sentry-raven gem to prevent "Not a git repository" console output
|
||||
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
|
||||
- Bump required gitlab-workhorse version to bring in a fix for missing
|
||||
|
@ -46,7 +98,6 @@ v 8.4.1
|
|||
and Nokogiri (1.6.7.2)
|
||||
- Fix redirect loop during import
|
||||
- Fix diff highlighting for all syntax themes
|
||||
- Warn admin during OAuth of granting admin rights (Zeger-Jan van de Weg)
|
||||
- Delete project and associations in a background worker
|
||||
|
||||
v 8.4.0
|
||||
|
@ -169,6 +220,7 @@ v 8.3.0
|
|||
- Handle and report SSL errors in Web hook test (Stan Hu)
|
||||
- Bump Redis requirement to 2.8 for Sidekiq 4 (Stan Hu)
|
||||
- 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)
|
||||
- Fix 500 error when update group member permission
|
||||
- Fix: As an admin, cannot add oneself as a member to a group/project
|
||||
|
@ -351,6 +403,7 @@ v 8.1.0
|
|||
- Improved performance of the trending projects page
|
||||
- Remove CI migration task
|
||||
- Improved performance of finding projects by their namespace
|
||||
- Add assignee data to Issuables' hook_data (Bram Daams)
|
||||
- Fix bug where transferring a project would result in stale commit links (Stan Hu)
|
||||
- Fix build trace updating
|
||||
- Include full path of source and target branch names in New Merge Request page (Stan Hu)
|
||||
|
|
159
CONTRIBUTING.md
159
CONTRIBUTING.md
|
@ -1,3 +1,29 @@
|
|||
<!-- 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
|
||||
|
||||
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.
|
|||
issues or chunks. You can simply not set the weight of a parent issue and set
|
||||
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
|
||||
|
||||
We welcome merge requests with fixes and improvements to GitLab code, tests,
|
||||
|
@ -214,15 +260,17 @@ request is as follows:
|
|||
1. Add your changes to the [CHANGELOG](CHANGELOG)
|
||||
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
|
||||
and make sure to read the [documentation styleguide][doc-styleguide]
|
||||
1. If you have multiple commits please combine them into one commit by
|
||||
[squashing them][git-squash]
|
||||
1. Push the commit(s) to your fork
|
||||
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 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 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`
|
||||
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
|
||||
|
@ -255,7 +303,68 @@ 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][].
|
||||
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.
|
||||
|
||||
### Merge request description format
|
||||
|
||||
Please submit merge requests using the following template in the merge request
|
||||
description area. Copy-paste it to retain the markdown format.
|
||||
|
||||
```
|
||||
## What does this MR do?
|
||||
|
||||
## Are there points in the code the reviewer needs to double check?
|
||||
|
||||
## Why was this MR needed?
|
||||
|
||||
## What are the relevant issue numbers?
|
||||
|
||||
## Screenshots (if relevant)
|
||||
```
|
||||
|
||||
### Contribution acceptance criteria
|
||||
|
||||
1. The change is as small as possible
|
||||
1. Include proper tests and make all tests pass (unless it contains a test
|
||||
exposing a bug in existing code)
|
||||
1. If you suspect a failing CI build is unrelated to your contribution, you may
|
||||
try and restart the failing CI job or ask a developer to fix the
|
||||
aforementioned failing test
|
||||
1. Your MR initially contains a single commit (please use `git rebase -i` to
|
||||
squash commits)
|
||||
1. Your changes can merge without problems (if not please merge `master`, never
|
||||
rebase commits pushed to the remote server)
|
||||
1. Does not break any existing functionality
|
||||
1. Fixes one specific issue or implements one specific feature (do not combine
|
||||
things, send separate merge requests if needed)
|
||||
1. Migrations should do only one thing (e.g., either create a table, move data
|
||||
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. Contains functionality we think other users will benefit from too
|
||||
1. Doesn't add configuration options since they complicate future changes
|
||||
1. Changes after submitting the merge request should be in separate commits
|
||||
(no squashing). If necessary, you will be asked to squash when the review is
|
||||
over, before merging.
|
||||
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
|
||||
entire line to follow it. This prevents linting tools from generating warnings.
|
||||
- Don't touch neighbouring lines. As an exception, automatic mass
|
||||
refactoring modifications may leave style non-compliant.
|
||||
|
||||
## 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.
|
||||
Like all merge requests the target should be master so all bugfixes are in master.
|
||||
|
||||
## Definition of done
|
||||
|
||||
|
@ -266,7 +375,7 @@ 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 in the /doc directory
|
||||
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
|
||||
|
@ -287,43 +396,6 @@ merge request:
|
|||
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
|
||||
|
||||
1. What does this MR do?
|
||||
1. Are there points in the code the reviewer needs to double check?
|
||||
1. Why was this MR needed?
|
||||
1. What are the relevant issue numbers?
|
||||
1. Screenshots (if relevant)
|
||||
|
||||
## Contribution acceptance criteria
|
||||
|
||||
1. The change is as small as possible (see the above paragraph for details)
|
||||
1. Include proper tests and make all tests pass (unless it contains a test
|
||||
exposing a bug in existing code)
|
||||
1. If you suspect a failing CI build is unrelated to your contribution, you may
|
||||
try and restart the failing CI job or ask a developer to fix the
|
||||
aforementioned failing test
|
||||
1. Your MR initially contains a single commit (please use `git rebase -i` to
|
||||
squash commits)
|
||||
1. Your changes can merge without problems (if not please merge `master`, never
|
||||
rebase commits pushed to the remote server)
|
||||
1. Does not break any existing functionality
|
||||
1. Fixes one specific issue or implements one specific feature (do not combine
|
||||
things, send separate merge requests if needed)
|
||||
1. Migrations should do only one thing (eg: either create a table, move data 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. Contains functionality we think other users will benefit from too
|
||||
1. Doesn't add configuration options since they complicate future changes
|
||||
1. Changes after submitting the merge request should be in separate commits
|
||||
(no squashing). If necessary, you will be asked to squash when the review is
|
||||
over, before merging.
|
||||
1. It conforms to the following style guides:
|
||||
* If your change touches a line that does not follow the style, modify the
|
||||
entire line to follow it. This prevents linting tools from generating warnings.
|
||||
* Don't touch neighbouring lines. As an exception, automatic mass
|
||||
refactoring modifications may leave style non-compliant.
|
||||
|
||||
## Style guides
|
||||
|
||||
1. [Ruby](https://github.com/bbatsov/ruby-style-guide).
|
||||
|
@ -338,7 +410,7 @@ merge request:
|
|||
contributors to enhance security
|
||||
1. [Database Migrations](doc/development/migration_style_guide.md)
|
||||
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
|
||||
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
|
||||
|
@ -379,7 +451,7 @@ reported by emailing `contact@gitlab.com`.
|
|||
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/).
|
||||
|
||||
[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/
|
||||
[Codetriage]: http://www.codetriage.com/gitlabhq/gitlabhq
|
||||
[up-for-grabs]: https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=up-for-grabs
|
||||
|
@ -400,3 +472,4 @@ available at [http://contributor-covenant.org/version/1/1/0/](http://contributor
|
|||
[Contributor Covenant]: http://contributor-covenant.org
|
||||
[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
|
||||
[doc-styleguide]: doc/development/doc_styleguide.md "Documentation styleguide"
|
||||
|
|
|
@ -1 +1 @@
|
|||
0.6.2
|
||||
0.6.4
|
||||
|
|
17
Gemfile
17
Gemfile
|
@ -21,7 +21,7 @@ gem "pg", '~> 0.18.2', group: :postgres
|
|||
gem 'devise', '~> 3.5.4'
|
||||
gem 'devise-async', '~> 0.9.0'
|
||||
gem 'doorkeeper', '~> 2.2.0'
|
||||
gem 'omniauth', '~> 1.2.2'
|
||||
gem 'omniauth', '~> 1.3.1'
|
||||
gem 'omniauth-azure-oauth2', '~> 0.0.6'
|
||||
gem 'omniauth-bitbucket', '~> 0.0.2'
|
||||
gem 'omniauth-cas3', '~> 1.1.2'
|
||||
|
@ -30,14 +30,15 @@ gem 'omniauth-github', '~> 1.1.1'
|
|||
gem 'omniauth-gitlab', '~> 1.0.0'
|
||||
gem 'omniauth-google-oauth2', '~> 0.2.0'
|
||||
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-twitter', '~> 1.2.0'
|
||||
gem 'omniauth_crowd', '~> 2.2.0'
|
||||
gem 'rack-oauth2', '~> 1.2.1'
|
||||
|
||||
# reCAPTCHA protection
|
||||
# Spam and anti-bot protection
|
||||
gem 'recaptcha', require: 'recaptcha/rails'
|
||||
gem 'akismet', '~> 2.0'
|
||||
|
||||
# Two-factor authentication
|
||||
gem 'devise-two-factor', '~> 2.0.0'
|
||||
|
@ -49,7 +50,7 @@ gem "browser", '~> 1.0.0'
|
|||
|
||||
# Extracting information from a git repository
|
||||
# Provide access to Gitlab::Git library
|
||||
gem "gitlab_git", '~> 7.2.23'
|
||||
gem "gitlab_git", '~> 8.1'
|
||||
|
||||
# LDAP Auth
|
||||
# GitLab fork with several improvements to original library. For full list of changes
|
||||
|
@ -104,7 +105,7 @@ gem 'rouge', '~> 1.10.1'
|
|||
|
||||
# See https://groups.google.com/forum/#!topic/ruby-security-ann/aSbgDiwb24s
|
||||
# and https://groups.google.com/forum/#!topic/ruby-security-ann/Dy7YiKb_pMM
|
||||
gem 'nokogiri', '1.6.7.2'
|
||||
gem 'nokogiri', '~> 1.6.7', '>= 1.6.7.2'
|
||||
|
||||
# Diffs
|
||||
gem 'diffy', '~> 3.0.3'
|
||||
|
@ -179,6 +180,9 @@ gem "underscore-rails", "~> 1.8.0"
|
|||
gem "sanitize", '~> 2.0'
|
||||
gem 'babosa', '~> 1.0.2'
|
||||
|
||||
# Sanitizes SVG input
|
||||
gem "loofah", "~> 2.0.3"
|
||||
|
||||
# Protect against bruteforcing
|
||||
gem "rack-attack", '~> 4.3.1'
|
||||
|
||||
|
@ -299,9 +303,6 @@ group :production do
|
|||
gem "gitlab_meta", '7.0'
|
||||
end
|
||||
|
||||
gem "newrelic_rpm", '~> 3.9.4.245'
|
||||
gem 'newrelic-grape'
|
||||
|
||||
gem 'octokit', '~> 3.8.0'
|
||||
|
||||
gem "mail_room", "~> 0.6.1"
|
||||
|
|
31
Gemfile.lock
31
Gemfile.lock
|
@ -49,7 +49,8 @@ GEM
|
|||
addressable (2.3.8)
|
||||
after_commit_queue (1.3.0)
|
||||
activerecord (>= 3.0)
|
||||
allocations (1.0.3)
|
||||
akismet (2.0.0)
|
||||
allocations (1.0.4)
|
||||
annotate (2.6.10)
|
||||
activerecord (>= 3.2, <= 4.3)
|
||||
rake (~> 10.4)
|
||||
|
@ -356,7 +357,7 @@ GEM
|
|||
posix-spawn (~> 0.3)
|
||||
gitlab_emoji (0.2.0)
|
||||
gemojione (~> 2.1)
|
||||
gitlab_git (7.2.24)
|
||||
gitlab_git (8.1.0)
|
||||
activesupport (~> 4.0)
|
||||
charlock_holmes (~> 0.7.3)
|
||||
github-linguist (~> 4.7.0)
|
||||
|
@ -478,10 +479,6 @@ GEM
|
|||
net-ldap (0.12.1)
|
||||
net-ssh (3.0.1)
|
||||
netrc (0.11.0)
|
||||
newrelic-grape (2.1.0)
|
||||
grape
|
||||
newrelic_rpm
|
||||
newrelic_rpm (3.9.4.245)
|
||||
nokogiri (1.6.7.2)
|
||||
mini_portile2 (~> 2.0.0.rc2)
|
||||
nprogress-rails (0.1.6.7)
|
||||
|
@ -494,9 +491,9 @@ GEM
|
|||
rack (~> 1.2)
|
||||
octokit (3.8.0)
|
||||
sawyer (~> 0.6.0, >= 0.5.3)
|
||||
omniauth (1.2.2)
|
||||
omniauth (1.3.1)
|
||||
hashie (>= 1.2, < 4)
|
||||
rack (~> 1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
omniauth-azure-oauth2 (0.0.6)
|
||||
jwt (~> 1.0)
|
||||
omniauth (~> 1.0)
|
||||
|
@ -534,9 +531,9 @@ GEM
|
|||
omniauth-oauth2 (1.3.1)
|
||||
oauth2 (~> 1.0)
|
||||
omniauth (~> 1.2)
|
||||
omniauth-saml (1.4.1)
|
||||
omniauth-saml (1.4.2)
|
||||
omniauth (~> 1.1)
|
||||
ruby-saml (~> 1.0.0)
|
||||
ruby-saml (~> 1.1, >= 1.1.1)
|
||||
omniauth-shibboleth (1.2.1)
|
||||
omniauth (>= 1.0.0)
|
||||
omniauth-twitter (1.2.1)
|
||||
|
@ -692,7 +689,7 @@ GEM
|
|||
ruby-fogbugz (0.2.1)
|
||||
crack (~> 0.4)
|
||||
ruby-progressbar (1.7.5)
|
||||
ruby-saml (1.0.0)
|
||||
ruby-saml (1.1.1)
|
||||
nokogiri (>= 1.5.10)
|
||||
uuid (~> 2.3)
|
||||
ruby2ruby (2.2.0)
|
||||
|
@ -884,6 +881,7 @@ DEPENDENCIES
|
|||
acts-as-taggable-on (~> 3.4)
|
||||
addressable (~> 2.3.8)
|
||||
after_commit_queue
|
||||
akismet (~> 2.0)
|
||||
allocations (~> 1.0)
|
||||
annotate (~> 2.6.0)
|
||||
asana (~> 0.4.0)
|
||||
|
@ -934,7 +932,7 @@ DEPENDENCIES
|
|||
github-markup (~> 1.3.1)
|
||||
gitlab-flowdock-git-hook (~> 1.0.1)
|
||||
gitlab_emoji (~> 0.2.0)
|
||||
gitlab_git (~> 7.2.23)
|
||||
gitlab_git (~> 8.1)
|
||||
gitlab_meta (= 7.0)
|
||||
gitlab_omniauth-ldap (~> 1.2.1)
|
||||
gollum-lib (~> 4.1.0)
|
||||
|
@ -953,6 +951,7 @@ DEPENDENCIES
|
|||
jquery-ui-rails (~> 5.0.0)
|
||||
kaminari (~> 0.16.3)
|
||||
letter_opener (~> 1.1.2)
|
||||
loofah (~> 2.0.3)
|
||||
mail_room (~> 0.6.1)
|
||||
method_source (~> 0.8)
|
||||
minitest (~> 5.7.0)
|
||||
|
@ -960,13 +959,11 @@ DEPENDENCIES
|
|||
mysql2 (~> 0.3.16)
|
||||
nested_form (~> 0.3.2)
|
||||
net-ssh (~> 3.0.1)
|
||||
newrelic-grape
|
||||
newrelic_rpm (~> 3.9.4.245)
|
||||
nokogiri (= 1.6.7.2)
|
||||
nokogiri (~> 1.6.7, >= 1.6.7.2)
|
||||
nprogress-rails (~> 0.1.6.7)
|
||||
oauth2 (~> 1.0.0)
|
||||
octokit (~> 3.8.0)
|
||||
omniauth (~> 1.2.2)
|
||||
omniauth (~> 1.3.1)
|
||||
omniauth-azure-oauth2 (~> 0.0.6)
|
||||
omniauth-bitbucket (~> 0.0.2)
|
||||
omniauth-cas3 (~> 1.1.2)
|
||||
|
@ -975,7 +972,7 @@ DEPENDENCIES
|
|||
omniauth-gitlab (~> 1.0.0)
|
||||
omniauth-google-oauth2 (~> 0.2.0)
|
||||
omniauth-kerberos (~> 0.3.0)
|
||||
omniauth-saml (~> 1.4.0)
|
||||
omniauth-saml (~> 1.4.2)
|
||||
omniauth-shibboleth (~> 1.2.0)
|
||||
omniauth-twitter (~> 1.2.0)
|
||||
omniauth_crowd (~> 2.2.0)
|
||||
|
|
|
@ -12,19 +12,6 @@ class @Admin
|
|||
e.preventDefault()
|
||||
$('.js-toggle-colors-container').toggle()
|
||||
|
||||
$('input#broadcast_message_color').on 'input', ->
|
||||
previewColor = $(@).val()
|
||||
$('div.broadcast-message-preview').css('background-color', previewColor)
|
||||
|
||||
$('input#broadcast_message_font').on 'input', ->
|
||||
previewColor = $(@).val()
|
||||
$('div.broadcast-message-preview').css('color', previewColor)
|
||||
|
||||
$('textarea#broadcast_message_message').on 'input', ->
|
||||
previewMessage = $(@).val()
|
||||
previewMessage = "Your message here" if previewMessage.trim() == ''
|
||||
$('div.broadcast-message-preview span').text(previewMessage)
|
||||
|
||||
$('.log-tabs a').click (e) ->
|
||||
e.preventDefault()
|
||||
$(this).tab('show')
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
callback(namespaces)
|
||||
|
||||
# Return projects list. Filtered by query
|
||||
projects: (query, callback) ->
|
||||
projects: (query, order, callback) ->
|
||||
url = Api.buildUrl(Api.projects_path)
|
||||
|
||||
$.ajax(
|
||||
|
@ -55,6 +55,7 @@
|
|||
data:
|
||||
private_token: gon.api_token
|
||||
search: query
|
||||
order_by: order
|
||||
per_page: 20
|
||||
dataType: "json"
|
||||
).done (projects) ->
|
||||
|
|
|
@ -211,8 +211,89 @@ $ ->
|
|||
$this.attr 'value', $this.val()
|
||||
return
|
||||
|
||||
$(document).on 'keyup', 'input[type="search"]' , (e) ->
|
||||
$this = $(this)
|
||||
$this.attr 'value', $this.val()
|
||||
$(document)
|
||||
.off 'keyup', 'input[type="search"]'
|
||||
.on 'keyup', 'input[type="search"]' , (e) ->
|
||||
$this = $(this)
|
||||
$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()
|
||||
|
|
|
@ -49,10 +49,11 @@ class @AwardsHandler
|
|||
counter.text(parseInt(counter.text()) - 1)
|
||||
emojiIcon.removeClass("active")
|
||||
@removeMeFromAuthorList(emoji)
|
||||
else if emoji =="thumbsup" || emoji == "thumbsdown"
|
||||
else if emoji == "thumbsup" || emoji == "thumbsdown"
|
||||
emojiIcon.tooltip("destroy")
|
||||
counter.text(0)
|
||||
emojiIcon.removeClass("active")
|
||||
@removeMeFromAuthorList(emoji)
|
||||
else
|
||||
emojiIcon.tooltip("destroy")
|
||||
emojiIcon.remove()
|
||||
|
|
|
@ -1,4 +1,22 @@
|
|||
#= require jquery.ba-resize
|
||||
#= require autosize
|
||||
|
||||
$ ->
|
||||
autosize($('.js-autosize'))
|
||||
$fields = $('.js-autosize')
|
||||
|
||||
$fields.on 'autosize:resized', ->
|
||||
$field = $(@)
|
||||
$field.data('height', $field.outerHeight())
|
||||
|
||||
$fields.on 'resize.autosize', ->
|
||||
$field = $(@)
|
||||
|
||||
if $field.data('height') != $field.outerHeight()
|
||||
$field.data('height', $field.outerHeight())
|
||||
autosize.destroy($field)
|
||||
$field.css('max-height', window.outerHeight)
|
||||
|
||||
autosize($fields)
|
||||
autosize.update($fields)
|
||||
|
||||
$fields.css('resize', 'vertical')
|
||||
|
|
22
app/assets/javascripts/broadcast_message.js.coffee
Normal file
22
app/assets/javascripts/broadcast_message.js.coffee
Normal file
|
@ -0,0 +1,22 @@
|
|||
$ ->
|
||||
$('input#broadcast_message_color').on 'input', ->
|
||||
previewColor = $(@).val()
|
||||
$('div.broadcast-message-preview').css('background-color', previewColor)
|
||||
|
||||
$('input#broadcast_message_font').on 'input', ->
|
||||
previewColor = $(@).val()
|
||||
$('div.broadcast-message-preview').css('color', previewColor)
|
||||
|
||||
previewPath = $('textarea#broadcast_message_message').data('preview-path')
|
||||
|
||||
$('textarea#broadcast_message_message').on 'input', ->
|
||||
message = $(@).val()
|
||||
|
||||
if message == ''
|
||||
$('.js-broadcast-message-preview').text("Your message here")
|
||||
else
|
||||
$.ajax(
|
||||
url: previewPath
|
||||
type: "POST"
|
||||
data: { broadcast_message: { message: message } }
|
||||
)
|
|
@ -1,3 +1,31 @@
|
|||
class @Dashboard
|
||||
constructor: ->
|
||||
new ProjectsList()
|
||||
@Dashboard =
|
||||
init: ->
|
||||
$(".projects-list-filter").off('keyup')
|
||||
this.initSearch()
|
||||
|
||||
initSearch: ->
|
||||
@timer = null
|
||||
$(".projects-list-filter").on('keyup', ->
|
||||
clearTimeout(@timer)
|
||||
@timer = setTimeout(Dashboard.filterResults, 500)
|
||||
)
|
||||
|
||||
filterResults: =>
|
||||
$('.projects-list-holder').fadeTo(250, 0.5)
|
||||
|
||||
form = null
|
||||
form = $("form#project-filter-form")
|
||||
search = $(".projects-list-filter").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) ->
|
||||
$('.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"
|
||||
|
|
|
@ -16,6 +16,8 @@ class Dispatcher
|
|||
shortcut_handler = null
|
||||
|
||||
switch page
|
||||
when 'explore:projects:index', 'explore:projects:starred', 'explore:projects:trending'
|
||||
Dashboard.init()
|
||||
when 'projects:issues:index'
|
||||
Issues.init()
|
||||
shortcut_handler = new ShortcutsNavigation()
|
||||
|
@ -58,7 +60,7 @@ class Dispatcher
|
|||
shortcut_handler = new ShortcutsNavigation()
|
||||
MergeRequests.init()
|
||||
when 'dashboard:show', 'root:show'
|
||||
new Dashboard()
|
||||
Dashboard.init()
|
||||
when 'dashboard:activity'
|
||||
new Activities()
|
||||
when 'dashboard:projects:starred'
|
||||
|
|
|
@ -65,8 +65,7 @@ class @DropzoneInput
|
|||
return
|
||||
|
||||
success: (header, response) ->
|
||||
child = $(dropzone[0]).children("textarea")
|
||||
$(child).val $(child).val() + response.link.markdown + "\n"
|
||||
pasteText response.link.markdown
|
||||
return
|
||||
|
||||
error: (temp, errorMessage) ->
|
||||
|
@ -128,6 +127,7 @@ class @DropzoneInput
|
|||
beforeSelection = $(child).val().substring 0, caretStart
|
||||
afterSelection = $(child).val().substring caretEnd, textEnd
|
||||
$(child).val beforeSelection + text + afterSelection
|
||||
child.get(0).setSelectionRange caretStart + text.length, caretEnd + text.length
|
||||
form_textarea.trigger "input"
|
||||
|
||||
getFilename = (e) ->
|
||||
|
|
|
@ -10,19 +10,7 @@ class @IssuableContext
|
|||
$(".issuable-sidebar .inline-update").on "change", ".js-assignee", ->
|
||||
$(this).submit()
|
||||
|
||||
$('.issuable-details').waitForImages ->
|
||||
$('.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) ->
|
||||
$(document).on "click",".edit-link", (e) ->
|
||||
block = $(@).parents('.block')
|
||||
block.find('.selectbox').show()
|
||||
block.find('.value').hide()
|
||||
|
|
|
@ -42,3 +42,9 @@ work = ->
|
|||
|
||||
$(document).on('page:fetch', start)
|
||||
$(document).on('page:change', stop)
|
||||
|
||||
$ ->
|
||||
# Make logo clickable as part of a workaround for Safari visited
|
||||
# link behaviour (See !2690).
|
||||
$('#logo').on 'click', ->
|
||||
$('#js-shortcuts-home').get(0).click()
|
||||
|
|
|
@ -50,3 +50,19 @@ class @Project
|
|||
$('#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'
|
||||
$(@).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
|
|||
$('.ajax-project-select').each (i, select) ->
|
||||
@groupId = $(select).data('group-id')
|
||||
@includeGroups = $(select).data('include-groups')
|
||||
@orderBy = $(select).data('order-by') || 'id'
|
||||
|
||||
placeholder = "Search for project"
|
||||
placeholder += " or group" if @includeGroups
|
||||
|
@ -28,7 +29,7 @@ class @ProjectSelect
|
|||
if @groupId
|
||||
Api.groupProjects @groupId, query.term, projectsCallback
|
||||
else
|
||||
Api.projects query.term, projectsCallback
|
||||
Api.projects query.term, @orderBy, projectsCallback
|
||||
|
||||
id: (project) ->
|
||||
project.web_url
|
||||
|
|
|
@ -3,24 +3,24 @@ class @ProjectsList
|
|||
$(".projects-list .js-expand").on 'click', (e) ->
|
||||
e.preventDefault()
|
||||
list = $(this).closest('.projects-list')
|
||||
list.find("li").show()
|
||||
list.find("li.bottom").hide()
|
||||
|
||||
$(".projects-list-filter").keyup ->
|
||||
terms = $(this).val()
|
||||
uiBox = $('div.projects-list-holder')
|
||||
filterSelector = $(this).data('filter-selector') || 'span.filter-title'
|
||||
$("#filter_projects").on 'keyup', ->
|
||||
ProjectsList.filter_results($("#filter_projects"))
|
||||
|
||||
if terms == "" || terms == undefined
|
||||
uiBox.find("ul.projects-list li").show()
|
||||
else
|
||||
uiBox.find("ul.projects-list li").each (index) ->
|
||||
name = $(this).find(filterSelector).text()
|
||||
|
||||
if name.toLowerCase().search(terms.toLowerCase()) == -1
|
||||
$(this).hide()
|
||||
else
|
||||
$(this).show()
|
||||
uiBox.find("ul.projects-list li.bottom").hide()
|
||||
@filter_results: ($element) ->
|
||||
terms = $element.val()
|
||||
filterSelector = $element.data('filter-selector') || 'span.filter-title'
|
||||
|
||||
if not terms
|
||||
$(".projects-list li").show()
|
||||
$('.gl-pagination').show()
|
||||
else
|
||||
$(".projects-list li").each (index) ->
|
||||
$this = $(this)
|
||||
name = $this.find(filterSelector).text()
|
||||
|
||||
if name.toLowerCase().indexOf(terms.toLowerCase()) == -1
|
||||
$this.hide()
|
||||
else
|
||||
$this.show()
|
||||
$('.gl-pagination').hide()
|
||||
|
|
|
@ -16,12 +16,31 @@ class @ShortcutsIssuable extends ShortcutsNavigation
|
|||
@replyWithSelectedText()
|
||||
return false
|
||||
)
|
||||
Mousetrap.bind('j', =>
|
||||
@prevIssue()
|
||||
return false
|
||||
)
|
||||
Mousetrap.bind('k', =>
|
||||
@nextIssue()
|
||||
return false
|
||||
)
|
||||
|
||||
|
||||
if isMergeRequest
|
||||
@enabledHelp.push('.hidden-shortcut.merge_requests')
|
||||
else
|
||||
@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: ->
|
||||
if window.getSelection
|
||||
selected = window.getSelection().toString()
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
&.s26 { width: 26px; height: 26px; margin-right: 8px; }
|
||||
&.s32 { width: 32px; height: 32px; 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; }
|
||||
&.s48 { width: 48px; height: 48px; margin-right: 10px; }
|
||||
&.s60 { width: 60px; height: 60px; margin-right: 12px; }
|
||||
|
@ -40,7 +41,8 @@
|
|||
&.s16 { font-size: 12px; line-height: 1.33; }
|
||||
&.s24 { font-size: 14px; line-height: 1.8; }
|
||||
&.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; }
|
||||
&.s90 { font-size: 36px; line-height: 90px; }
|
||||
&.s110 { font-size: 40px; line-height: 112px; font-weight: 300; }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
@include border-radius(3px);
|
||||
font-size: $gl-font-size;
|
||||
font-weight: 500;
|
||||
padding: $gl-vert-padding $gl-padding;
|
||||
padding: $gl-vert-padding $gl-btn-padding;
|
||||
|
||||
&:focus,
|
||||
&:active {
|
||||
|
@ -82,8 +82,7 @@
|
|||
&.btn-success,
|
||||
&.btn-new,
|
||||
&.btn-create,
|
||||
&.btn-save,
|
||||
&.btn-green {
|
||||
&.btn-save {
|
||||
@include btn-green;
|
||||
}
|
||||
|
||||
|
@ -159,7 +158,6 @@
|
|||
|
||||
.input-group-btn {
|
||||
.btn {
|
||||
@include btn-gray;
|
||||
@include btn-middle;
|
||||
|
||||
&:hover {
|
||||
|
@ -186,8 +184,4 @@
|
|||
border: 1px solid #c6cacf !important;
|
||||
background-color: #e4e7ed !important;
|
||||
}
|
||||
|
||||
.btn-green {
|
||||
@include btn-green
|
||||
}
|
||||
}
|
||||
|
|
|
@ -376,11 +376,11 @@ table {
|
|||
margin-bottom: $gl-padding;
|
||||
}
|
||||
|
||||
.new-project-item-select-holder {
|
||||
.project-item-select-holder {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
||||
.new-project-item-select {
|
||||
.project-item-select {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
|
|
|
@ -73,7 +73,6 @@ header {
|
|||
|
||||
.title {
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
font-size: 19px;
|
||||
line-height: $header-height;
|
||||
font-weight: normal;
|
||||
|
@ -88,6 +87,22 @@ header {
|
|||
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 {
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
display: block;
|
||||
float: left;
|
||||
padding: 0 $gl-padding;
|
||||
padding: 0 $gl-btn-padding;
|
||||
font-weight: normal;
|
||||
margin-right: 10px;
|
||||
font-size: $gl-font-size;
|
||||
|
|
|
@ -109,7 +109,6 @@ ul.content-list {
|
|||
padding: 0;
|
||||
|
||||
> li {
|
||||
padding: $gl-padding 0;
|
||||
border-color: $table-border-color;
|
||||
color: $gl-gray;
|
||||
|
||||
|
|
|
@ -83,7 +83,7 @@
|
|||
background: #FFF;
|
||||
border: 1px solid #ddd;
|
||||
min-height: 140px;
|
||||
max-height: 430px;
|
||||
max-height: 500px;
|
||||
padding: 5px;
|
||||
box-shadow: none;
|
||||
width: 100%;
|
||||
|
|
|
@ -116,7 +116,7 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
aside {
|
||||
aside:not(.right-sidebar){
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 @@
|
|||
overflow: hidden;
|
||||
transition-duration: .3s;
|
||||
|
||||
.home {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
}
|
||||
|
||||
#logo {
|
||||
z-index: 2;
|
||||
position: absolute;
|
||||
width: 58px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a {
|
||||
float: left;
|
||||
height: $header-height;
|
||||
|
@ -70,7 +83,7 @@
|
|||
width: 158px;
|
||||
float: left;
|
||||
margin: 0;
|
||||
margin-left: 14px;
|
||||
margin-left: 50px;
|
||||
font-size: 19px;
|
||||
line-height: 41px;
|
||||
font-weight: normal;
|
||||
|
@ -200,6 +213,14 @@
|
|||
}
|
||||
}
|
||||
|
||||
@mixin expanded-gutter {
|
||||
padding-right: $gutter_width;
|
||||
}
|
||||
|
||||
@mixin collapsed-gutter {
|
||||
padding-right: $sidebar_collapsed_width;
|
||||
}
|
||||
|
||||
@mixin collapsed-sidebar {
|
||||
padding-left: $sidebar_collapsed_width;
|
||||
|
||||
|
@ -266,6 +287,7 @@
|
|||
background: #f2f6f7;
|
||||
}
|
||||
|
||||
// page is small enough
|
||||
@media (max-width: $screen-md-max) {
|
||||
.page-sidebar-collapsed {
|
||||
@include collapsed-sidebar;
|
||||
|
@ -275,12 +297,32 @@
|
|||
@include collapsed-sidebar;
|
||||
}
|
||||
|
||||
.page-gutter {
|
||||
&.right-sidebar-collapsed {
|
||||
@include collapsed-gutter;
|
||||
}
|
||||
&.right-sidebar-expanded {
|
||||
@include expanded-gutter;
|
||||
}
|
||||
}
|
||||
|
||||
.collapse-nav {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
// page is large enough
|
||||
@media(min-width: $screen-md-max) {
|
||||
|
||||
.page-gutter {
|
||||
&.right-sidebar-collapsed {
|
||||
@include collapsed-gutter;
|
||||
}
|
||||
&.right-sidebar-expanded {
|
||||
@include expanded-gutter;
|
||||
}
|
||||
}
|
||||
|
||||
.page-sidebar-collapsed {
|
||||
@include collapsed-sidebar;
|
||||
}
|
||||
|
@ -288,4 +330,4 @@
|
|||
.page-sidebar-expanded {
|
||||
@include expanded-sidebar;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -12,6 +12,9 @@ $gl-font-size: 15px;
|
|||
$list-font-size: 15px;
|
||||
$sidebar_collapsed_width: 62px;
|
||||
$sidebar_width: 230px;
|
||||
$gutter_collapsed_width: 62px;
|
||||
$gutter_width: 290px;
|
||||
$gutter_inner_width: 258px;
|
||||
$avatar_radius: 50%;
|
||||
$code_font_size: 13px;
|
||||
$code_line_height: 1.5;
|
||||
|
@ -22,9 +25,10 @@ $header-height: 58px;
|
|||
$fixed-layout-width: 1280px;
|
||||
$gl-gray: #5a5a5a;
|
||||
$gl-padding: 16px;
|
||||
$gl-btn-padding: 10px;
|
||||
$gl-vert-padding: 6px;
|
||||
$gl-padding-top:10px;
|
||||
$gl-avatar-size: 46px;
|
||||
$gl-avatar-size: 40px;
|
||||
$secondary-text: #7f8fa4;
|
||||
$error-exclamation-point: #E62958;
|
||||
|
||||
|
@ -36,11 +40,12 @@ $white-light: #FFFFFF;
|
|||
$white-normal: #ededed;
|
||||
$white-dark: #ededed;
|
||||
|
||||
$gray-light: #f7f7f7;
|
||||
$gray-normal: #ededed;
|
||||
$gray-light: #faf9f9;
|
||||
$gray-normal: #f5f5f5;
|
||||
$gray-dark: #ededed;
|
||||
$gray-darkest: #c9c9c9;
|
||||
|
||||
$green-light: #31AF64;
|
||||
$green-light: #38ae67;
|
||||
$green-normal: #2FAA60;
|
||||
$green-dark: #2CA05B;
|
||||
|
||||
|
@ -52,7 +57,7 @@ $blue-medium-light: #3498CB;
|
|||
$blue-medium: #2F8EBF;
|
||||
$blue-medium-dark: #2D86B4;
|
||||
|
||||
$orange-light: #FC6443;
|
||||
$orange-light: rgba(252, 109, 38, 0.80);
|
||||
$orange-normal: #E75E40;
|
||||
$orange-dark: #CE5237;
|
||||
|
||||
|
@ -64,8 +69,8 @@ $border-white-light: #F1F2F4;
|
|||
$border-white-normal: #D6DAE2;
|
||||
$border-white-dark: #C6CACF;
|
||||
|
||||
$border-gray-light: #d1d1d1;
|
||||
$border-gray-normal: #D6DAE2;
|
||||
$border-gray-light: rgba(0, 0, 0, 0.06);
|
||||
$border-gray-normal: rgba(0, 0, 0, 0.10);;
|
||||
$border-gray-dark: #C6CACF;
|
||||
|
||||
$border-green-light: #2FAA60;
|
||||
|
@ -76,7 +81,7 @@ $border-blue-light: #2D9FD8;
|
|||
$border-blue-normal: #2897CE;
|
||||
$border-blue-dark: #258DC1;
|
||||
|
||||
$border-orange-light: #ED5C3D;
|
||||
$border-orange-light: #fc6d26;
|
||||
$border-orange-normal: #CE5237;
|
||||
$border-orange-dark: #C14E35;
|
||||
|
||||
|
|
|
@ -55,6 +55,16 @@
|
|||
@extend .alert-warning;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
|
||||
> div, p {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.broadcast-message-preview {
|
||||
|
|
|
@ -40,10 +40,6 @@
|
|||
.avatar {
|
||||
@include border-radius(50%);
|
||||
}
|
||||
|
||||
.identicon {
|
||||
line-height: 46px;
|
||||
}
|
||||
}
|
||||
|
||||
.dash-project-access-icon {
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
*/
|
||||
.event-item {
|
||||
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;
|
||||
color: #7f8fa4;
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
|||
|
||||
.event-title,
|
||||
.event-item-timestamp {
|
||||
line-height: 44px;
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@
|
|||
}
|
||||
|
||||
.avatar {
|
||||
margin-left: -($gl-avatar-size + 15px);
|
||||
margin-left: -($gl-avatar-size + $gl-padding-top);
|
||||
}
|
||||
|
||||
.event-title {
|
||||
|
@ -41,7 +41,6 @@
|
|||
margin-right: 174px;
|
||||
|
||||
.event-note {
|
||||
margin-top: 5px;
|
||||
word-wrap: break-word;
|
||||
|
||||
.md {
|
||||
|
@ -98,8 +97,6 @@
|
|||
&:last-child { border:none }
|
||||
|
||||
.event_commits {
|
||||
margin-top: 9px;
|
||||
|
||||
li {
|
||||
&.commit {
|
||||
background: transparent;
|
||||
|
|
|
@ -6,11 +6,3 @@
|
|||
font-size: 30px;
|
||||
}
|
||||
}
|
||||
|
||||
.explore-trending-block {
|
||||
.lead {
|
||||
line-height: 32px;
|
||||
font-size: 18px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
input[type='search'] {
|
||||
width: 225px;
|
||||
vertical-align: bottom;
|
||||
|
||||
|
||||
@media (max-width: $screen-xs-max) {
|
||||
width: 100px;
|
||||
vertical-align: bottom;
|
||||
|
@ -21,3 +21,21 @@
|
|||
height: 42px;
|
||||
}
|
||||
}
|
||||
|
||||
.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 @@
|
|||
}
|
||||
}
|
||||
|
||||
.project-issuable-filter {
|
||||
.controls {
|
||||
float: right;
|
||||
margin-top: 11px;
|
||||
}
|
||||
|
||||
.nav-links {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.issuable-details {
|
||||
section {
|
||||
border-right: 1px solid $border-white-light;
|
||||
|
||||
.issuable-discussion {
|
||||
margin-right: 1px;
|
||||
}
|
||||
|
@ -73,11 +60,43 @@
|
|||
.block {
|
||||
@include clearfix;
|
||||
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 {
|
||||
border: none;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-top: 7px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.select2-container span {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.issuable-count {
|
||||
|
||||
}
|
||||
|
||||
.gutter-toggle {
|
||||
margin-left: 20px;
|
||||
border-left: 1px solid $border-gray-light;
|
||||
padding-left: 10px;
|
||||
|
||||
&:hover {
|
||||
color: $gray-darkest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
|
@ -133,3 +152,115 @@
|
|||
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;
|
||||
&:hover {
|
||||
background: $gray-dark;
|
||||
border: 1px solid $border-gray-dark;
|
||||
}
|
||||
}
|
||||
|
||||
&.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;
|
||||
}
|
||||
|
||||
.btn-clipboard {
|
||||
border: none;
|
||||
|
||||
&:hover {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
i {
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
&.right-sidebar-expanded {
|
||||
.sidebar-collapsed-icon {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.detail-page-description {
|
||||
small {
|
||||
color: $gray-darkest;
|
||||
}
|
||||
}
|
|
@ -65,10 +65,6 @@ form.edit-issue {
|
|||
width: 3em;
|
||||
}
|
||||
|
||||
.merge-request-info {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.merge-request-status {
|
||||
color: $gl-gray;
|
||||
font-size: 15px;
|
||||
|
@ -143,4 +139,4 @@ form.edit-issue {
|
|||
.issue-closed-by-widget {
|
||||
color: $secondary-text;
|
||||
margin-left: 52px;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -147,7 +147,7 @@
|
|||
.edit_note {
|
||||
.markdown-area {
|
||||
min-height: 140px;
|
||||
max-height: 430px;
|
||||
max-height: 500px;
|
||||
}
|
||||
.note-form-actions {
|
||||
background: transparent;
|
||||
|
|
|
@ -281,36 +281,6 @@
|
|||
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-thumbnail {
|
||||
text-align: center;
|
||||
|
@ -386,22 +356,6 @@ pre.light-well {
|
|||
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 {
|
||||
margin: 0 7px 0 7px;
|
||||
|
||||
|
@ -437,12 +391,11 @@ pre.light-well {
|
|||
@include basic-list;
|
||||
|
||||
.project-row {
|
||||
padding: $gl-padding 0;
|
||||
border-color: $table-border-color;
|
||||
|
||||
&.no-description {
|
||||
.project {
|
||||
line-height: 44px;
|
||||
line-height: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -455,12 +408,16 @@ pre.light-well {
|
|||
.project-controls {
|
||||
float: right;
|
||||
color: $gl-gray;
|
||||
line-height: 45px;
|
||||
line-height: 40px;
|
||||
color: #7f8fa4;
|
||||
|
||||
a:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
> span {
|
||||
margin-left: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.project-description {
|
||||
|
@ -559,52 +516,12 @@ pre.light-well {
|
|||
}
|
||||
}
|
||||
|
||||
.cannot-be-merged,
|
||||
.cannot-be-merged,
|
||||
.cannot-be-merged:hover {
|
||||
color: #E62958;
|
||||
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 {
|
||||
i:nth-child(1) {
|
||||
color: #2AA056;
|
||||
|
|
|
@ -79,6 +79,9 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
|
|||
:recaptcha_private_key,
|
||||
:sentry_enabled,
|
||||
:sentry_dsn,
|
||||
:akismet_enabled,
|
||||
:akismet_api_key,
|
||||
:email_author_in_body,
|
||||
restricted_visibility_levels: [],
|
||||
import_sources: []
|
||||
)
|
||||
|
|
|
@ -2,7 +2,7 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
|
|||
before_action :finder, only: [:edit, :update, :destroy]
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
|
@ -36,6 +36,10 @@ class Admin::BroadcastMessagesController < Admin::ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def preview
|
||||
@message = broadcast_message_params[:message]
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def finder
|
||||
|
|
17
app/controllers/admin/spam_logs_controller.rb
Normal file
17
app/controllers/admin/spam_logs_controller.rb
Normal file
|
@ -0,0 +1,17 @@
|
|||
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
|
|||
params[:authenticity_token].presence
|
||||
elsif params[:private_token].presence
|
||||
params[:private_token].presence
|
||||
elsif request.headers['PRIVATE-TOKEN'].present?
|
||||
request.headers['PRIVATE-TOKEN']
|
||||
end
|
||||
user = user_token && User.find_by_authentication_token(user_token.to_s)
|
||||
|
||||
|
@ -162,7 +164,7 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
|
||||
def git_not_found!
|
||||
render html: "errors/git_not_found", layout: "errors", status: 404
|
||||
render "errors/git_not_found.html", layout: "errors", status: 404
|
||||
end
|
||||
|
||||
def method_missing(method_sym, *arguments, &block)
|
||||
|
@ -275,9 +277,10 @@ class ApplicationController < ActionController::Base
|
|||
}
|
||||
end
|
||||
|
||||
def view_to_html_string(partial)
|
||||
def view_to_html_string(partial, locals = {})
|
||||
render_to_string(
|
||||
partial,
|
||||
locals: locals,
|
||||
layout: false,
|
||||
formats: [:html]
|
||||
)
|
||||
|
|
|
@ -3,52 +3,5 @@ module Ci
|
|||
def self.railtie_helpers_paths
|
||||
"app/helpers/ci"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def authorize_access_project!
|
||||
unless can?(current_user, :read_project, project)
|
||||
return page_404
|
||||
end
|
||||
end
|
||||
|
||||
def authorize_manage_builds!
|
||||
unless can?(current_user, :manage_builds, project)
|
||||
return page_404
|
||||
end
|
||||
end
|
||||
|
||||
def authenticate_admin!
|
||||
return render_404 unless current_user.is_admin?
|
||||
end
|
||||
|
||||
def authorize_manage_project!
|
||||
unless can?(current_user, :admin_project, project)
|
||||
return page_404
|
||||
end
|
||||
end
|
||||
|
||||
def page_404
|
||||
render file: "#{Rails.root}/public/404.html", status: 404, layout: false
|
||||
end
|
||||
|
||||
def default_headers
|
||||
headers['X-Frame-Options'] = 'DENY'
|
||||
headers['X-XSS-Protection'] = '1; mode=block'
|
||||
end
|
||||
|
||||
# JSON for infinite scroll via Pager object
|
||||
def pager_json(partial, count)
|
||||
html = render_to_string(
|
||||
partial,
|
||||
layout: false,
|
||||
formats: [:html]
|
||||
)
|
||||
|
||||
render json: {
|
||||
html: html,
|
||||
count: count
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
module Ci
|
||||
class ProjectsController < Ci::ApplicationController
|
||||
before_action :project, except: [:index]
|
||||
before_action :authenticate_user!, except: [:index, :build, :badge]
|
||||
before_action :authorize_access_project!, except: [:index, :badge]
|
||||
before_action :project
|
||||
before_action :authorize_read_project!, except: [:badge]
|
||||
before_action :no_cache, only: [:badge]
|
||||
protect_from_forgery
|
||||
|
||||
|
@ -13,9 +12,13 @@ module Ci
|
|||
|
||||
# Project status badge
|
||||
# Image with build status for sha or ref
|
||||
#
|
||||
# This action in DEPRECATED, this is here only for backwards compatibility
|
||||
# with projects migrated from GitLab CI.
|
||||
#
|
||||
def badge
|
||||
return render_404 unless @project
|
||||
image = Ci::ImageForBuildService.new.execute(@project, params)
|
||||
|
||||
send_file image.path, filename: image.name, disposition: 'inline', type:"image/svg+xml"
|
||||
end
|
||||
|
||||
|
|
|
@ -3,7 +3,16 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
|
|||
|
||||
def index
|
||||
@projects = current_user.authorized_projects.sorted_by_activity.non_archived
|
||||
@projects = @projects.sort(@sort = params[:sort])
|
||||
@projects = @projects.includes(:namespace)
|
||||
|
||||
terms = params['filter_projects']
|
||||
|
||||
if terms.present?
|
||||
@projects = @projects.search(terms)
|
||||
end
|
||||
|
||||
@projects = @projects.page(params[:page]).per(PER_PAGE) if terms.blank?
|
||||
@last_push = current_user.recent_push
|
||||
|
||||
respond_to do |format|
|
||||
|
@ -13,6 +22,11 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
|
|||
load_events
|
||||
render layout: false
|
||||
end
|
||||
format.json do
|
||||
render json: {
|
||||
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -20,6 +34,14 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
|
|||
@projects = current_user.starred_projects
|
||||
@projects = @projects.includes(:namespace, :forked_from_project, :tags)
|
||||
@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) if terms.blank?
|
||||
@last_push = current_user.recent_push
|
||||
@groups = []
|
||||
|
||||
|
@ -27,8 +49,9 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
|
|||
format.html
|
||||
|
||||
format.json do
|
||||
load_events
|
||||
pager_json("events/_events", @events.count)
|
||||
render json: {
|
||||
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,19 +6,49 @@ class Explore::ProjectsController < Explore::ApplicationController
|
|||
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
|
||||
@projects = @projects.non_archived
|
||||
@projects = @projects.search(params[:search]) if params[:search].present?
|
||||
@projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
|
||||
@projects = @projects.sort(@sort = params[:sort])
|
||||
@projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE)
|
||||
@projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: {
|
||||
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def trending
|
||||
@trending_projects = TrendingProjectsFinder.new.execute(current_user)
|
||||
@trending_projects = @trending_projects.non_archived
|
||||
@trending_projects = @trending_projects.page(params[:page]).per(PER_PAGE)
|
||||
@projects = TrendingProjectsFinder.new.execute(current_user)
|
||||
@projects = @projects.non_archived
|
||||
@projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
|
||||
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: {
|
||||
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def starred
|
||||
@starred_projects = ProjectsFinder.new.execute(current_user)
|
||||
@starred_projects = @starred_projects.reorder('star_count DESC')
|
||||
@starred_projects = @starred_projects.page(params[:page]).per(PER_PAGE)
|
||||
@projects = ProjectsFinder.new.execute(current_user)
|
||||
@projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
|
||||
@projects = @projects.reorder('star_count DESC')
|
||||
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json do
|
||||
render json: {
|
||||
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -14,7 +14,7 @@ class GroupsController < Groups::ApplicationController
|
|||
|
||||
# Load group projects
|
||||
before_action :load_projects, except: [:index, :new, :create, :projects, :edit, :update, :autocomplete]
|
||||
before_action :event_filter, only: :show
|
||||
before_action :event_filter, only: [:show, :events]
|
||||
|
||||
layout :determine_layout
|
||||
|
||||
|
@ -41,13 +41,16 @@ class GroupsController < Groups::ApplicationController
|
|||
def show
|
||||
@last_push = current_user.recent_push if current_user
|
||||
@projects = @projects.includes(:namespace)
|
||||
@projects = @projects.search(params[:filter_projects]) if params[:filter_projects].present?
|
||||
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank?
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
|
||||
format.json do
|
||||
load_events
|
||||
pager_json("events/_events", @events.count)
|
||||
render json: {
|
||||
html: view_to_html_string("dashboard/projects/_projects", locals: { projects: @projects })
|
||||
}
|
||||
end
|
||||
|
||||
format.atom do
|
||||
|
@ -57,6 +60,15 @@ class GroupsController < Groups::ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def events
|
||||
respond_to do |format|
|
||||
format.json do
|
||||
load_events
|
||||
pager_json("events/_events", @events.count)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def edit
|
||||
end
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
||||
include AuthenticatesWithTwoFactor
|
||||
|
||||
protect_from_forgery except: [:kerberos, :saml, :cas3]
|
||||
|
||||
|
@ -29,8 +30,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
|
|||
|
||||
# Do additional LDAP checks for the user filter and EE features
|
||||
if ldap_user.allowed?
|
||||
log_audit_event(@user, with: :ldap)
|
||||
sign_in_and_redirect(@user)
|
||||
if @user.two_factor_enabled?
|
||||
prompt_for_two_factor(@user)
|
||||
else
|
||||
log_audit_event(@user, with: :ldap)
|
||||
sign_in_and_redirect(@user)
|
||||
end
|
||||
else
|
||||
flash[:alert] = "Access denied for your LDAP account."
|
||||
redirect_to new_user_session_path
|
||||
|
|
|
@ -28,6 +28,11 @@ class Projects::ApplicationController < ApplicationController
|
|||
|
||||
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
|
||||
return render_404 unless @project.builds_enabled?
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class Projects::ArtifactsController < Projects::ApplicationController
|
||||
layout 'project'
|
||||
before_action :authorize_read_build_artifacts!
|
||||
before_action :authorize_read_build!
|
||||
|
||||
def download
|
||||
unless artifacts_file.file_storage?
|
||||
|
@ -43,14 +43,4 @@ class Projects::ArtifactsController < Projects::ApplicationController
|
|||
def artifacts_file
|
||||
@artifacts_file ||= build.artifacts_file
|
||||
end
|
||||
|
||||
def authorize_read_build_artifacts!
|
||||
unless can?(current_user, :read_build_artifacts, @project)
|
||||
if current_user.nil?
|
||||
return authenticate_user!
|
||||
else
|
||||
return render_404
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -2,11 +2,10 @@ class Projects::AvatarsController < Projects::ApplicationController
|
|||
before_action :project
|
||||
|
||||
def show
|
||||
repository = @project.repository
|
||||
@blob = repository.blob_at_branch('master', @project.avatar_in_git)
|
||||
@blob = @repository.blob_at_branch('master', @project.avatar_in_git)
|
||||
if @blob
|
||||
headers['X-Content-Type-Options'] = 'nosniff'
|
||||
headers.store(*Gitlab::Workhorse.send_git_blob(repository, @blob))
|
||||
headers.store(*Gitlab::Workhorse.send_git_blob(@repository, @blob))
|
||||
headers['Content-Disposition'] = 'inline'
|
||||
headers['Content-Type'] = @blob.content_type
|
||||
head :ok # 'render nothing: true' messes up the Content-Type
|
||||
|
|
11
app/controllers/projects/badges_controller.rb
Normal file
11
app/controllers/projects/badges_controller.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
class Projects::BadgesController < Projects::ApplicationController
|
||||
def build
|
||||
respond_to do |format|
|
||||
format.html { render_404 }
|
||||
format.svg do
|
||||
image = Ci::ImageForBuildService.new.execute(project, ref: params[:ref])
|
||||
send_file(image.path, filename: image.name, disposition: 'inline', type: 'image/svg+xml')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -33,6 +33,7 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
|
||||
def edit
|
||||
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
|
||||
blob.load_all_data!(@repository)
|
||||
end
|
||||
|
||||
def update
|
||||
|
@ -51,6 +52,7 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
|
||||
def preview
|
||||
@content = params[:content]
|
||||
@blob.load_all_data!(@repository)
|
||||
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true)
|
||||
diff_lines = diffy.diff.scan(/.*\n/)[2..-1]
|
||||
diff_lines = Gitlab::Diff::Parser.new.parse(diff_lines)
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
class Projects::BuildsController < Projects::ApplicationController
|
||||
before_action :build, except: [:index, :cancel_all]
|
||||
|
||||
before_action :authorize_manage_builds!, except: [:index, :show, :status]
|
||||
|
||||
layout "project"
|
||||
before_action :authorize_read_build!, except: [:cancel, :cancel_all, :retry]
|
||||
before_action :authorize_update_build!, except: [:index, :show, :status]
|
||||
layout 'project'
|
||||
|
||||
def index
|
||||
@scope = params[:scope]
|
||||
|
@ -23,7 +22,6 @@ class Projects::BuildsController < Projects::ApplicationController
|
|||
|
||||
def cancel_all
|
||||
@project.builds.running_or_pending.each(&:cancel)
|
||||
|
||||
redirect_to namespace_project_builds_path(project.namespace, project)
|
||||
end
|
||||
|
||||
|
@ -46,20 +44,18 @@ class Projects::BuildsController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
build = Ci::Build.retry(@build)
|
||||
|
||||
redirect_to build_path(build)
|
||||
end
|
||||
|
||||
def status
|
||||
render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
|
||||
end
|
||||
|
||||
def cancel
|
||||
@build.cancel
|
||||
|
||||
redirect_to build_path(@build)
|
||||
end
|
||||
|
||||
def status
|
||||
render json: @build.to_json(only: [:status, :id, :sha, :coverage], methods: :sha)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def build
|
||||
|
@ -69,10 +65,4 @@ class Projects::BuildsController < Projects::ApplicationController
|
|||
def build_path(build)
|
||||
namespace_project_build_path(build.project.namespace, build.project, build)
|
||||
end
|
||||
|
||||
def authorize_manage_builds!
|
||||
unless can?(current_user, :manage_builds, project)
|
||||
return render_404
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
class Projects::CommitController < Projects::ApplicationController
|
||||
# Authorize
|
||||
before_action :require_non_empty_project
|
||||
before_action :authorize_download_code!, except: [:cancel_builds]
|
||||
before_action :authorize_manage_builds!, only: [:cancel_builds]
|
||||
before_action :authorize_download_code!, except: [:cancel_builds, :retry_builds]
|
||||
before_action :authorize_update_build!, only: [:cancel_builds, :retry_builds]
|
||||
before_action :authorize_read_commit_status!, only: [:builds]
|
||||
before_action :commit
|
||||
before_action :authorize_manage_builds!, only: [:cancel_builds, :retry_builds]
|
||||
before_action :define_show_vars, only: [:show, :builds]
|
||||
|
||||
def show
|
||||
return git_not_found! unless @commit
|
||||
apply_diff_view_cookie!
|
||||
|
||||
@line_notes = commit.notes.inline
|
||||
@note = @project.build_commit_note(commit)
|
||||
|
@ -66,6 +66,8 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def define_show_vars
|
||||
return git_not_found! unless commit
|
||||
|
||||
if params[:w].to_i == 1
|
||||
@diffs = commit.diffs({ ignore_whitespace_change: true })
|
||||
else
|
||||
|
@ -77,10 +79,4 @@ class Projects::CommitController < Projects::ApplicationController
|
|||
|
||||
@statuses = ci_commit.statuses if ci_commit
|
||||
end
|
||||
|
||||
def authorize_manage_builds!
|
||||
unless can?(current_user, :manage_builds, project)
|
||||
return render_404
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,6 +21,9 @@ class Projects::CommitsController < Projects::ApplicationController
|
|||
@note_counts = project.notes.where(commit_id: @commits.map(&:id)).
|
||||
group(:commit_id).count
|
||||
|
||||
@merge_request = @project.merge_requests.opened.
|
||||
find_by(source_project: @project, source_branch: @ref, target_branch: @repository.root_ref)
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json { pager_json("projects/commits/_commits", @commits.size) }
|
||||
|
|
|
@ -4,24 +4,23 @@ class Projects::CompareController < Projects::ApplicationController
|
|||
# Authorize
|
||||
before_action :require_non_empty_project
|
||||
before_action :authorize_download_code!
|
||||
before_action :assign_ref_vars, only: [:index, :show]
|
||||
before_action :merge_request, only: [:index, :show]
|
||||
|
||||
def index
|
||||
@ref = Addressable::URI.unescape(params[:to])
|
||||
end
|
||||
|
||||
def show
|
||||
base_ref = Addressable::URI.unescape(params[:from])
|
||||
@ref = head_ref = Addressable::URI.unescape(params[:to])
|
||||
diff_options = { ignore_whitespace_change: true } if params[:w] == '1'
|
||||
|
||||
compare_result = CompareService.new.
|
||||
execute(@project, head_ref, @project, base_ref, diff_options)
|
||||
execute(@project, @head_ref, @project, @base_ref, diff_options)
|
||||
|
||||
if compare_result
|
||||
@commits = Commit.decorate(compare_result.commits, @project)
|
||||
@diffs = compare_result.diffs
|
||||
@commit = @project.commit(head_ref)
|
||||
@base_commit = @project.merge_base_commit(base_ref, head_ref)
|
||||
@commit = @project.commit(@head_ref)
|
||||
@base_commit = @project.merge_base_commit(@base_ref, @head_ref)
|
||||
@diff_refs = [@base_commit, @commit]
|
||||
@line_notes = []
|
||||
end
|
||||
|
@ -31,4 +30,16 @@ class Projects::CompareController < Projects::ApplicationController
|
|||
redirect_to namespace_project_compare_path(@project.namespace, @project,
|
||||
params[:from], params[:to])
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assign_ref_vars
|
||||
@base_ref = Addressable::URI.unescape(params[:from])
|
||||
@ref = @head_ref = Addressable::URI.unescape(params[:to])
|
||||
end
|
||||
|
||||
def merge_request
|
||||
@merge_request ||= @project.merge_requests.opened.
|
||||
find_by(source_project: @project, source_branch: @head_ref, target_branch: @base_ref)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -57,6 +57,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
|||
end
|
||||
|
||||
def diffs
|
||||
apply_diff_view_cookie!
|
||||
|
||||
@commit = @merge_request.last_commit
|
||||
@base_commit = @merge_request.diff_base_commit
|
||||
|
||||
|
|
|
@ -11,11 +11,12 @@ class Projects::MilestonesController < Projects::ApplicationController
|
|||
respond_to :html
|
||||
|
||||
def index
|
||||
@milestones = case params[:state]
|
||||
when 'all'; @project.milestones.order("state, due_date DESC")
|
||||
when 'closed'; @project.milestones.closed.order("due_date DESC")
|
||||
else @project.milestones.active.order("due_date ASC")
|
||||
end
|
||||
@milestones =
|
||||
case params[:state]
|
||||
when 'all' then @project.milestones.reorder(due_date: :desc, title: :asc)
|
||||
when 'closed' then @project.milestones.closed.reorder(due_date: :desc, title: :asc)
|
||||
else @project.milestones.active.reorder(due_date: :asc, title: :asc)
|
||||
end
|
||||
|
||||
@milestones = @milestones.includes(:project)
|
||||
@milestones = @milestones.page(params[:page]).per(PER_PAGE)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class Projects::RunnerProjectsController < Projects::ApplicationController
|
||||
before_action :authorize_admin_project!
|
||||
before_action :authorize_admin_build!
|
||||
|
||||
layout 'project_settings'
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
class Projects::RunnersController < Projects::ApplicationController
|
||||
before_action :authorize_admin_build!
|
||||
before_action :set_runner, only: [:edit, :update, :destroy, :pause, :resume, :show]
|
||||
before_action :authorize_admin_project!
|
||||
|
||||
layout 'project_settings'
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class Projects::TriggersController < Projects::ApplicationController
|
||||
before_action :authorize_admin_project!
|
||||
before_action :authorize_admin_build!
|
||||
|
||||
layout 'project_settings'
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class Projects::VariablesController < Projects::ApplicationController
|
||||
before_action :authorize_admin_project!
|
||||
before_action :authorize_admin_build!
|
||||
|
||||
layout 'project_settings'
|
||||
|
||||
|
|
|
@ -227,6 +227,7 @@ class ProjectsController < ApplicationController
|
|||
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
|
||||
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
|
||||
:builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
|
||||
:public_builds,
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -4,8 +4,9 @@ class UsersController < ApplicationController
|
|||
|
||||
def show
|
||||
@contributed_projects = contributed_projects.joined(@user).reject(&:forked?)
|
||||
|
||||
|
||||
@projects = PersonalProjectsFinder.new(@user).execute(current_user)
|
||||
@projects = @projects.page(params[:page]).per(PER_PAGE)
|
||||
|
||||
@groups = @user.groups.order_id_desc
|
||||
|
||||
|
|
|
@ -169,18 +169,6 @@ module ApplicationHelper
|
|||
Gitlab.config.extra
|
||||
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
|
||||
#
|
||||
# time - Time object
|
||||
|
@ -224,8 +212,7 @@ module ApplicationHelper
|
|||
file_content
|
||||
end
|
||||
else
|
||||
GitHub::Markup.render(file_name, file_content).
|
||||
force_encoding(file_content.encoding).html_safe
|
||||
other_markup(file_name, file_content)
|
||||
end
|
||||
rescue RuntimeError
|
||||
simple_format(file_content)
|
||||
|
|
|
@ -23,6 +23,10 @@ module ApplicationSettingsHelper
|
|||
current_application_settings.user_oauth_applications
|
||||
end
|
||||
|
||||
def askimet_enabled?
|
||||
current_application_settings.akismet_enabled?
|
||||
end
|
||||
|
||||
# Return a group of checkboxes that use Bootstrap's button plugin for a
|
||||
# toggle button effect.
|
||||
def restricted_level_checkboxes(help_block_id)
|
||||
|
|
|
@ -126,4 +126,16 @@ module BlobHelper
|
|||
blob.size
|
||||
end
|
||||
end
|
||||
|
||||
def blob_svg?(blob)
|
||||
blob.language && blob.language.name == 'SVG'
|
||||
end
|
||||
|
||||
# SVGs can contain malicious JavaScript; only include whitelisted
|
||||
# elements and attributes. Note that this whitelist is by no means complete
|
||||
# and may omit some elements.
|
||||
def sanitize_svg(blob)
|
||||
blob.data = Loofah.scrub_fragment(blob.data, :strip).to_xml
|
||||
blob
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@ module BroadcastMessagesHelper
|
|||
return unless message.present?
|
||||
|
||||
content_tag :div, class: 'broadcast-message', style: broadcast_message_style(message) do
|
||||
icon('bullhorn') << ' ' << message.message
|
||||
icon('bullhorn') << ' ' << render_broadcast_message(message.message)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -31,4 +31,8 @@ module BroadcastMessagesHelper
|
|||
'Pending'
|
||||
end
|
||||
end
|
||||
|
||||
def render_broadcast_message(message)
|
||||
Banzai.render(message, pipeline: :broadcast_message).html_safe
|
||||
end
|
||||
end
|
||||
|
|
|
@ -10,8 +10,19 @@ module ExploreHelper
|
|||
|
||||
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
|
||||
end
|
||||
|
||||
def explore_controller?
|
||||
controller.class.name.split("::").first == "Explore"
|
||||
end
|
||||
end
|
||||
|
|
|
@ -78,6 +78,21 @@ module GitlabMarkdownHelper
|
|||
)
|
||||
end
|
||||
|
||||
def other_markup(file_name, text)
|
||||
Gitlab::OtherMarkup.render(
|
||||
file_name,
|
||||
text,
|
||||
project: @project,
|
||||
current_user: (current_user if defined?(current_user)),
|
||||
|
||||
# RelativeLinkFilter
|
||||
project_wiki: @project_wiki,
|
||||
requested_path: @path,
|
||||
ref: @ref,
|
||||
commit: @commit
|
||||
)
|
||||
end
|
||||
|
||||
# Return the first line of +text+, up to +max_chars+, after parsing the line
|
||||
# as Markdown. HTML tags in the parsed output are not counted toward the
|
||||
# +max_chars+ limit. If the length limit falls within a tag's contents, then
|
||||
|
|
37
app/helpers/issuables_helper.rb
Normal file
37
app/helpers/issuables_helper.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
module IssuablesHelper
|
||||
|
||||
def sidebar_gutter_toggle_icon
|
||||
sidebar_gutter_collapsed? ? icon('angle-double-left') : icon('angle-double-right')
|
||||
end
|
||||
|
||||
def sidebar_gutter_collapsed_class
|
||||
"right-sidebar-#{sidebar_gutter_collapsed? ? 'collapsed' : 'expanded'}"
|
||||
end
|
||||
|
||||
def issuables_count(issuable)
|
||||
base_issuable_scope(issuable).maximum(:iid)
|
||||
end
|
||||
|
||||
def next_issuable_for(issuable)
|
||||
base_issuable_scope(issuable).where('iid > ?', issuable.iid).last
|
||||
end
|
||||
|
||||
def prev_issuable_for(issuable)
|
||||
base_issuable_scope(issuable).where('iid < ?', issuable.iid).first
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def sidebar_gutter_collapsed?
|
||||
cookies[:collapsed_gutter] == 'true'
|
||||
end
|
||||
|
||||
def base_issuable_scope(issuable)
|
||||
issuable.project.send(issuable.class.table_name).send(issuable_state_scope(issuable))
|
||||
end
|
||||
|
||||
def issuable_state_scope(issuable)
|
||||
issuable.open? ? :opened : :closed
|
||||
end
|
||||
|
||||
end
|
|
@ -44,14 +44,14 @@ module IssuesHelper
|
|||
end
|
||||
|
||||
def bulk_update_milestone_options
|
||||
milestones = project_active_milestones.to_a
|
||||
milestones = @project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
|
||||
milestones.unshift(Milestone::None)
|
||||
|
||||
options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id])
|
||||
end
|
||||
|
||||
def milestone_options(object)
|
||||
milestones = object.project.milestones.active.to_a
|
||||
milestones = object.project.milestones.active.reorder(due_date: :asc, title: :asc).to_a
|
||||
milestones.unshift(Milestone::None)
|
||||
|
||||
options_from_collection_for_select(milestones, 'id', 'title', object.milestone_id)
|
||||
|
@ -69,7 +69,7 @@ module IssuesHelper
|
|||
end
|
||||
end
|
||||
|
||||
def issue_button_visibility(issue, closed)
|
||||
def issue_button_visibility(issue, closed)
|
||||
return 'hidden' if issue.closed? == closed
|
||||
end
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ module LabelsHelper
|
|||
# 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
|
||||
# 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
|
||||
# body of the link element. If omitted, defaults to
|
||||
# `render_colored_label`.
|
||||
|
@ -23,14 +25,19 @@ module LabelsHelper
|
|||
# # Force the generated link to use a provided project
|
||||
# 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
|
||||
# link_to_label(label) { "My Custom Label Text" }
|
||||
#
|
||||
# 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
|
||||
link = namespace_project_issues_path(project.namespace, project,
|
||||
label_name: label.name)
|
||||
link = send("namespace_project_#{type.to_s.pluralize}_path",
|
||||
project.namespace,
|
||||
project,
|
||||
label_name: label.name)
|
||||
|
||||
if block_given?
|
||||
link_to link, &block
|
||||
|
|
|
@ -19,6 +19,19 @@ module NavHelper
|
|||
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
|
||||
if nav_menu_collapsed?
|
||||
"header-collapsed"
|
||||
|
|
|
@ -20,6 +20,12 @@ module ProjectsHelper
|
|||
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 = {})
|
||||
default_opts = { avatar: true, name: true, size: 16, author_class: 'author', title: ":name" }
|
||||
opts = default_opts.merge(opts)
|
||||
|
@ -53,14 +59,23 @@ module ProjectsHelper
|
|||
link_to(simple_sanitize(owner.name), user_path(owner))
|
||||
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 += ' · '.html_safe + link_to(simple_sanitize(name), url) if name
|
||||
|
||||
content_tag :span do
|
||||
full_title
|
||||
end
|
||||
full_title
|
||||
end
|
||||
|
||||
def remove_project_message(project)
|
||||
|
@ -83,10 +98,6 @@ module ProjectsHelper
|
|||
project_nav_tabs.include? name
|
||||
end
|
||||
|
||||
def project_active_milestones
|
||||
@project.milestones.active.order("due_date, title ASC")
|
||||
end
|
||||
|
||||
def project_for_deploy_key(deploy_key)
|
||||
if deploy_key.projects.include?(@project)
|
||||
@project
|
||||
|
@ -126,7 +137,7 @@ module ProjectsHelper
|
|||
nav_tabs << :merge_requests
|
||||
end
|
||||
|
||||
if project.builds_enabled? && can?(current_user, :read_build, project)
|
||||
if can?(current_user, :read_build, project)
|
||||
nav_tabs << :builds
|
||||
end
|
||||
|
||||
|
|
|
@ -70,7 +70,7 @@ module SearchHelper
|
|||
|
||||
# Autocomplete results for the current user's groups
|
||||
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)}",
|
||||
url: group_path(group)
|
||||
|
@ -80,7 +80,7 @@ module SearchHelper
|
|||
|
||||
# Autocomplete results for the current user's projects
|
||||
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|
|
||||
{
|
||||
label: "project: #{search_result_sanitize(p.name_with_namespace)}",
|
||||
|
|
|
@ -33,7 +33,7 @@ module SnippetsHelper
|
|||
# surrounding code.
|
||||
#
|
||||
# @returns Array, unique and sorted.
|
||||
def matching_lines(lined_content, surrounding_lines)
|
||||
def matching_lines(lined_content, surrounding_lines, query)
|
||||
used_lines = []
|
||||
lined_content.each_with_index do |line, line_number|
|
||||
used_lines.concat bounded_line_numbers(
|
||||
|
@ -51,9 +51,9 @@ module SnippetsHelper
|
|||
# surrounding_lines() worth of unmatching lines.
|
||||
#
|
||||
# @returns a hash with {snippet_object, snippet_chunks:{data,start_line}}
|
||||
def chunk_snippet(snippet, surrounding_lines = 3)
|
||||
def chunk_snippet(snippet, query, surrounding_lines = 3)
|
||||
lined_content = snippet.content.split("\n")
|
||||
used_lines = matching_lines(lined_content, surrounding_lines)
|
||||
used_lines = matching_lines(lined_content, surrounding_lines, query)
|
||||
|
||||
snippet_chunk = []
|
||||
snippet_chunks = []
|
||||
|
|
|
@ -10,7 +10,7 @@ class EmailRejectionMailer < BaseMailer
|
|||
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['References'] = @original_message.message_id
|
||||
|
||||
|
|
|
@ -3,26 +3,27 @@ module Emails
|
|||
def build_fail_email(build_id, to)
|
||||
@build = Ci::Build.find(build_id)
|
||||
@project = @build.project
|
||||
|
||||
add_project_headers
|
||||
add_build_headers
|
||||
headers['X-GitLab-Build-Status'] = "failed"
|
||||
add_build_headers('failed')
|
||||
mail(to: to, subject: subject("Build failed for #{@project.name}", @build.short_sha))
|
||||
end
|
||||
|
||||
def build_success_email(build_id, to)
|
||||
@build = Ci::Build.find(build_id)
|
||||
@project = @build.project
|
||||
|
||||
add_project_headers
|
||||
add_build_headers
|
||||
headers['X-GitLab-Build-Status'] = "success"
|
||||
add_build_headers('success')
|
||||
mail(to: to, subject: subject("Build success for #{@project.name}", @build.short_sha))
|
||||
end
|
||||
|
||||
private
|
||||
def add_build_headers
|
||||
|
||||
def add_build_headers(status)
|
||||
headers['X-GitLab-Build-Id'] = @build.id
|
||||
headers['X-GitLab-Build-Ref'] = @build.ref
|
||||
headers['X-GitLab-Build-Status'] = status.to_s
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,17 +5,18 @@ class Ability
|
|||
return [] unless user.is_a?(User)
|
||||
return [] if user.blocked?
|
||||
|
||||
case subject.class.name
|
||||
when "Project" then project_abilities(user, subject)
|
||||
when "Issue" then issue_abilities(user, subject)
|
||||
when "Note" then note_abilities(user, subject)
|
||||
when "ProjectSnippet" then project_snippet_abilities(user, subject)
|
||||
when "PersonalSnippet" then personal_snippet_abilities(user, subject)
|
||||
when "MergeRequest" then merge_request_abilities(user, subject)
|
||||
when "Group" then group_abilities(user, subject)
|
||||
when "Namespace" then namespace_abilities(user, subject)
|
||||
when "GroupMember" then group_member_abilities(user, subject)
|
||||
when "ProjectMember" then project_member_abilities(user, subject)
|
||||
case subject
|
||||
when CommitStatus then commit_status_abilities(user, subject)
|
||||
when Project then project_abilities(user, subject)
|
||||
when Issue then issue_abilities(user, subject)
|
||||
when Note then note_abilities(user, subject)
|
||||
when ProjectSnippet then project_snippet_abilities(user, subject)
|
||||
when PersonalSnippet then personal_snippet_abilities(user, subject)
|
||||
when MergeRequest then merge_request_abilities(user, subject)
|
||||
when Group then group_abilities(user, subject)
|
||||
when Namespace then namespace_abilities(user, subject)
|
||||
when GroupMember then group_member_abilities(user, subject)
|
||||
when ProjectMember then project_member_abilities(user, subject)
|
||||
else []
|
||||
end.concat(global_abilities(user))
|
||||
end
|
||||
|
@ -25,6 +26,8 @@ class Ability
|
|||
case true
|
||||
when subject.is_a?(PersonalSnippet)
|
||||
anonymous_personal_snippet_abilities(subject)
|
||||
when subject.is_a?(CommitStatus)
|
||||
anonymous_commit_status_abilities(subject)
|
||||
when subject.is_a?(Project) || subject.respond_to?(:project)
|
||||
anonymous_project_abilities(subject)
|
||||
when subject.is_a?(Group) || subject.respond_to?(:group)
|
||||
|
@ -52,16 +55,26 @@ class Ability
|
|||
:read_project_member,
|
||||
:read_merge_request,
|
||||
:read_note,
|
||||
:read_build,
|
||||
:read_commit_status,
|
||||
:download_code
|
||||
]
|
||||
|
||||
# Allow to read builds by anonymous user if guests are allowed
|
||||
rules << :read_build if project.public_builds?
|
||||
|
||||
rules - project_disabled_features_rules(project)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
|
||||
def anonymous_commit_status_abilities(subject)
|
||||
rules = anonymous_project_abilities(subject.project)
|
||||
# If subject is Ci::Build which inherits from CommitStatus filter the abilities
|
||||
rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
|
||||
rules
|
||||
end
|
||||
|
||||
def anonymous_group_abilities(subject)
|
||||
group = if subject.is_a?(Group)
|
||||
subject
|
||||
|
@ -113,6 +126,9 @@ class Ability
|
|||
|
||||
if project.public? || project.internal?
|
||||
rules.push(*public_project_rules)
|
||||
|
||||
# Allow to read builds for internal projects
|
||||
rules << :read_build if project.public_builds?
|
||||
end
|
||||
|
||||
if project.owner == user || user.admin?
|
||||
|
@ -134,7 +150,8 @@ class Ability
|
|||
def public_project_rules
|
||||
@public_project_rules ||= project_guest_rules + [
|
||||
:download_code,
|
||||
:fork_project
|
||||
:fork_project,
|
||||
:read_commit_status,
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -149,7 +166,6 @@ class Ability
|
|||
:read_project_member,
|
||||
:read_merge_request,
|
||||
:read_note,
|
||||
:read_build,
|
||||
:create_project,
|
||||
:create_issue,
|
||||
:create_note
|
||||
|
@ -158,24 +174,26 @@ class Ability
|
|||
|
||||
def project_report_rules
|
||||
@project_report_rules ||= project_guest_rules + [
|
||||
:create_commit_status,
|
||||
:read_commit_statuses,
|
||||
:read_build_artifacts,
|
||||
:download_code,
|
||||
:fork_project,
|
||||
:create_project_snippet,
|
||||
:update_issue,
|
||||
:admin_issue,
|
||||
:admin_label
|
||||
:admin_label,
|
||||
:read_commit_status,
|
||||
:read_build,
|
||||
]
|
||||
end
|
||||
|
||||
def project_dev_rules
|
||||
@project_dev_rules ||= project_report_rules + [
|
||||
:admin_merge_request,
|
||||
:create_commit_status,
|
||||
:update_commit_status,
|
||||
:create_build,
|
||||
:update_build,
|
||||
:create_merge_request,
|
||||
:create_wiki,
|
||||
:manage_builds,
|
||||
:push_code
|
||||
]
|
||||
end
|
||||
|
@ -201,7 +219,9 @@ class Ability
|
|||
:admin_merge_request,
|
||||
:admin_note,
|
||||
:admin_wiki,
|
||||
:admin_project
|
||||
:admin_project,
|
||||
:admin_commit_status,
|
||||
:admin_build
|
||||
]
|
||||
end
|
||||
|
||||
|
@ -240,6 +260,10 @@ class Ability
|
|||
rules += named_abilities('wiki')
|
||||
end
|
||||
|
||||
unless project.builds_enabled
|
||||
rules += named_abilities('build')
|
||||
end
|
||||
|
||||
rules
|
||||
end
|
||||
|
||||
|
@ -376,6 +400,22 @@ class Ability
|
|||
rules
|
||||
end
|
||||
|
||||
def commit_status_abilities(user, subject)
|
||||
rules = project_abilities(user, subject.project)
|
||||
# If subject is Ci::Build which inherits from CommitStatus filter the abilities
|
||||
rules = filter_build_abilities(rules) if subject.is_a?(Ci::Build)
|
||||
rules
|
||||
end
|
||||
|
||||
def filter_build_abilities(rules)
|
||||
# If we can't read build we should also not have that
|
||||
# ability when looking at this in context of commit_status
|
||||
%w(read create update admin).each do |rule|
|
||||
rules.delete(:"#{rule}_commit_status") unless rules.include?(:"#{rule}_build")
|
||||
end
|
||||
rules
|
||||
end
|
||||
|
||||
def abilities
|
||||
@abilities ||= begin
|
||||
abilities = Six.new
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
# metrics_port :integer default(8089)
|
||||
# sentry_enabled :boolean default(FALSE)
|
||||
# sentry_dsn :string
|
||||
# email_author_in_body :boolean default(FALSE)
|
||||
#
|
||||
|
||||
class ApplicationSetting < ActiveRecord::Base
|
||||
|
@ -70,8 +71,8 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
url: true
|
||||
|
||||
validates :admin_notification_email,
|
||||
allow_blank: true,
|
||||
email: true
|
||||
email: true,
|
||||
allow_blank: true
|
||||
|
||||
validates :two_factor_grace_period,
|
||||
numericality: { greater_than_or_equal_to: 0 }
|
||||
|
@ -88,6 +89,14 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
presence: true,
|
||||
if: :sentry_enabled
|
||||
|
||||
validates :akismet_api_key,
|
||||
presence: true,
|
||||
if: :akismet_enabled
|
||||
|
||||
validates :max_attachment_size,
|
||||
presence: true,
|
||||
numericality: { only_integer: true, greater_than: 0 }
|
||||
|
||||
validates_each :restricted_visibility_levels do |record, attr, value|
|
||||
unless value.nil?
|
||||
value.each do |level|
|
||||
|
@ -143,7 +152,9 @@ class ApplicationSetting < ActiveRecord::Base
|
|||
shared_runners_enabled: Settings.gitlab_ci['shared_runners_enabled'],
|
||||
max_artifacts_size: Settings.artifacts['max_size'],
|
||||
require_two_factor_authentication: false,
|
||||
two_factor_grace_period: 48
|
||||
two_factor_grace_period: 48,
|
||||
recaptcha_enabled: false,
|
||||
akismet_enabled: false
|
||||
)
|
||||
end
|
||||
|
||||
|
|
|
@ -205,7 +205,11 @@ module Ci
|
|||
end
|
||||
|
||||
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
|
||||
nil
|
||||
end
|
||||
|
|
|
@ -126,17 +126,17 @@ module Issuable
|
|||
end
|
||||
|
||||
def to_hook_data(user)
|
||||
{
|
||||
hook_data = {
|
||||
object_kind: self.class.name.underscore,
|
||||
user: user.hook_attrs,
|
||||
repository: {
|
||||
name: project.name,
|
||||
url: project.url_to_repo,
|
||||
description: project.description,
|
||||
homepage: project.web_url
|
||||
},
|
||||
object_attributes: hook_attrs
|
||||
project: project.hook_attrs,
|
||||
object_attributes: hook_attrs,
|
||||
# DEPRECATED
|
||||
repository: project.hook_attrs.slice(:name, :url, :description, :homepage)
|
||||
}
|
||||
hook_data.merge!(assignee: assignee.hook_attrs) if assignee
|
||||
|
||||
hook_data
|
||||
end
|
||||
|
||||
def label_names
|
||||
|
|
|
@ -15,7 +15,7 @@ class Email < ActiveRecord::Base
|
|||
belongs_to :user
|
||||
|
||||
validates :user_id, presence: true
|
||||
validates :email, presence: true, email: { strict_mode: true }, uniqueness: true
|
||||
validates :email, presence: true, uniqueness: true, email: true
|
||||
validate :unique_email, if: ->(email) { email.email_changed? }
|
||||
|
||||
before_validation :cleanup_email
|
||||
|
|
|
@ -49,7 +49,7 @@ class Event < ActiveRecord::Base
|
|||
scope :code_push, -> { where(action: PUSHED) }
|
||||
|
||||
scope :in_projects, ->(projects) do
|
||||
where(project_id: projects.select(:id).reorder(nil)).recent
|
||||
where(project_id: projects.map(&:id)).recent
|
||||
end
|
||||
|
||||
scope :with_associations, -> { includes(project: :namespace) }
|
||||
|
|
|
@ -39,7 +39,6 @@ class Member < ActiveRecord::Base
|
|||
if: :invite?
|
||||
},
|
||||
email: {
|
||||
strict_mode: true,
|
||||
allow_nil: true
|
||||
},
|
||||
uniqueness: {
|
||||
|
|
|
@ -137,6 +137,7 @@ class MergeRequest < ActiveRecord::Base
|
|||
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
|
||||
scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
|
||||
scope :of_projects, ->(ids) { where(target_project_id: ids) }
|
||||
scope :opened, -> { with_state(:opened) }
|
||||
scope :merged, -> { with_state(:merged) }
|
||||
scope :closed, -> { with_state(:closed) }
|
||||
scope :closed_and_merged, -> { with_states(:closed, :merged) }
|
||||
|
@ -240,7 +241,7 @@ class MergeRequest < ActiveRecord::Base
|
|||
return unless unchecked?
|
||||
|
||||
can_be_merged =
|
||||
project.repository.can_be_merged?(source_sha, target_branch)
|
||||
!broken? && project.repository.can_be_merged?(source_sha, target_branch)
|
||||
|
||||
if can_be_merged
|
||||
mark_as_mergeable
|
||||
|
@ -258,7 +259,7 @@ class MergeRequest < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def work_in_progress?
|
||||
!!(title =~ /\A\[?WIP\]?:? /i)
|
||||
!!(title =~ /\A\[?WIP(\]|:| )/i)
|
||||
end
|
||||
|
||||
def mergeable?
|
||||
|
@ -284,7 +285,8 @@ class MergeRequest < ActiveRecord::Base
|
|||
def can_remove_source_branch?(current_user)
|
||||
!source_project.protected_branch?(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
|
||||
|
||||
def mr_and_commit_notes
|
||||
|
@ -346,10 +348,10 @@ class MergeRequest < ActiveRecord::Base
|
|||
# Return the set of issues that will be closed if this merge request is accepted.
|
||||
def closes_issues(current_user = self.author)
|
||||
if target_branch == project.default_branch
|
||||
issues = commits.flat_map { |c| c.closes_issues(current_user) }
|
||||
issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user).
|
||||
closed_by_message(description))
|
||||
issues.uniq(&:id)
|
||||
messages = commits.map(&:safe_message) << description
|
||||
|
||||
Gitlab::ClosingIssueExtractor.new(project, current_user).
|
||||
closed_by_message(messages.join("\n"))
|
||||
else
|
||||
[]
|
||||
end
|
||||
|
|
|
@ -34,7 +34,7 @@ class Milestone < ActiveRecord::Base
|
|||
scope :closed, -> { with_state(:closed) }
|
||||
scope :of_projects, ->(ids) { where(project_id: ids) }
|
||||
|
||||
validates :title, presence: true
|
||||
validates :title, presence: true, uniqueness: { scope: :project_id }
|
||||
validates :project, presence: true
|
||||
|
||||
strip_attributes :title
|
||||
|
|
|
@ -342,7 +342,7 @@ class Project < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def repository
|
||||
@repository ||= Repository.new(path_with_namespace, nil, self)
|
||||
@repository ||= Repository.new(path_with_namespace, self)
|
||||
end
|
||||
|
||||
def commit(id = 'HEAD')
|
||||
|
@ -738,11 +738,20 @@ class Project < ActiveRecord::Base
|
|||
def hook_attrs
|
||||
{
|
||||
name: name,
|
||||
ssh_url: ssh_url_to_repo,
|
||||
http_url: http_url_to_repo,
|
||||
description: description,
|
||||
web_url: web_url,
|
||||
avatar_url: avatar_url,
|
||||
git_ssh_url: ssh_url_to_repo,
|
||||
git_http_url: http_url_to_repo,
|
||||
namespace: namespace.name,
|
||||
visibility_level: visibility_level
|
||||
visibility_level: visibility_level,
|
||||
path_with_namespace: path_with_namespace,
|
||||
default_branch: default_branch,
|
||||
# Backward compatibility
|
||||
homepage: web_url,
|
||||
url: url_to_repo,
|
||||
ssh_url: ssh_url_to_repo,
|
||||
http_url: http_url_to_repo
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -790,6 +799,8 @@ class Project < ActiveRecord::Base
|
|||
def change_head(branch)
|
||||
# Cached divergent commit counts are based on repository head
|
||||
repository.expire_branch_cache
|
||||
repository.expire_root_ref_cache
|
||||
|
||||
gitlab_shell.update_repository_head(self.path_with_namespace, branch)
|
||||
reload_default_branch
|
||||
end
|
||||
|
|
|
@ -112,7 +112,7 @@ class PushoverService < Service
|
|||
priority: priority,
|
||||
title: "#{project.name_with_namespace}",
|
||||
message: message,
|
||||
url: data[:repository][:homepage],
|
||||
url: data[:project][:web_url],
|
||||
url_title: "See project #{project.name_with_namespace}"
|
||||
}
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ class ProjectWiki
|
|||
end
|
||||
|
||||
def repository
|
||||
Repository.new(path_with_namespace, default_branch, @project)
|
||||
Repository.new(path_with_namespace, @project)
|
||||
end
|
||||
|
||||
def default_branch
|
||||
|
|
|
@ -15,7 +15,7 @@ class Repository
|
|||
Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete))
|
||||
end
|
||||
|
||||
def initialize(path_with_namespace, default_branch = nil, project = nil)
|
||||
def initialize(path_with_namespace, project)
|
||||
@path_with_namespace = path_with_namespace
|
||||
@project = project
|
||||
end
|
||||
|
@ -44,7 +44,9 @@ class Repository
|
|||
end
|
||||
|
||||
def empty?
|
||||
raw_repository.empty?
|
||||
return @empty unless @empty.nil?
|
||||
|
||||
@empty = cache.fetch(:empty?) { raw_repository.empty? }
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -57,7 +59,11 @@ class Repository
|
|||
# This method return true if repository contains some content visible in project page.
|
||||
#
|
||||
def has_visible_content?
|
||||
raw_repository.branch_count > 0
|
||||
return @has_visible_content unless @has_visible_content.nil?
|
||||
|
||||
@has_visible_content = cache.fetch(:has_visible_content?) do
|
||||
raw_repository.branch_count > 0
|
||||
end
|
||||
end
|
||||
|
||||
def commit(id = 'HEAD')
|
||||
|
@ -78,7 +84,8 @@ class Repository
|
|||
offset: offset,
|
||||
# --follow doesn't play well with --skip. See:
|
||||
# https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
|
||||
follow: false
|
||||
follow: false,
|
||||
skip_merges: skip_merges
|
||||
}
|
||||
|
||||
commits = Gitlab::Git::Commit.where(options)
|
||||
|
@ -184,8 +191,11 @@ class Repository
|
|||
cache.fetch(:"diverging_commit_counts_#{branch.name}") do
|
||||
# Rugged seems to throw a `ReferenceError` when given branch_names rather
|
||||
# than SHA-1 hashes
|
||||
number_commits_behind = commits_between(branch.target, root_ref_hash).size
|
||||
number_commits_ahead = commits_between(root_ref_hash, branch.target).size
|
||||
number_commits_behind = raw_repository.
|
||||
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 }
|
||||
end
|
||||
|
@ -196,12 +206,6 @@ class Repository
|
|||
readme version contribution_guide changelog license)
|
||||
end
|
||||
|
||||
def branch_cache_keys
|
||||
branches.map do |branch|
|
||||
:"diverging_commit_counts_#{branch.name}"
|
||||
end
|
||||
end
|
||||
|
||||
def build_cache
|
||||
cache_keys.each do |key|
|
||||
unless cache.exist?(key)
|
||||
|
@ -226,20 +230,39 @@ class Repository
|
|||
@branches = nil
|
||||
end
|
||||
|
||||
def expire_cache
|
||||
def expire_cache(branch_name = nil)
|
||||
cache_keys.each do |key|
|
||||
cache.expire(key)
|
||||
end
|
||||
|
||||
expire_branch_cache
|
||||
expire_branch_cache(branch_name)
|
||||
end
|
||||
|
||||
def expire_branch_cache
|
||||
branches.each do |branch|
|
||||
cache.expire(:"diverging_commit_counts_#{branch.name}")
|
||||
def expire_branch_cache(branch_name = nil)
|
||||
# When we push to the root branch we have to flush the cache for all other
|
||||
# branches as their statistics are based on the commits relative to the
|
||||
# root branch.
|
||||
if !branch_name || branch_name == root_ref
|
||||
branches.each do |branch|
|
||||
cache.expire(:"diverging_commit_counts_#{branch.name}")
|
||||
end
|
||||
# In case a commit is pushed to a non-root branch we only have to flush the
|
||||
# cache for said branch.
|
||||
else
|
||||
cache.expire(:"diverging_commit_counts_#{branch_name}")
|
||||
end
|
||||
end
|
||||
|
||||
def expire_root_ref_cache
|
||||
cache.expire(:root_ref)
|
||||
@root_ref = nil
|
||||
end
|
||||
|
||||
def expire_has_visible_content_cache
|
||||
cache.expire(:has_visible_content?)
|
||||
@has_visible_content = nil
|
||||
end
|
||||
|
||||
def rebuild_cache
|
||||
cache_keys.each do |key|
|
||||
cache.expire(key)
|
||||
|
@ -477,7 +500,7 @@ class Repository
|
|||
end
|
||||
|
||||
def root_ref
|
||||
@root_ref ||= raw_repository.root_ref
|
||||
@root_ref ||= cache.fetch(:root_ref) { raw_repository.root_ref }
|
||||
end
|
||||
|
||||
def commit_dir(user, path, message, branch)
|
||||
|
|
10
app/models/spam_log.rb
Normal file
10
app/models/spam_log.rb
Normal file
|
@ -0,0 +1,10 @@
|
|||
class SpamLog < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
|
||||
validates :user, presence: true
|
||||
|
||||
def remove_user
|
||||
user.block
|
||||
user.destroy
|
||||
end
|
||||
end
|
5
app/models/spam_report.rb
Normal file
5
app/models/spam_report.rb
Normal file
|
@ -0,0 +1,5 @@
|
|||
class SpamReport < ActiveRecord::Base
|
||||
belongs_to :user
|
||||
|
||||
validates :user, presence: true
|
||||
end
|
|
@ -39,6 +39,8 @@ class Tree
|
|||
|
||||
git_repo = repository.raw_repository
|
||||
@readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path)
|
||||
@readme.load_all_data!(git_repo)
|
||||
@readme
|
||||
end
|
||||
|
||||
def trees
|
||||
|
|
|
@ -138,6 +138,7 @@ class User < ActiveRecord::Base
|
|||
has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
|
||||
has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy
|
||||
has_one :abuse_report, dependent: :destroy
|
||||
has_many :spam_logs, dependent: :destroy
|
||||
has_many :builds, dependent: :nullify, class_name: 'Ci::Build'
|
||||
|
||||
|
||||
|
@ -145,11 +146,8 @@ class User < ActiveRecord::Base
|
|||
# Validations
|
||||
#
|
||||
validates :name, presence: true
|
||||
# Note that a 'uniqueness' and presence check is provided by devise :validatable for email. We do not need to
|
||||
# duplicate that here as the validation framework will have duplicate errors in the event of a failure.
|
||||
validates :email, presence: true, email: { strict_mode: true }
|
||||
validates :notification_email, presence: true, email: { strict_mode: true }
|
||||
validates :public_email, presence: true, email: { strict_mode: true }, allow_blank: true, uniqueness: true
|
||||
validates :notification_email, presence: true, email: true
|
||||
validates :public_email, presence: true, uniqueness: true, email: true, allow_blank: true
|
||||
validates :bio, length: { maximum: 255 }, allow_blank: true
|
||||
validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
|
||||
validates :username,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue