Merge branch 'code-from-ce-6-7' into 'master'
Code from CE 6.7 Fixes #75
@@ -35,3 +35,4 @@ doc/code/*
|
||||
*.log
|
||||
public/uploads.*
|
||||
public/assets/
|
||||
.envrc
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
language: ruby
|
||||
env:
|
||||
global:
|
||||
- DB=mysql
|
||||
- TRAVIS=true
|
||||
matrix:
|
||||
- TASK=spinach
|
||||
- TASK=spec
|
||||
- TASK=jasmine:ci
|
||||
- TASK=spinach DB=mysql
|
||||
- TASK=spec DB=mysql
|
||||
- TASK=jasmine:ci DB=mysql
|
||||
- TASK=spinach DB=postgresql
|
||||
- TASK=spec DB=postgresql
|
||||
- TASK=jasmine:ci DB=postgresql
|
||||
before_install:
|
||||
- sudo apt-get install libicu-dev -y
|
||||
branches:
|
||||
@@ -15,13 +17,12 @@ branches:
|
||||
rvm:
|
||||
- 2.0.0
|
||||
services:
|
||||
- mysql
|
||||
- redis-server
|
||||
before_script:
|
||||
- "cp config/database.yml.$DB config/database.yml"
|
||||
- "cp config/gitlab.yml.example config/gitlab.yml"
|
||||
- "bundle exec rake db:setup"
|
||||
- "bundle exec rake db:seed_fu"
|
||||
- "cp config/database.yml.$DB config/database.yml"
|
||||
- "cp config/gitlab.yml.example config/gitlab.yml"
|
||||
script: "bundle exec rake $TASK --trace"
|
||||
notifications:
|
||||
email: false
|
||||
|
||||
@@ -1,3 +1,42 @@
|
||||
v 6.7.0
|
||||
- Increased the example Nginx client_max_body_size from 5MB to 20MB, consider updating it manually on existing installations
|
||||
- Add support for Gemnasium as a Project Service (Olivier Gonzalez)
|
||||
- Add edit file button to MergeRequest diff
|
||||
- Public groups (Jason Hollingsworth)
|
||||
- Cleaner headers in Notification Emails (Pierre de La Morinerie)
|
||||
- Blob and tree gfm links to anchors work
|
||||
- Piwik Integration (Sebastian Winkler)
|
||||
- Show contribution guide link for new issue form (Jeroen van Baarsen)
|
||||
- Fix CI status for merge requests from fork
|
||||
- Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
|
||||
- Converted all the help sections into markdown
|
||||
- LDAP user filters
|
||||
- Streamline the content of notification emails (Pierre de La Morinerie)
|
||||
- Fixes a bug with group member administration (Matt DeTullio)
|
||||
- Sort tag names using VersionSorter (Robert Speicher)
|
||||
- Add GFM autocompletion for MergeRequests (Robert Speicher)
|
||||
- Add webhook when a new tag is pushed (Jeroen van Baarsen)
|
||||
- Add button for toggling inline comments in diff view
|
||||
- Add retry feature for repository import
|
||||
- Reuse the GitLab LDAP connection within each request
|
||||
- Changed markdown new line behaviour to conform to markdown standards
|
||||
- Fix global search
|
||||
- Faster authorized_keys rebuilding in `rake gitlab:shell:setup` (requires gitlab-shell 1.8.5)
|
||||
- Create and Update MR calls now support the description parameter (Greg Messner)
|
||||
- Markdown relative links in the wiki link to wiki pages, markdown relative links in repositories link to files in the repository
|
||||
|
||||
v 6.6.5
|
||||
- Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
|
||||
- Hide mr close button for comment form if merge request was closed or inline comment
|
||||
- Adds ability to reopen closed merge request
|
||||
|
||||
v 6.6.4
|
||||
- Add missing html escape for highlighted code blocks in comments, issues
|
||||
|
||||
v 6.6.3
|
||||
- Fix 500 error when edit yourself from admin area
|
||||
- Hide private groups for public profiles
|
||||
|
||||
v 6.6.2
|
||||
- Fix 500 error on branch/tag create or remove via UI
|
||||
|
||||
@@ -18,7 +57,7 @@ v 6.6.0
|
||||
- Remove snippet expiration
|
||||
- Mobile UI improvements (Drew Blessing)
|
||||
- Fix block/remove UI for admin::users#show page
|
||||
- Show users' group membership on users' activity page
|
||||
- Show users' group membership on users' activity page (Robert Djurasaj)
|
||||
- User pages are visible without login if user is authorized to a public project
|
||||
- Markdown rendered headers have id derived from their name and link to their id
|
||||
- Improve application to work faster with large groups (100+ members)
|
||||
@@ -27,6 +66,7 @@ v 6.6.0
|
||||
- Restyle Issue#show page and MR#show page
|
||||
- Ability to filter by multiple labels for Issues page
|
||||
- Rails version to 4.0.3
|
||||
- Fixed attachment identifier displaying underneath note text (Jason Blanchard)
|
||||
|
||||
v 6.5.1
|
||||
- Fix branch selectbox when create merge request from fork
|
||||
|
||||
@@ -22,7 +22,7 @@ Issues and merge requests should be in English and contain appropriate language
|
||||
|
||||
## Issue tracker
|
||||
|
||||
To get support for your particular problem please use the channels as detailed in the getting help section of [the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/).
|
||||
To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/).
|
||||
|
||||
The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious bugs or misbehavior in the latest [stable or development release of GitLab](MAINTENANCE.md). When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue.
|
||||
|
||||
@@ -48,7 +48,7 @@ Please send a merge request with a tested solution or a merge request with a fai
|
||||
|
||||
## Merge requests
|
||||
|
||||
We welcome merge requests with fixes and improvements to GitLab code, tests, and/or documentation. The features we would really like a merge request for are listed with the [status 'accepting merge/merge requests' on our feedback forum](http://feedback.gitlab.com/forums/176466-general/status/796455) but other improvements are also welcome.
|
||||
We welcome merge requests with fixes and improvements to GitLab code, tests, and/or documentation. The features we would really like a merge request for are listed with the [status 'accepting merge requests' on our feedback forum](http://feedback.gitlab.com/forums/176466-general/status/796455) but other improvements are also welcome. If you want to add a new feature that is not marked it is best to first create a feedback issue (if there isn't one already) and leave a comment asking for it to be marked accepting merge requests. Please include screenshots or wireframes if the feature will also change the UI.
|
||||
|
||||
### Merge request guidelines
|
||||
|
||||
@@ -66,25 +66,30 @@ If you can, please submit a merge request with the fix or improvements including
|
||||
1. If the MR changes the UI it should include before and after screenshots
|
||||
1. Link relevant [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues) and/or [feedback items](http://feedback.gitlab.com/) from the merge request description and leave a comment on them with a link back to the MR
|
||||
1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submittion
|
||||
1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines]( doc/development/shell_commands.md).
|
||||
|
||||
The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab.com team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features.
|
||||
|
||||
Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? The smaller a MR is the more likely it is it will be merged, after that you can send more MR's to enhance it.
|
||||
|
||||
The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month.
|
||||
The best time to submit a MR and get feedback fast.
|
||||
Before this time the GitLab.com team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud.
|
||||
After the 7th it is already getting closer to the release date of the next version.
|
||||
This means there is less time to fix the issues created by merging large new features.
|
||||
For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). Please ensure that your merge request meets the following contribution acceptance criteria.
|
||||
|
||||
We will accept a merge requests if it:
|
||||
## Contribution acceptance criteria
|
||||
|
||||
* Includes proper tests and all tests pass (unless it contains a test exposing a bug in existing code)
|
||||
* Can be merged without problems (if not please use: `git rebase master`)
|
||||
* Do not break any existing functionality
|
||||
* Conforms to the [Ruby](https://github.com/bbatsov/ruby-style-guide) and [Rails](https://github.com/bbatsov/rails-style-guide) style guides and best practices
|
||||
* Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed)
|
||||
* Keeps the GitLab code base clean and well structured
|
||||
* Contains functionality we think other users will benefit from too
|
||||
* Doesn't add configuration options since these complicate future changes
|
||||
* Contains a single commit (please use `git rebase -i` to squash commits)
|
||||
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. Can merge without problems (if not please use: `git rebase master`)
|
||||
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. 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. Contains a single commit (please use `git rebase -i` to squash commits)
|
||||
1. It conforms to the following style guides
|
||||
|
||||
For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed).
|
||||
## Style guides
|
||||
|
||||
1. [Ruby style guide](https://github.com/bbatsov/ruby-style-guide)
|
||||
1. [Rails style guide](https://github.com/bbatsov/rails-style-guide)
|
||||
1. [CoffeeScript style guide](https://github.com/polarmobile/coffeescript-style-guide)
|
||||
1. [Shell command guidelines](doc/development/shell_commands.md)
|
||||
|
||||
@@ -15,6 +15,9 @@ gem 'rails-observers'
|
||||
gem 'actionpack-page_caching'
|
||||
gem 'actionpack-action_caching'
|
||||
|
||||
# Default values for AR models
|
||||
gem "default_value_for", "~> 3.0.0"
|
||||
|
||||
# Supported DBs
|
||||
gem "mysql2", group: :mysql
|
||||
gem "pg", group: :postgres
|
||||
@@ -29,7 +32,7 @@ gem 'omniauth-github'
|
||||
|
||||
# Extracting information from a git repository
|
||||
# Provide access to Gitlab::Git library
|
||||
gem "gitlab_git", '~> 5.4.0'
|
||||
gem "gitlab_git", '~> 5.7.1'
|
||||
|
||||
# Ruby/Rack Git Smart-HTTP Server Handler
|
||||
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
|
||||
@@ -46,7 +49,8 @@ gem "gitlab-linguist", "~> 3.0.0", require: "linguist"
|
||||
|
||||
# API
|
||||
gem "grape", "~> 0.6.1"
|
||||
gem "grape-entity", "~> 0.3.0"
|
||||
# Replace with rubygems when nesteted entities get released
|
||||
gem "grape-entity", "~> 0.4.1", ref: 'd904381c951e86250c3f44213b349a3dd8e83fb1', git: 'https://github.com/intridea/grape-entity.git'
|
||||
gem 'rack-cors', require: 'rack/cors'
|
||||
|
||||
# Email validation
|
||||
@@ -112,6 +116,7 @@ gem 'settingslogic'
|
||||
|
||||
# Misc
|
||||
gem "foreman"
|
||||
gem 'version_sorter'
|
||||
|
||||
# Cache
|
||||
gem "redis-rails"
|
||||
@@ -125,6 +130,9 @@ gem "hipchat", "~> 0.14.0"
|
||||
# Flowdock integration
|
||||
gem "gitlab-flowdock-git-hook", "~> 0.4.2"
|
||||
|
||||
# Gemnasium integration
|
||||
gem "gemnasium-gitlab-service", "~> 0.2"
|
||||
|
||||
# d3
|
||||
gem "d3_rails", "~> 3.1.4"
|
||||
|
||||
@@ -162,7 +170,7 @@ group :development do
|
||||
gem "annotate", "~> 2.6.0.beta2"
|
||||
gem "letter_opener"
|
||||
gem 'quiet_assets', '~> 1.0.1'
|
||||
gem 'rack-mini-profiler'
|
||||
gem 'rack-mini-profiler', require: false
|
||||
|
||||
# Better errors handler
|
||||
gem 'better_errors'
|
||||
|
||||
@@ -5,6 +5,15 @@ GIT
|
||||
specs:
|
||||
github-markup (0.7.6)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/intridea/grape-entity.git
|
||||
revision: d904381c951e86250c3f44213b349a3dd8e83fb1
|
||||
ref: d904381c951e86250c3f44213b349a3dd8e83fb1
|
||||
specs:
|
||||
grape-entity (0.4.1)
|
||||
activesupport
|
||||
multi_json (>= 1.3.2)
|
||||
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
@@ -101,6 +110,8 @@ GEM
|
||||
daemons (1.1.9)
|
||||
database_cleaner (1.2.0)
|
||||
debug_inspector (0.0.2)
|
||||
default_value_for (3.0.0)
|
||||
activerecord (>= 3.2.0, < 5.0)
|
||||
descendants_tracker (0.0.3)
|
||||
devise (3.0.4)
|
||||
bcrypt-ruby (~> 3.0)
|
||||
@@ -152,6 +163,8 @@ GEM
|
||||
dotenv (>= 0.7)
|
||||
thor (>= 0.13.6)
|
||||
formatador (0.2.4)
|
||||
gemnasium-gitlab-service (0.2.1)
|
||||
rugged (~> 0.19)
|
||||
gemoji (1.3.1)
|
||||
gherkin-ruby (0.3.1)
|
||||
racc
|
||||
@@ -177,7 +190,7 @@ GEM
|
||||
charlock_holmes (~> 0.6.6)
|
||||
escape_utils (~> 0.2.4)
|
||||
mime-types (~> 1.19)
|
||||
gitlab_git (5.4.0)
|
||||
gitlab_git (5.7.1)
|
||||
activesupport (~> 4.0.0)
|
||||
charlock_holmes (~> 0.6.9)
|
||||
gitlab-grit (~> 2.6.1)
|
||||
@@ -202,9 +215,6 @@ GEM
|
||||
rack-accept
|
||||
rack-mount
|
||||
virtus (>= 1.0.0)
|
||||
grape-entity (0.3.0)
|
||||
activesupport
|
||||
multi_json (>= 1.3.2)
|
||||
growl (1.0.3)
|
||||
guard (2.2.4)
|
||||
formatador (>= 0.2.4)
|
||||
@@ -231,7 +241,7 @@ GEM
|
||||
httparty
|
||||
httparty
|
||||
http_parser.rb (0.5.3)
|
||||
httparty (0.12.0)
|
||||
httparty (0.13.0)
|
||||
json (~> 1.8)
|
||||
multi_xml (>= 0.5.2)
|
||||
httpauth (0.2.0)
|
||||
@@ -338,7 +348,7 @@ GEM
|
||||
rack-attack (2.3.0)
|
||||
rack
|
||||
rack-cors (0.2.9)
|
||||
rack-mini-profiler (0.1.31)
|
||||
rack-mini-profiler (0.9.0)
|
||||
rack (>= 1.1.3)
|
||||
rack-mount (0.8.3)
|
||||
rack (>= 1.0.0)
|
||||
@@ -533,6 +543,7 @@ GEM
|
||||
raindrops (~> 0.7)
|
||||
unicorn-worker-killer (0.4.2)
|
||||
unicorn (~> 4)
|
||||
version_sorter (1.1.0)
|
||||
virtus (1.0.1)
|
||||
axiom-types (~> 0.0.5)
|
||||
coercible (~> 1.0)
|
||||
@@ -568,6 +579,7 @@ DEPENDENCIES
|
||||
coveralls
|
||||
d3_rails (~> 3.1.4)
|
||||
database_cleaner
|
||||
default_value_for (~> 3.0.0)
|
||||
devise (= 3.0.4)
|
||||
devise-async (= 0.8.0)
|
||||
email_spec
|
||||
@@ -578,18 +590,19 @@ DEPENDENCIES
|
||||
fog (~> 1.3.1)
|
||||
font-awesome-rails (~> 3.2)
|
||||
foreman
|
||||
gemnasium-gitlab-service (~> 0.2)
|
||||
gemoji (~> 1.3.0)
|
||||
github-markup (~> 0.7.4)!
|
||||
gitlab-flowdock-git-hook (~> 0.4.2)
|
||||
gitlab-gollum-lib (~> 1.1.0)
|
||||
gitlab-grack (~> 2.0.0.pre)
|
||||
gitlab-linguist (~> 3.0.0)
|
||||
gitlab_git (~> 5.4.0)
|
||||
gitlab_git (~> 5.7.1)
|
||||
gitlab_meta (= 6.0)
|
||||
gitlab_omniauth-ldap (= 1.0.4)
|
||||
gon (~> 5.0.0)
|
||||
grape (~> 0.6.1)
|
||||
grape-entity (~> 0.3.0)
|
||||
grape-entity (~> 0.4.1)!
|
||||
growl
|
||||
guard-rspec
|
||||
guard-spinach
|
||||
@@ -657,4 +670,5 @@ DEPENDENCIES
|
||||
underscore-rails (~> 1.4.4)
|
||||
unicorn (~> 4.6.3)
|
||||
unicorn-worker-killer
|
||||
version_sorter
|
||||
webmock
|
||||
|
||||
@@ -12,7 +12,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
|
||||
- Closes invalid issues and merge requests with a comment (duplicates, [feature requests](#feature-requests), [fixed in newer version](#issue-fixed-in-newer-version), [issue report for old version](#issue-report-for-old-version), not a problem in GitLab, etc.)
|
||||
- Assigns appropriate [labels](#how-we-handle-issues)
|
||||
- Asks for feedback from issue reporter/merge request initiator ([invalid issue reports](#improperly-formatted-issue), [format code](#code-format), etc.)
|
||||
- Asks for feedback from the relevant developer(s) based on the [list of members and their specialities](http://gitlab.org/team/)
|
||||
- Asks for feedback from the relevant developer(s) based on the [list of members and their specialities](https://www.gitlab.com/core-team/)
|
||||
- Monitors all issues/merge requests for feedback (but especially ones commented on since automatically watching them):
|
||||
- Closes issues with no feedback from the reporter for two weeks
|
||||
- Closes stale merge requests
|
||||
@@ -37,7 +37,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
|
||||
|
||||
## Mentioning people
|
||||
|
||||
The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](http://gitlab.org/team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person.
|
||||
The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://www.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person.
|
||||
|
||||
## Workflow labels
|
||||
|
||||
@@ -89,7 +89,7 @@ Please use ``` to format console output, logs, and code as it's very hard to rea
|
||||
|
||||
### Issue fixed in newer version
|
||||
|
||||
Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(http://blog.gitlab.org/). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
|
||||
Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://github.com/gitlabhq/gitlabhq/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines).
|
||||
|
||||
### Improperly formatted merge request
|
||||
|
||||
|
||||
@@ -23,19 +23,17 @@
|
||||
|
||||
### Resources
|
||||
|
||||
* GitLab.org community site: [Homepage](http://gitlab.org) | [Screenshots](http://gitlab.org/screenshots/) | [Blog](http://blog.gitlab.org/) | [Demo](http://demo.gitlabhq.com/users/sign_in)
|
||||
* [GitLab.com](https://www.gitlab.com/) includes information about [subscriptions](https://www.gitlab.com/subscription/), [consultancy](https://www.gitlab.com/consultancy/), the [community](https://www.gitlab.com/community/) and the [hosted GitLab Cloud](https://www.gitlab.com/cloud/).
|
||||
|
||||
* GitLab.com commercial services: [Homepage](http://www.gitlab.com/) | [Subscription](http://www.gitlab.com/subscription/) | [Consultancy](http://www.gitlab.com/consultancy/) | [GitLab Cloud](http://www.gitlab.com/cloud/) | [Blog](http://blog.gitlab.com/)
|
||||
* [GitLab Enterprise Edition](https://www.gitlab.com/gitlab-ce/) offers additional features that are useful for larger organizations (100+ users).
|
||||
|
||||
* [GitLab Enterprise Edition](https://www.gitlab.com/features/) offers additional features that are useful for larger organizations (100+ users).
|
||||
|
||||
* [GitLab CI](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/README.md) is a continuous integration (CI) server that is easy to integrate with GitLab.
|
||||
* [GitLab CI](https://www.gitlab.com/gitlab-ci/) is a continuous integration (CI) server that is easy to integrate with GitLab.
|
||||
|
||||
* Unofficial third-party [iPhone app](http://gitlabcontrol.com/) and [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en) for GitLab
|
||||
|
||||
### Requirements
|
||||
|
||||
* Ubuntu/Debian**
|
||||
* Ubuntu/Debian/CentOS/RHEL**
|
||||
* ruby 1.9.3+
|
||||
* git 1.7.10+
|
||||
* redis 2.0+
|
||||
@@ -47,7 +45,7 @@
|
||||
|
||||
#### Official installation methods
|
||||
|
||||
* [GitLab packages (beta)](https://www.gitlab.com/downloads/) These packages contain GitLab and all its depencies (PostgreSQL, Redis, Nginx, Unicorn, etc.). They are made with [omnibus-gitlab](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md) that also contains the installation instructions. These packages currently support a reduced selection of GitLab's normal features. For instance, it is not yet possible to create/restore application backups or to use HTTPS.
|
||||
* [GitLab packages](https://www.gitlab.com/downloads/) These packages contain GitLab and all its depencies (Ruby, PostgreSQL, Redis, Nginx, Unicorn, etc.). They are made with [omnibus-gitlab](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md) that also contains the installation instructions.
|
||||
|
||||
* [GitLab virtual machine images](https://www.gitlab.com/downloads/) contain an operating system and a preinstalled GitLab. They are made with [GitLab Packer](https://gitlab.com/gitlab-org/gitlab-packer/blob/master/README.md) that also contains the installation instructions.
|
||||
|
||||
@@ -155,12 +153,3 @@ or start each component separately
|
||||
* [Book](http://www.packtpub.com/gitlab-repository-management/book) written by GitLab enthusiast Jonathan M. Hethey is unofficial but it offers a good overview.
|
||||
|
||||
* [Gitter chat room](https://gitter.im/gitlabhq/gitlabhq#) here you can ask questions when you need help.
|
||||
|
||||
|
||||
### Getting in touch
|
||||
|
||||
* [Core team](http://gitlab.org/team/)
|
||||
|
||||
* [Contributors](http://contributors.gitlab.org/)
|
||||
|
||||
* [Community](http://gitlab.org/community/)
|
||||
|
||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 4.3 KiB After Width: | Height: | Size: 4.3 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 3.4 KiB After Width: | Height: | Size: 3.4 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 494 B After Width: | Height: | Size: 494 B |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
|
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.1 KiB |
@@ -1,5 +1,15 @@
|
||||
$ ->
|
||||
$("body").on "click", ".js-details-target", ->
|
||||
container = $(@).closest(".js-details-container")
|
||||
|
||||
container.toggleClass("open")
|
||||
|
||||
# Show details content. Hides link after click.
|
||||
#
|
||||
# %div
|
||||
# %a.js-details-expand
|
||||
# %div.js-details-content
|
||||
#
|
||||
$("body").on "click", ".js-details-expand", (e) ->
|
||||
$(@).next('.js-details-content').removeClass("hide")
|
||||
$(@).hide()
|
||||
e.preventDefault()
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
$ ->
|
||||
$("body").on "click", ".js-toggler-target", ->
|
||||
container = $(@).closest(".js-toggler-container")
|
||||
|
||||
container.toggleClass("on")
|
||||
|
||||
$("body").on "click", ".js-toggle-visibility-link", (e) ->
|
||||
# Toggle button. Show/hide content inside parent container.
|
||||
# Button does not change visibility. If button has icon - it changes chevron style.
|
||||
#
|
||||
# %div.js-toggle-container
|
||||
# %a.js-toggle-button
|
||||
# %div.js-toggle-content
|
||||
#
|
||||
$("body").on "click", ".js-toggle-button", (e) ->
|
||||
$(@).find('i').
|
||||
toggleClass('icon-chevron-down').
|
||||
toggleClass('icon-chevron-up')
|
||||
container = $(".js-toggle-visibility-container")
|
||||
container.toggleClass("hide")
|
||||
e.preventDefault()
|
||||
|
||||
$("body").on "click", ".js-toggle-button", (e) ->
|
||||
$(@).closest(".js-toggle-container").find(".js-toggle-content").toggle()
|
||||
e.preventDefault()
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
class Commit
|
||||
constructor: ->
|
||||
$('.files .file').each ->
|
||||
$('.files .diff-file').each ->
|
||||
new CommitFile(this)
|
||||
|
||||
@Commit = Commit
|
||||
|
||||
@@ -13,6 +13,7 @@ GitLab.GfmAutoComplete =
|
||||
Members:
|
||||
template: '<li data-value="${username}">${username} <small>${name}</small></li>'
|
||||
|
||||
# Issues and MergeRequests
|
||||
Issues:
|
||||
template: '<li data-value="${id}"><small>${id}</small> ${title} </li>'
|
||||
|
||||
@@ -46,11 +47,22 @@ GitLab.GfmAutoComplete =
|
||||
before_save: (issues) ->
|
||||
$.map issues, (i) -> id: i.iid, title: sanitize(i.title), search: "#{i.iid} #{i.title}"
|
||||
|
||||
input.atwho
|
||||
at: '!'
|
||||
alias: 'mergerequests'
|
||||
search_key: 'search'
|
||||
tpl: @Issues.template
|
||||
callbacks:
|
||||
before_save: (merges) ->
|
||||
$.map merges, (m) -> id: m.iid, title: sanitize(m.title), search: "#{m.iid} #{m.title}"
|
||||
|
||||
input.one "focus", =>
|
||||
$.getJSON(@dataSource).done (data) ->
|
||||
# load members
|
||||
input.atwho 'load', "@", data.members
|
||||
# load issues
|
||||
input.atwho 'load', "issues", data.issues
|
||||
# load merge requests
|
||||
input.atwho 'load', "mergerequests", data.mergerequests
|
||||
# load emojis
|
||||
input.atwho 'load', ":", data.emojis
|
||||
|
||||
@@ -62,11 +62,6 @@ $ ->
|
||||
$('.remove-row').bind 'ajax:success', ->
|
||||
$(this).closest('li').fadeOut()
|
||||
|
||||
# Click a .appear-link, appear-data fadeout
|
||||
$(".appear-link").on 'click', (e) ->
|
||||
$('.appear-data').fadeIn()
|
||||
e.preventDefault()
|
||||
|
||||
# Initialize select2 selects
|
||||
$('select.select2').select2(width: 'resolve', dropdownAutoWidth: true)
|
||||
|
||||
@@ -117,14 +112,10 @@ $ ->
|
||||
|
||||
|
||||
# Commit show suppressed diff
|
||||
$(".content").on "click", ".supp_diff_link", ->
|
||||
$(".diff-content").on "click", ".supp_diff_link", ->
|
||||
$(@).next('table').show()
|
||||
$(@).remove()
|
||||
|
||||
$(".content").on "click", ".js-details-expand", ->
|
||||
$(@).next('.js-details-contain').removeClass("hide")
|
||||
$(@).remove()
|
||||
|
||||
(($) ->
|
||||
# Disable an element and add the 'disabled' Bootstrap class
|
||||
$.fn.extend disable: ->
|
||||
|
||||
@@ -10,6 +10,16 @@
|
||||
query: (query) ->
|
||||
Api.projectUsers project_id, query.term, (users) ->
|
||||
data = { results: users }
|
||||
|
||||
nullUser = {
|
||||
name: 'Unassigned',
|
||||
avatar: null,
|
||||
username: 'none',
|
||||
id: ''
|
||||
}
|
||||
|
||||
data.results.unshift(nullUser)
|
||||
|
||||
query.callback(data)
|
||||
|
||||
initSelection: (element, callback) ->
|
||||
@@ -35,8 +45,13 @@
|
||||
else
|
||||
avatar = gon.relative_url_root + "/assets/no_avatar.png"
|
||||
|
||||
if user.id == ''
|
||||
avatarMarkup = ''
|
||||
else
|
||||
avatarMarkup = "<div class='user-image'><img class='avatar s24' src='#{avatar}'></div>"
|
||||
|
||||
"<div class='user-result'>
|
||||
<div class='user-image'><img class='avatar s24' src='#{avatar}'></div>
|
||||
#{avatarMarkup}
|
||||
<div class='user-name'>#{user.name}</div>
|
||||
<div class='user-username'>#{user.username}</div>
|
||||
</div>"
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
@import "generic/issue_box.scss";
|
||||
@import "generic/files.scss";
|
||||
@import "generic/lists.scss";
|
||||
@import "generic/flash.scss";
|
||||
@import "generic/forms.scss";
|
||||
@import "generic/selects.scss";
|
||||
@import "generic/highlight.scss";
|
||||
@@ -47,6 +48,7 @@
|
||||
@import "sections/header.scss";
|
||||
@import "sections/nav.scss";
|
||||
@import "sections/commits.scss";
|
||||
@import "sections/diff.scss";
|
||||
@import "sections/issues.scss";
|
||||
@import "sections/projects.scss";
|
||||
@import "sections/snippets.scss";
|
||||
@@ -68,7 +70,7 @@
|
||||
@import "sections/groups.scss";
|
||||
|
||||
/**
|
||||
* Code ighlight
|
||||
* Code highlight
|
||||
*/
|
||||
@import "highlight/white.scss";
|
||||
@import "highlight/dark.scss";
|
||||
|
||||
@@ -2,3 +2,18 @@
|
||||
background: #f9f9f9;
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.centered-light-block {
|
||||
text-align: center;
|
||||
color: #888;
|
||||
margin: 20px;
|
||||
}
|
||||
|
||||
.nothing-here-block {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #666;
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
@@ -158,6 +158,14 @@
|
||||
color: #468847;
|
||||
}
|
||||
}
|
||||
|
||||
&.btn-grouped {
|
||||
margin-right: 7px;
|
||||
float: left;
|
||||
&:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-block {
|
||||
@@ -169,9 +177,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
.btn,
|
||||
.btn-group {
|
||||
&.grouped {
|
||||
&.btn-grouped {
|
||||
margin-right: 7px;
|
||||
float: left;
|
||||
&:last-child {
|
||||
|
||||
@@ -11,8 +11,6 @@
|
||||
.bgred { background: #F2DEDE!important }
|
||||
|
||||
/** COMMON CLASSES **/
|
||||
.left { float:left }
|
||||
|
||||
.prepend-top-10 { margin-top:10px }
|
||||
.prepend-top-20 { margin-top:20px }
|
||||
.prepend-left-10 { margin-left:10px }
|
||||
@@ -24,32 +22,9 @@
|
||||
.append-bottom-20 { margin-bottom:20px }
|
||||
.inline { display: inline-block }
|
||||
|
||||
.padded { padding:20px }
|
||||
.ipadded { padding:20px!important }
|
||||
.lborder { border-left:1px solid #eee }
|
||||
.underlined_link { text-decoration: underline; }
|
||||
.underlined-link { text-decoration: underline; }
|
||||
.hint { font-style: italic; color: #999; }
|
||||
.light { color: #888 }
|
||||
.tiny { font-weight: normal }
|
||||
.vtop { vertical-align: top !important; }
|
||||
|
||||
|
||||
/** ALERT MESSAGES **/
|
||||
.alert.alert-disabled {
|
||||
background: #EEE;
|
||||
color: #777;
|
||||
border-color: #DDD;
|
||||
}
|
||||
|
||||
/** HELPERS **/
|
||||
.nothing_here_message {
|
||||
text-align: center;
|
||||
padding: 20px;
|
||||
color: #666;
|
||||
font-weight: normal;
|
||||
font-size: 16px;
|
||||
line-height: 36px;
|
||||
}
|
||||
|
||||
.slead {
|
||||
color: #666;
|
||||
@@ -59,53 +34,23 @@
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
|
||||
.tab-content {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.only-wide {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
pre.well-pre {
|
||||
border: 1px solid #EEE;
|
||||
background: #f9f9f9;
|
||||
border-radius: 0;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.input-append .btn.active, .input-prepend .btn.active {
|
||||
background: #CCC;
|
||||
border-color: #BBB;
|
||||
text-shadow: 0 1px 1px #fff;
|
||||
font-weight: bold;
|
||||
@include box-shadow(inset 0 2px 4px rgba(0,0,0,.15));
|
||||
}
|
||||
|
||||
/** Big Labels **/
|
||||
.state-label {
|
||||
font-size: 14px;
|
||||
padding: 9px 25px;
|
||||
text-align: center;
|
||||
text-shadow: none;
|
||||
margin-right: 20px;
|
||||
|
||||
&.state-label-blue {
|
||||
background: #31708f;
|
||||
color: #FFF;
|
||||
pre {
|
||||
&.clean {
|
||||
background: none;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
&.state-label-green {
|
||||
background: #4A4;
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
&.state-label-red {
|
||||
background: #DA4E49;
|
||||
color: #FFF;
|
||||
&.well-pre {
|
||||
border: 1px solid #EEE;
|
||||
background: #f9f9f9;
|
||||
border-radius: 0;
|
||||
color: #555;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,31 +80,6 @@ pre.well-pre {
|
||||
}
|
||||
|
||||
/** FLASH message **/
|
||||
.flash-container {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
opacity: 0.8;
|
||||
z-index: 100;
|
||||
|
||||
.flash-notice {
|
||||
background: #49C;
|
||||
padding: 10px;
|
||||
text-shadow: 0 1px 1px #178;
|
||||
}
|
||||
|
||||
.flash-alert {
|
||||
background: #C67;
|
||||
text-shadow: 0 1px 1px #945;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
.author_link {
|
||||
color: $link_color;
|
||||
}
|
||||
@@ -279,22 +199,6 @@ li.note {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.merge-request,
|
||||
.issue {
|
||||
&.today{
|
||||
background: #EFE;
|
||||
border-color: #CEC;
|
||||
}
|
||||
&.closed {
|
||||
background: #F5f5f5;
|
||||
border-color: #E5E5E5;
|
||||
}
|
||||
&.merged {
|
||||
background: #F5f5f5;
|
||||
border-color: #E5E5E5;
|
||||
}
|
||||
}
|
||||
|
||||
.git_error_tips {
|
||||
@extend .col-md-6;
|
||||
text-align: left;
|
||||
@@ -358,15 +262,6 @@ li.note {
|
||||
}
|
||||
}
|
||||
|
||||
pre {
|
||||
&.clean {
|
||||
background: none;
|
||||
border: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.milestone {
|
||||
&.milestone-closed {
|
||||
background: #eee;
|
||||
@@ -403,10 +298,6 @@ img.emoji {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.appear-data {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chart {
|
||||
overflow: hidden;
|
||||
height: 220px;
|
||||
@@ -459,40 +350,6 @@ table {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.ajax-users-select {
|
||||
width: 400px;
|
||||
|
||||
&.input-large {
|
||||
width: 210px;
|
||||
}
|
||||
|
||||
&.input-clamp {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.user-result {
|
||||
.user-image {
|
||||
float: left;
|
||||
}
|
||||
.user-name {
|
||||
}
|
||||
.user-username {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.namespace-result {
|
||||
.namespace-kind {
|
||||
color: #AAA;
|
||||
font-weight: normal;
|
||||
}
|
||||
.namespace-path {
|
||||
margin-left: 10px;
|
||||
font-weight: bolder;
|
||||
}
|
||||
}
|
||||
|
||||
.btn-sign-in {
|
||||
margin-top: 7px;
|
||||
text-shadow: none;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
.flash-container {
|
||||
display: none;
|
||||
cursor: pointer;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 14px;
|
||||
position: fixed;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
opacity: 0.8;
|
||||
z-index: 100;
|
||||
|
||||
.flash-notice {
|
||||
background: #49C;
|
||||
padding: 10px;
|
||||
text-shadow: 0 1px 1px #178;
|
||||
}
|
||||
|
||||
.flash-alert {
|
||||
background: #C67;
|
||||
text-shadow: 0 1px 1px #945;
|
||||
padding: 10px;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
input[type='search'].search-text-input {
|
||||
background-image: url("icon-search.png");
|
||||
background-image: image-url("icon-search.png");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 10px;
|
||||
padding-left: 25px;
|
||||
|
||||
@@ -10,9 +10,61 @@
|
||||
.issue-box {
|
||||
color: #666;
|
||||
margin:20px 0;
|
||||
background: #FAFAFA;
|
||||
background: #FFF;
|
||||
border: 1px solid #EEE;
|
||||
|
||||
&.issue-box-closed {
|
||||
border-color: #DA4E49;
|
||||
.state {
|
||||
background-color: #f2dede;
|
||||
border-color: #ebccd1;
|
||||
color: #a94442;
|
||||
.state-label {
|
||||
background: #DA4E49;
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.issue-box-merged {
|
||||
border-color: #31708f;
|
||||
.state {
|
||||
background-color: #d9edf7;
|
||||
border-color: #bce8f1;
|
||||
color: #31708f;
|
||||
.state-label {
|
||||
background: #31708f;
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.issue-box-open {
|
||||
border-color: #4A4;
|
||||
.state {
|
||||
background-color: #dff0d8;
|
||||
border-color: #d6e9c6;
|
||||
color: #3c763d;
|
||||
.state-label {
|
||||
background: #4A4;
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.issue-box-expired {
|
||||
border-color: #cea61b;
|
||||
.state {
|
||||
background-color: #fcf8e3;
|
||||
border-color: #faebcc;
|
||||
color: #8a6d3b;
|
||||
.state-label {
|
||||
background: #cea61b;
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.control-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
@@ -48,4 +100,13 @@
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.state-label {
|
||||
font-size: 14px;
|
||||
padding: 1px 25px;
|
||||
text-align: center;
|
||||
text-shadow: none;
|
||||
margin-right: 20px;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,3 +87,37 @@ select {
|
||||
padding-top: 4px;
|
||||
}
|
||||
}
|
||||
|
||||
.ajax-users-select {
|
||||
width: 400px;
|
||||
|
||||
&.input-large {
|
||||
width: 210px;
|
||||
}
|
||||
|
||||
&.input-clamp {
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.user-result {
|
||||
.user-image {
|
||||
float: left;
|
||||
}
|
||||
.user-name {
|
||||
}
|
||||
.user-username {
|
||||
color: #999;
|
||||
}
|
||||
}
|
||||
|
||||
.namespace-result {
|
||||
.namespace-kind {
|
||||
color: #AAA;
|
||||
font-weight: normal;
|
||||
}
|
||||
.namespace-path {
|
||||
margin-left: 10px;
|
||||
font-weight: bolder;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ a:focus {
|
||||
display: inline-block;
|
||||
width: $size;
|
||||
height: $size;
|
||||
background-image: url("icon-link.png");
|
||||
background-image: image-url("icon-link.png");
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
|
||||
@@ -178,3 +178,9 @@
|
||||
.shadow {
|
||||
@include box-shadow(0 5px 15px #000);
|
||||
}
|
||||
|
||||
.wiki, .note-body {
|
||||
.highlight {
|
||||
border: 1px solid #DDD;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,7 @@ html {
|
||||
}
|
||||
|
||||
body {
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
|
||||
.container {
|
||||
|
||||
@@ -10,332 +10,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
.file {
|
||||
border: 1px solid #CCC;
|
||||
margin-bottom: 1em;
|
||||
|
||||
.header {
|
||||
@extend .clearfix;
|
||||
background: #DDD;
|
||||
border-bottom: 1px solid #CCC;
|
||||
padding: 5px 5px 5px 10px;
|
||||
color: #555;
|
||||
|
||||
> span {
|
||||
font-family: $monospace_font;
|
||||
font-size: 14px;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.view-file {
|
||||
font-weight: bold;
|
||||
float: right;
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
.commit-short-id {
|
||||
font-family: $monospace_font;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.file-mode {
|
||||
font-family: $monospace_font;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
overflow: auto;
|
||||
overflow-y: hidden;
|
||||
background: #FFF;
|
||||
color: #333;
|
||||
font-size: 12px;
|
||||
.old {
|
||||
span.idiff {
|
||||
background-color: #FAA;
|
||||
}
|
||||
}
|
||||
.new {
|
||||
span.idiff {
|
||||
background-color: #AFA;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
font-family: $monospace_font;
|
||||
border: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
td {
|
||||
line-height: 18px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.old_line, .new_line, .diff_line {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: none;
|
||||
background: #EEE;
|
||||
color: #666;
|
||||
padding: 0px 5px;
|
||||
border-right: 1px solid #ccc;
|
||||
text-align: right;
|
||||
min-width: 35px;
|
||||
max-width: 50px;
|
||||
width: 35px;
|
||||
@include user-select(none);
|
||||
a {
|
||||
float: left;
|
||||
width: 35px;
|
||||
font-weight: normal;
|
||||
color: #666;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
&.new {
|
||||
background: #CFD;
|
||||
}
|
||||
&.old {
|
||||
background: #FDD;
|
||||
}
|
||||
}
|
||||
.diff_line {
|
||||
padding: 0;
|
||||
}
|
||||
.line_holder {
|
||||
&.old .old_line,
|
||||
&.old .new_line {
|
||||
background: #FCC;
|
||||
border-color: #E7BABA;
|
||||
}
|
||||
&.new .old_line,
|
||||
&.new .new_line {
|
||||
background: #CFC;
|
||||
border-color: #B9ECB9;
|
||||
}
|
||||
}
|
||||
.line_content {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
height: 18px;
|
||||
margin: 0px;
|
||||
padding: 0px 0.5em;
|
||||
border: none;
|
||||
&.new {
|
||||
background: #CFD;
|
||||
}
|
||||
&.old {
|
||||
background: #FDD;
|
||||
}
|
||||
&.matched {
|
||||
color: #ccc;
|
||||
background: #fafafa;
|
||||
}
|
||||
&.parallel {
|
||||
display: table-cell;
|
||||
overflow: hidden;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.image {
|
||||
background: #ddd;
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
.wrap{
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.frame {
|
||||
display: inline-block;
|
||||
background-color: #fff;
|
||||
line-height: 0;
|
||||
img{
|
||||
border: 1px solid #FFF;
|
||||
background: url('trans_bg.gif');
|
||||
max-width: 100%;
|
||||
}
|
||||
&.deleted {
|
||||
border: 1px solid $deleted;
|
||||
}
|
||||
|
||||
&.added {
|
||||
border: 1px solid $added;
|
||||
}
|
||||
}
|
||||
.image-info{
|
||||
font-size: 12px;
|
||||
margin: 5px 0 0 0;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.view.swipe{
|
||||
position: relative;
|
||||
|
||||
.swipe-frame{
|
||||
display: block;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
}
|
||||
.swipe-wrap{
|
||||
overflow: hidden;
|
||||
border-left: 1px solid #999;
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 13px;
|
||||
right: 7px;
|
||||
}
|
||||
.frame{
|
||||
top: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
&.deleted{
|
||||
margin: 0;
|
||||
display: block;
|
||||
top: 13px;
|
||||
right: 7px;
|
||||
}
|
||||
}
|
||||
.swipe-bar{
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 15px;
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
.top-handle{
|
||||
background-position: -15px 3px;
|
||||
}
|
||||
.bottom-handle{
|
||||
background-position: -15px -11px;
|
||||
}
|
||||
};
|
||||
.top-handle{
|
||||
display: block;
|
||||
height: 14px;
|
||||
width: 15px;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
background: url('swipemode_sprites.gif') 0 3px no-repeat;
|
||||
}
|
||||
.bottom-handle{
|
||||
display: block;
|
||||
height: 14px;
|
||||
width: 15px;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
background: url('swipemode_sprites.gif') 0 -11px no-repeat;
|
||||
}
|
||||
}
|
||||
} //.view.swipe
|
||||
.view.onion-skin{
|
||||
.onion-skin-frame{
|
||||
display: block;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
}
|
||||
.frame.added, .frame.deleted {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
.controls{
|
||||
display: block;
|
||||
height: 14px;
|
||||
width: 300px;
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 50%;
|
||||
margin-left: -150px;
|
||||
|
||||
.drag-track{
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
height: 10px;
|
||||
width: 276px;
|
||||
background: url('onion_skin_sprites.gif') -4px -20px repeat-x;
|
||||
}
|
||||
|
||||
.dragger {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
background: url('onion_skin_sprites.gif') 0px -34px repeat-x;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.transparent {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 0px;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
background: url('onion_skin_sprites.gif') -2px 0px no-repeat;
|
||||
}
|
||||
|
||||
.opaque {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 0px;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
background: url('onion_skin_sprites.gif') -2px -10px no-repeat;
|
||||
}
|
||||
}
|
||||
} //.view.onion-skin
|
||||
}
|
||||
.view-modes{
|
||||
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
|
||||
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
|
||||
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
|
||||
background-image: -ms-linear-gradient(#eee 6.6%, #dfdfdf);
|
||||
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
|
||||
|
||||
ul, li{
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
li{
|
||||
color: grey;
|
||||
border-left: 1px solid #c1c1c1;
|
||||
padding: 0 12px 0 16px;
|
||||
cursor: pointer;
|
||||
&:first-child{
|
||||
border-left: none;
|
||||
}
|
||||
&:hover{
|
||||
text-decoration: underline;
|
||||
}
|
||||
&.active{
|
||||
&:hover{
|
||||
text-decoration: none;
|
||||
}
|
||||
cursor: default;
|
||||
color: #333;
|
||||
}
|
||||
&.disabled{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** COMMIT BLOCK **/
|
||||
.commit-title{
|
||||
display: block;
|
||||
@@ -399,7 +73,7 @@
|
||||
|
||||
|
||||
.commits-compare-switch{
|
||||
background: url("switch_icon.png") no-repeat center center;
|
||||
background: image-url("switch_icon.png") no-repeat center center;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
text-indent: -9999px;
|
||||
@@ -494,6 +168,25 @@ li.commit {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
.text-expander {
|
||||
background: #eee;
|
||||
color: #555;
|
||||
padding: 0 5px;
|
||||
cursor: pointer;
|
||||
margin-left: 4px;
|
||||
&:hover {
|
||||
background-color: #ddd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.commit-row-description {
|
||||
font-size: 14px;
|
||||
border-left: 1px solid #e5e5e5;
|
||||
padding: 0 15px 0 7px;
|
||||
margin: 5px 0 10px 5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.commit-row-info {
|
||||
|
||||
@@ -0,0 +1,329 @@
|
||||
.diff-file {
|
||||
border: 1px solid #CCC;
|
||||
margin-bottom: 1em;
|
||||
|
||||
.diff-header {
|
||||
@extend .clearfix;
|
||||
background: #DDD;
|
||||
border-bottom: 1px solid #CCC;
|
||||
padding: 5px 5px 5px 10px;
|
||||
color: #555;
|
||||
|
||||
> span {
|
||||
font-family: $monospace_font;
|
||||
font-size: 14px;
|
||||
line-height: 2;
|
||||
}
|
||||
|
||||
.diff-btn-group {
|
||||
float: right;
|
||||
|
||||
.btn {
|
||||
background-color: #EEE;
|
||||
color: #666;
|
||||
font-weight: bolder;
|
||||
}
|
||||
}
|
||||
|
||||
.commit-short-id {
|
||||
font-family: $monospace_font;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.file-mode {
|
||||
font-family: $monospace_font;
|
||||
}
|
||||
}
|
||||
.diff-content {
|
||||
overflow: auto;
|
||||
overflow-y: hidden;
|
||||
background: #FFF;
|
||||
color: #333;
|
||||
font-size: 12px;
|
||||
.old {
|
||||
span.idiff {
|
||||
background-color: #FAA;
|
||||
}
|
||||
}
|
||||
.new {
|
||||
span.idiff {
|
||||
background-color: #AFA;
|
||||
}
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
font-family: $monospace_font;
|
||||
border: none;
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
td {
|
||||
line-height: 18px;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
.old_line, .new_line, .diff_line {
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
border: none;
|
||||
background: #EEE;
|
||||
color: #666;
|
||||
padding: 0px 5px;
|
||||
border-right: 1px solid #ccc;
|
||||
text-align: right;
|
||||
min-width: 35px;
|
||||
max-width: 50px;
|
||||
width: 35px;
|
||||
@include user-select(none);
|
||||
a {
|
||||
float: left;
|
||||
width: 35px;
|
||||
font-weight: normal;
|
||||
color: #666;
|
||||
&:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
&.new {
|
||||
background: #CFD;
|
||||
}
|
||||
&.old {
|
||||
background: #FDD;
|
||||
}
|
||||
}
|
||||
.diff_line {
|
||||
padding: 0;
|
||||
}
|
||||
.line_holder {
|
||||
&.old .old_line,
|
||||
&.old .new_line {
|
||||
background: #FCC;
|
||||
border-color: #E7BABA;
|
||||
}
|
||||
&.new .old_line,
|
||||
&.new .new_line {
|
||||
background: #CFC;
|
||||
border-color: #B9ECB9;
|
||||
}
|
||||
}
|
||||
.line_content {
|
||||
display: block;
|
||||
white-space: pre;
|
||||
height: 18px;
|
||||
margin: 0px;
|
||||
padding: 0px 0.5em;
|
||||
border: none;
|
||||
&.new {
|
||||
background: #CFD;
|
||||
}
|
||||
&.old {
|
||||
background: #FDD;
|
||||
}
|
||||
&.matched {
|
||||
color: #ccc;
|
||||
background: #fafafa;
|
||||
}
|
||||
&.parallel {
|
||||
display: table-cell;
|
||||
overflow: hidden;
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
}
|
||||
.image {
|
||||
background: #ddd;
|
||||
text-align: center;
|
||||
padding: 30px;
|
||||
.wrap{
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.frame {
|
||||
display: inline-block;
|
||||
background-color: #fff;
|
||||
line-height: 0;
|
||||
img{
|
||||
border: 1px solid #FFF;
|
||||
background: image-url('trans_bg.gif');
|
||||
max-width: 100%;
|
||||
}
|
||||
&.deleted {
|
||||
border: 1px solid $deleted;
|
||||
}
|
||||
|
||||
&.added {
|
||||
border: 1px solid $added;
|
||||
}
|
||||
}
|
||||
.image-info{
|
||||
font-size: 12px;
|
||||
margin: 5px 0 0 0;
|
||||
color: grey;
|
||||
}
|
||||
|
||||
.view.swipe{
|
||||
position: relative;
|
||||
|
||||
.swipe-frame{
|
||||
display: block;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
}
|
||||
.swipe-wrap{
|
||||
overflow: hidden;
|
||||
border-left: 1px solid #999;
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 13px;
|
||||
right: 7px;
|
||||
}
|
||||
.frame{
|
||||
top: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
&.deleted{
|
||||
margin: 0;
|
||||
display: block;
|
||||
top: 13px;
|
||||
right: 7px;
|
||||
}
|
||||
}
|
||||
.swipe-bar{
|
||||
display: block;
|
||||
height: 100%;
|
||||
width: 15px;
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
cursor: pointer;
|
||||
&:hover{
|
||||
.top-handle{
|
||||
background-position: -15px 3px;
|
||||
}
|
||||
.bottom-handle{
|
||||
background-position: -15px -11px;
|
||||
}
|
||||
};
|
||||
.top-handle{
|
||||
display: block;
|
||||
height: 14px;
|
||||
width: 15px;
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
background: image-url('swipemode_sprites.gif') 0 3px no-repeat;
|
||||
}
|
||||
.bottom-handle{
|
||||
display: block;
|
||||
height: 14px;
|
||||
width: 15px;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
background: image-url('swipemode_sprites.gif') 0 -11px no-repeat;
|
||||
}
|
||||
}
|
||||
} //.view.swipe
|
||||
.view.onion-skin{
|
||||
.onion-skin-frame{
|
||||
display: block;
|
||||
margin: auto;
|
||||
position: relative;
|
||||
}
|
||||
.frame.added, .frame.deleted {
|
||||
position: absolute;
|
||||
display: block;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
.controls{
|
||||
display: block;
|
||||
height: 14px;
|
||||
width: 300px;
|
||||
z-index: 100;
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 50%;
|
||||
margin-left: -150px;
|
||||
|
||||
.drag-track{
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 12px;
|
||||
height: 10px;
|
||||
width: 276px;
|
||||
background: image-url('onion_skin_sprites.gif') -4px -20px repeat-x;
|
||||
}
|
||||
|
||||
.dragger {
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
background: image-url('onion_skin_sprites.gif') 0px -34px repeat-x;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.transparent {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 0px;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
background: image-url('onion_skin_sprites.gif') -2px 0px no-repeat;
|
||||
}
|
||||
|
||||
.opaque {
|
||||
display: block;
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 0px;
|
||||
height: 10px;
|
||||
width: 10px;
|
||||
background: image-url('onion_skin_sprites.gif') -2px -10px no-repeat;
|
||||
}
|
||||
}
|
||||
} //.view.onion-skin
|
||||
}
|
||||
.view-modes{
|
||||
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
|
||||
background-image: -webkit-gradient(linear, 0 0, 0 30, color-stop(0.066, #eee), to(#dfdfdf));
|
||||
background-image: -webkit-linear-gradient(#eee 6.6%, #dfdfdf);
|
||||
background-image: -moz-linear-gradient(#eee 6.6%, #dfdfdf);
|
||||
background-image: -ms-linear-gradient(#eee 6.6%, #dfdfdf);
|
||||
background-image: -o-linear-gradient(#eee 6.6%, #dfdfdf);
|
||||
|
||||
ul, li{
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
li{
|
||||
color: grey;
|
||||
border-left: 1px solid #c1c1c1;
|
||||
padding: 0 12px 0 16px;
|
||||
cursor: pointer;
|
||||
&:first-child{
|
||||
border-left: none;
|
||||
}
|
||||
&:hover{
|
||||
text-decoration: underline;
|
||||
}
|
||||
&.active{
|
||||
&:hover{
|
||||
text-decoration: none;
|
||||
}
|
||||
cursor: default;
|
||||
color: #333;
|
||||
}
|
||||
&.disabled{
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,7 +19,7 @@ header {
|
||||
line-height: 32px;
|
||||
padding: 6px 10px;
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus, &:active {
|
||||
background: none;
|
||||
}
|
||||
}
|
||||
@@ -161,7 +161,7 @@ header {
|
||||
}
|
||||
|
||||
.search-input {
|
||||
background-image: url("icon-search.png");
|
||||
background-image: image-url("icon-search.png");
|
||||
background-repeat: no-repeat;
|
||||
background-position: 10px;
|
||||
height: inherit;
|
||||
@@ -192,7 +192,8 @@ header {
|
||||
color: #AAA;
|
||||
text-shadow: 0 1px 0 #444;
|
||||
|
||||
&:hover {
|
||||
&:hover, &:focus, &:active {
|
||||
background: none;
|
||||
color: #FFF;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,3 +125,21 @@
|
||||
form.edit-issue {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.merge-request,
|
||||
.issue {
|
||||
&.today {
|
||||
background: #EFE;
|
||||
border-color: #CEC;
|
||||
}
|
||||
|
||||
&.closed {
|
||||
background: #F5f5f5;
|
||||
border-color: #E5E5E5;
|
||||
}
|
||||
|
||||
&.merged {
|
||||
background: #F5f5f5;
|
||||
border-color: #E5E5E5;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,3 +89,9 @@
|
||||
.merge-request-form-info {
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
// hide mr close link for inline diff comment form
|
||||
.diff-file .close-mr-link,
|
||||
.diff-file .reopen-mr-link {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -47,13 +47,13 @@ ul.notes {
|
||||
.discussion-body {
|
||||
margin-left: 50px;
|
||||
|
||||
.file,
|
||||
.diff-file,
|
||||
.discussion-hidden,
|
||||
.notes {
|
||||
@extend .borders;
|
||||
background-color: #F9F9F9;
|
||||
}
|
||||
.file .notes {
|
||||
.diff-file .notes {
|
||||
/* reset */
|
||||
background: inherit;
|
||||
border: none;
|
||||
@@ -87,7 +87,6 @@ ul.notes {
|
||||
}
|
||||
.attachment {
|
||||
font-size: 14px;
|
||||
margin-top: -20px;
|
||||
}
|
||||
.note-body {
|
||||
@include md-typography;
|
||||
@@ -114,7 +113,7 @@ ul.notes {
|
||||
}
|
||||
}
|
||||
|
||||
.file .notes_holder {
|
||||
.diff-file .notes_holder {
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
@@ -184,20 +183,19 @@ ul.notes {
|
||||
}
|
||||
}
|
||||
}
|
||||
.file .note .note-actions {
|
||||
.diff-file .note .note-actions {
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Line note button on the side of diffs
|
||||
*/
|
||||
|
||||
.file tr.line_holder {
|
||||
.diff-file tr.line_holder {
|
||||
.add-diff-note {
|
||||
background: url("diff_note_add.png") no-repeat left 0;
|
||||
background: image-url("diff_note_add.png") no-repeat left 0;
|
||||
height: 22px;
|
||||
margin-left: -65px;
|
||||
position: absolute;
|
||||
@@ -235,22 +233,25 @@ ul.notes {
|
||||
.reply-btn {
|
||||
@extend .btn-primary;
|
||||
}
|
||||
.file .content tr.line_holder:hover {
|
||||
&> td.line_content {
|
||||
background: $hover !important;
|
||||
border-color: darken($hover, 10%) !important;
|
||||
.diff-file .diff-content {
|
||||
tr.line_holder:hover {
|
||||
&> td.line_content {
|
||||
background: $hover !important;
|
||||
border-color: darken($hover, 10%) !important;
|
||||
}
|
||||
&> td.new_line,
|
||||
&> td.old_line {
|
||||
background: darken($hover, 4%) !important;
|
||||
border-color: darken($hover, 10%) !important;
|
||||
}
|
||||
}
|
||||
&> td.new_line,
|
||||
&> td.old_line {
|
||||
background: darken($hover, 4%) !important;
|
||||
border-color: darken($hover, 10%) !important;
|
||||
|
||||
tr.line_holder:hover > td .line_note_link {
|
||||
opacity: 1.0;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
}
|
||||
.file .content tr.line_holder:hover > td .line_note_link {
|
||||
opacity: 1.0;
|
||||
filter: alpha(opacity=100);
|
||||
}
|
||||
.file,
|
||||
.diff-file,
|
||||
.discussion {
|
||||
.new_note {
|
||||
margin: 0;
|
||||
@@ -302,6 +303,7 @@ ul.notes {
|
||||
@extend .col-md-4;
|
||||
@extend .thumbnail;
|
||||
margin-left: 45px;
|
||||
float: none;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
class Admin::BackgroundJobsController < Admin::ApplicationController
|
||||
def show
|
||||
@sidekiq_processes = `ps -U #{Settings.gitlab.user} -o euser,pid,pcpu,pmem,stat,start,command | grep sidekiq | grep -v grep`
|
||||
ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Settings.gitlab.user} -o pid,pcpu,pmem,stat,start,command))
|
||||
@sidekiq_processes = ps_output.split("\n").grep(/sidekiq/)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -68,7 +68,9 @@ class Admin::UsersController < Admin::ApplicationController
|
||||
params[:user].delete(:password_confirmation)
|
||||
end
|
||||
|
||||
user.admin = (admin && admin.to_i > 0)
|
||||
if admin.present?
|
||||
user.admin = !admin.to_i.zero?
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
if user.update_attributes(params[:user], as: :admin)
|
||||
|
||||
@@ -155,7 +155,6 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
def dev_tools
|
||||
Rack::MiniProfiler.authorize_request
|
||||
end
|
||||
|
||||
def default_headers
|
||||
@@ -182,7 +181,7 @@ class ApplicationController < ActionController::Base
|
||||
end
|
||||
|
||||
def ldap_security_check
|
||||
if current_user && current_user.ldap_user? && current_user.requires_ldap_check?
|
||||
if current_user && current_user.requires_ldap_check?
|
||||
gitlab_ldap_access do |access|
|
||||
if access.allowed?(current_user)
|
||||
access.update_permissions(current_user)
|
||||
@@ -232,4 +231,8 @@ class ApplicationController < ActionController::Base
|
||||
devise_parameter_sanitizer.for(:sign_in) { |u| u.permit(:username, :email, :password, :login, :remember_me) }
|
||||
devise_parameter_sanitizer.for(:sign_up) { |u| u.permit(:username, :email, :name, :password, :password_confirmation) }
|
||||
end
|
||||
|
||||
def hexdigest(string)
|
||||
Digest::SHA1.hexdigest string
|
||||
end
|
||||
end
|
||||
|
||||
@@ -22,6 +22,8 @@ class DashboardController < ApplicationController
|
||||
|
||||
@last_push = current_user.recent_push
|
||||
|
||||
@publicish_project_count = Project.publicish(current_user).count
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
format.json { pager_json("events/_events", @events.count) }
|
||||
@@ -53,14 +55,15 @@ class DashboardController < ApplicationController
|
||||
end
|
||||
|
||||
def merge_requests
|
||||
@merge_requests = FilteringService.new.execute(MergeRequest, current_user, params)
|
||||
@merge_requests = MergeRequestsFinder.new.execute(current_user, params)
|
||||
@merge_requests = @merge_requests.page(params[:page]).per(20)
|
||||
@merge_requests = @merge_requests.preload(:author, :target_project)
|
||||
end
|
||||
|
||||
def issues
|
||||
@issues = FilteringService.new.execute(Issue, current_user, params)
|
||||
@issues = IssuesFinder.new.execute(current_user, params)
|
||||
@issues = @issues.page(params[:page]).per(20)
|
||||
@issues = @issues.includes(:author, :project)
|
||||
@issues = @issues.preload(:author, :project)
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
@@ -77,5 +80,6 @@ class DashboardController < ApplicationController
|
||||
def default_filter
|
||||
params[:scope] = 'assigned-to-me' if params[:scope].blank?
|
||||
params[:state] = 'opened' if params[:state].blank?
|
||||
params[:authorized_only] = true
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
class GroupsController < ApplicationController
|
||||
skip_before_filter :authenticate_user!, only: [:show, :issues, :members, :merge_requests]
|
||||
respond_to :html
|
||||
before_filter :group, except: [:new, :create]
|
||||
|
||||
@@ -36,7 +37,7 @@ class GroupsController < ApplicationController
|
||||
@events = Event.in_projects(project_ids)
|
||||
@events = event_filter.apply_filter(@events)
|
||||
@events = @events.limit(20).offset(params[:offset] || 0)
|
||||
@last_push = current_user.recent_push
|
||||
@last_push = current_user.recent_push if current_user
|
||||
|
||||
@shared_projects = @group.shared_projects
|
||||
|
||||
@@ -48,14 +49,15 @@ class GroupsController < ApplicationController
|
||||
end
|
||||
|
||||
def merge_requests
|
||||
@merge_requests = FilteringService.new.execute(MergeRequest, current_user, params)
|
||||
@merge_requests = MergeRequestsFinder.new.execute(current_user, params)
|
||||
@merge_requests = @merge_requests.page(params[:page]).per(20)
|
||||
@merge_requests = @merge_requests.preload(:author, :target_project)
|
||||
end
|
||||
|
||||
def issues
|
||||
@issues = FilteringService.new.execute(Issue, current_user, params)
|
||||
@issues = IssuesFinder.new.execute(current_user, params)
|
||||
@issues = @issues.page(params[:page]).per(20)
|
||||
@issues = @issues.includes(:author, :project)
|
||||
@issues = @issues.preload(:author, :project)
|
||||
|
||||
respond_to do |format|
|
||||
format.html
|
||||
@@ -100,17 +102,21 @@ class GroupsController < ApplicationController
|
||||
end
|
||||
|
||||
def projects
|
||||
@projects ||= current_user.authorized_projects.where(namespace_id: group.id).sorted_by_activity
|
||||
@projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity.non_archived
|
||||
end
|
||||
|
||||
def project_ids
|
||||
projects.map(&:id)
|
||||
projects.pluck(:id)
|
||||
end
|
||||
|
||||
# Dont allow unauthorized access to group
|
||||
def authorize_read_group!
|
||||
unless @group and (projects.present? or can?(current_user, :read_group, @group))
|
||||
return render_404
|
||||
if current_user.nil?
|
||||
return authenticate_user!
|
||||
else
|
||||
return render_404
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -133,13 +139,21 @@ class GroupsController < ApplicationController
|
||||
def determine_layout
|
||||
if [:new, :create].include?(action_name.to_sym)
|
||||
'navless'
|
||||
else
|
||||
elsif current_user
|
||||
'group'
|
||||
else
|
||||
'public_group'
|
||||
end
|
||||
end
|
||||
|
||||
def default_filter
|
||||
params[:scope] = 'assigned-to-me' if params[:scope].blank?
|
||||
if params[:scope].blank?
|
||||
if current_user
|
||||
params[:scope] = 'assigned-to-me'
|
||||
else
|
||||
params[:scope] = 'all'
|
||||
end
|
||||
end
|
||||
params[:state] = 'opened' if params[:state].blank?
|
||||
params[:group_id] = @group.id
|
||||
end
|
||||
|
||||
@@ -2,6 +2,8 @@ class Projects::EditTreeController < Projects::BaseTreeController
|
||||
before_filter :require_branch_head
|
||||
before_filter :blob
|
||||
before_filter :authorize_push!
|
||||
before_filter :from_merge_request
|
||||
before_filter :after_edit_path
|
||||
|
||||
def show
|
||||
@last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha
|
||||
@@ -12,7 +14,12 @@ class Projects::EditTreeController < Projects::BaseTreeController
|
||||
|
||||
if result[:status] == :success
|
||||
flash[:notice] = "Your changes have been successfully committed"
|
||||
redirect_to project_blob_path(@project, @id)
|
||||
|
||||
if from_merge_request
|
||||
from_merge_request.reload_code
|
||||
end
|
||||
|
||||
redirect_to after_edit_path
|
||||
else
|
||||
flash[:alert] = result[:error]
|
||||
render :show
|
||||
@@ -24,4 +31,19 @@ class Projects::EditTreeController < Projects::BaseTreeController
|
||||
def blob
|
||||
@blob ||= @repository.blob_at(@commit.id, @path)
|
||||
end
|
||||
|
||||
def after_edit_path
|
||||
@after_edit_path ||=
|
||||
if from_merge_request
|
||||
diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) +
|
||||
"#file-path-#{hexdigest(@path)}"
|
||||
else
|
||||
project_blob_path(@project, @id)
|
||||
end
|
||||
end
|
||||
|
||||
def from_merge_request
|
||||
# If blob edit was initiated from merge request page
|
||||
@from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -121,7 +121,7 @@ class Projects::IssuesController < Projects::ApplicationController
|
||||
def issues_filtered
|
||||
params[:scope] = 'all' if params[:scope].blank?
|
||||
params[:state] = 'opened' if params[:state].blank?
|
||||
@issues = FilteringService.new.execute(Issue, current_user, params.merge(project_id: @project.id))
|
||||
@issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id))
|
||||
end
|
||||
|
||||
# Since iids are implemented only in 6.1
|
||||
|
||||
@@ -13,7 +13,7 @@ class Projects::LabelsController < Projects::ApplicationController
|
||||
def generate
|
||||
Gitlab::IssuesLabels.generate(@project)
|
||||
|
||||
redirect_to project_labels_path(@project)
|
||||
redirect_to project_issues_path(@project)
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
@@ -21,7 +21,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||
params[:scope] = 'all' if params[:scope].blank?
|
||||
params[:state] = 'opened' if params[:state].blank?
|
||||
|
||||
@merge_requests = FilteringService.new.execute(MergeRequest, current_user, params.merge(project_id: @project.id))
|
||||
@merge_requests = MergeRequestsFinder.new.execute(current_user, params.merge(project_id: @project.id))
|
||||
@merge_requests = @merge_requests.page(params[:page]).per(20)
|
||||
|
||||
@sort = params[:sort].humanize
|
||||
@@ -60,7 +60,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||
def new
|
||||
@merge_request = MergeRequest.new(params[:merge_request])
|
||||
@merge_request.source_project = @project unless @merge_request.source_project
|
||||
@merge_request.target_project = @project unless @merge_request.target_project
|
||||
@merge_request.target_project ||= (@project.forked_from_project || @project)
|
||||
@target_branches = @merge_request.target_project.nil? ? [] : @merge_request.target_project.repository.branch_names
|
||||
|
||||
@merge_request.target_branch ||= @merge_request.target_project.default_branch
|
||||
|
||||
@source_project = @merge_request.source_project
|
||||
@merge_request
|
||||
end
|
||||
@@ -131,7 +135,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||
def automerge
|
||||
return access_denied! unless allowed_to_merge?
|
||||
|
||||
if @merge_request.opened? && @merge_request.can_be_merged?
|
||||
if @merge_request.open? && @merge_request.can_be_merged?
|
||||
@merge_request.should_remove_source_branch = params[:should_remove_source_branch]
|
||||
@merge_request.automerge!(current_user, params[:merge_commit_message])
|
||||
@status = true
|
||||
@@ -162,7 +166,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||
end
|
||||
|
||||
def ci_status
|
||||
status = project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
|
||||
status = @merge_request.source_project.gitlab_ci_service.commit_status(merge_request.last_commit.sha)
|
||||
response = {status: status}
|
||||
|
||||
render json: response
|
||||
@@ -226,7 +230,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
|
||||
|
||||
@merge_request_diff = @merge_request.merge_request_diff
|
||||
@allowed_to_merge = allowed_to_merge?
|
||||
@show_merge_controls = @merge_request.opened? && @commits.any? && @allowed_to_merge
|
||||
@show_merge_controls = @merge_request.open? && @commits.any? && @allowed_to_merge
|
||||
end
|
||||
|
||||
def allowed_to_merge?
|
||||
|
||||
@@ -5,7 +5,7 @@ class Projects::NotesController < Projects::ApplicationController
|
||||
before_filter :authorize_admin_note!, only: [:update, :destroy]
|
||||
|
||||
def index
|
||||
@notes = Notes::LoadService.new(project, current_user, params).execute
|
||||
@notes = NotesFinder.new.execute(project, current_user, params)
|
||||
|
||||
notes_json = { notes: [] }
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ class ProjectsController < ApplicationController
|
||||
|
||||
# Authorize
|
||||
before_filter :authorize_read_project!, except: [:index, :new, :create]
|
||||
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
|
||||
before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import]
|
||||
before_filter :require_non_empty_project, only: [:blob, :tree, :graph]
|
||||
|
||||
layout 'navless', only: [:new, :create, :fork]
|
||||
@@ -21,16 +21,9 @@ class ProjectsController < ApplicationController
|
||||
|
||||
def create
|
||||
@project = ::Projects::CreateService.new(current_user, params[:project]).execute
|
||||
flash[:notice] = 'Project was successfully created.' if @project.saved?
|
||||
|
||||
respond_to do |format|
|
||||
flash[:notice] = 'Project was successfully created.' if @project.saved?
|
||||
format.html do
|
||||
if @project.saved?
|
||||
redirect_to @project
|
||||
else
|
||||
render "new"
|
||||
end
|
||||
end
|
||||
format.js
|
||||
end
|
||||
end
|
||||
@@ -55,6 +48,11 @@ class ProjectsController < ApplicationController
|
||||
end
|
||||
|
||||
def show
|
||||
if @project.import_in_progress?
|
||||
redirect_to import_project_path(@project)
|
||||
return
|
||||
end
|
||||
|
||||
return authenticate_user! unless @project.public? || current_user
|
||||
|
||||
limit = (params[:limit] || 20).to_i
|
||||
@@ -67,9 +65,7 @@ class ProjectsController < ApplicationController
|
||||
if @project.empty_repo?
|
||||
render "projects/empty", layout: user_layout
|
||||
else
|
||||
if current_user
|
||||
@last_push = current_user.recent_push(@project.id)
|
||||
end
|
||||
@last_push = current_user.recent_push(@project.id) if current_user
|
||||
render :show, layout: user_layout
|
||||
end
|
||||
end
|
||||
@@ -77,6 +73,28 @@ class ProjectsController < ApplicationController
|
||||
end
|
||||
end
|
||||
|
||||
def import
|
||||
if project.import_finished?
|
||||
redirect_to @project
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
def retry_import
|
||||
unless @project.import_failed?
|
||||
redirect_to import_project_path(@project)
|
||||
end
|
||||
|
||||
@project.import_url = params[:project][:import_url]
|
||||
|
||||
if @project.save
|
||||
@project.reload
|
||||
@project.import_retry
|
||||
end
|
||||
|
||||
redirect_to import_project_path(@project)
|
||||
end
|
||||
|
||||
def destroy
|
||||
return access_denied! unless can?(current_user, :remove_project, project)
|
||||
|
||||
@@ -108,6 +126,7 @@ class ProjectsController < ApplicationController
|
||||
@suggestions = {
|
||||
emojis: Emoji.names,
|
||||
issues: @project.issues.select([:iid, :title, :description]),
|
||||
mergerequests: @project.merge_requests.select([:iid, :title, :description]),
|
||||
members: @project.team.members.sort_by(&:username).map { |user| { username: user.username, name: user.name } }
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ class Public::ProjectsController < ApplicationController
|
||||
layout 'public'
|
||||
|
||||
def index
|
||||
@projects = Project.public_or_internal_only(current_user)
|
||||
@projects = Project.publicish(current_user)
|
||||
@projects = @projects.search(params[:search]) if params[:search].present?
|
||||
@projects = @projects.sort(@sort = params[:sort])
|
||||
@projects = @projects.includes(:namespace).page(params[:page]).per(20)
|
||||
|
||||
@@ -7,6 +7,7 @@ class SearchController < ApplicationController
|
||||
|
||||
if @project
|
||||
return access_denied! unless can?(current_user, :download_code, @project)
|
||||
|
||||
@search_results = Search::ProjectService.new(@project, current_user, params).execute
|
||||
else
|
||||
@search_results = Search::GlobalService.new(current_user, params).execute
|
||||
|
||||
@@ -19,6 +19,9 @@ class SnippetsController < ApplicationController
|
||||
|
||||
def user_index
|
||||
@user = User.find_by(username: params[:username])
|
||||
|
||||
render_404 and return unless @user
|
||||
|
||||
@snippets = @user.snippets.fresh.non_expired
|
||||
|
||||
if @user == current_user
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
class UsersController < ApplicationController
|
||||
|
||||
skip_before_filter :authenticate_user!, only: [:show]
|
||||
layout :determine_layout
|
||||
|
||||
def show
|
||||
@user = User.find_by_username!(params[:username])
|
||||
@projects = @user.authorized_projects.includes(:namespace).select {|project| can?(current_user, :read_project, project)}
|
||||
@projects = @user.authorized_projects.accessible_to(current_user)
|
||||
if !current_user && @projects.empty?
|
||||
return authenticate_user!
|
||||
end
|
||||
@events = @user.recent_events.where(project_id: @projects.map(&:id)).limit(20)
|
||||
@groups = @user.groups.accessible_to(current_user)
|
||||
@events = @user.recent_events.where(project_id: @projects.pluck(:id)).limit(20)
|
||||
@title = @user.name
|
||||
end
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Finders
|
||||
|
||||
This type of classes responsible for collectiong items based on different conditions.
|
||||
To prevent lookup methods in models like this:
|
||||
|
||||
```ruby
|
||||
class Project
|
||||
def issues_for_user_filtered_by(user, filter)
|
||||
# A lot of logic not related to project model itself
|
||||
end
|
||||
end
|
||||
|
||||
issues = project.issues_for_user_filtered_by(user, params)
|
||||
```
|
||||
|
||||
Better use this:
|
||||
|
||||
```ruby
|
||||
issues = IssuesFinder.new.execute(project, user, filter)
|
||||
```
|
||||
|
||||
It will help keep models thiner
|
||||
@@ -1,4 +1,4 @@
|
||||
# FilteringService class
|
||||
# BaseFinder
|
||||
#
|
||||
# Used to filter Issues and MergeRequests collections by set of params
|
||||
#
|
||||
@@ -16,11 +16,10 @@
|
||||
# label_name: string
|
||||
# sort: string
|
||||
#
|
||||
class FilteringService
|
||||
attr_accessor :klass, :current_user, :params
|
||||
class BaseFinder
|
||||
attr_accessor :current_user, :params
|
||||
|
||||
def execute(klass, current_user, params)
|
||||
@klass = klass
|
||||
def execute(current_user, params)
|
||||
@current_user = current_user
|
||||
@params = params
|
||||
|
||||
@@ -41,16 +40,16 @@ class FilteringService
|
||||
def init_collection
|
||||
table_name = klass.table_name
|
||||
|
||||
return klass.of_projects(Project.public_only) unless current_user
|
||||
|
||||
if project
|
||||
if current_user.can?(:read_project, project)
|
||||
if project.public? || (current_user && current_user.can?(:read_project, project))
|
||||
project.send(table_name)
|
||||
else
|
||||
[]
|
||||
end
|
||||
else
|
||||
elsif current_user && params[:authorized_only].presence
|
||||
klass.of_projects(current_user.authorized_projects)
|
||||
else
|
||||
klass.of_projects(Project.accessible_to(current_user))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# Finders::Issues class
|
||||
#
|
||||
# Used to filter Issues collections by set of params
|
||||
#
|
||||
# Arguments:
|
||||
# current_user - which user use
|
||||
# params:
|
||||
# scope: 'created-by-me' or 'assigned-to-me' or 'all'
|
||||
# state: 'open' or 'closed' or 'all'
|
||||
# group_id: integer
|
||||
# project_id: integer
|
||||
# milestone_id: integer
|
||||
# assignee_id: integer
|
||||
# search: string
|
||||
# label_name: string
|
||||
# sort: string
|
||||
#
|
||||
class IssuesFinder < BaseFinder
|
||||
def klass
|
||||
Issue
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,22 @@
|
||||
# Finders::MergeRequest class
|
||||
#
|
||||
# Used to filter MergeRequests collections by set of params
|
||||
#
|
||||
# Arguments:
|
||||
# current_user - which user use
|
||||
# params:
|
||||
# scope: 'created-by-me' or 'assigned-to-me' or 'all'
|
||||
# state: 'open' or 'closed' or 'all'
|
||||
# group_id: integer
|
||||
# project_id: integer
|
||||
# milestone_id: integer
|
||||
# assignee_id: integer
|
||||
# search: string
|
||||
# label_name: string
|
||||
# sort: string
|
||||
#
|
||||
class MergeRequestsFinder < BaseFinder
|
||||
def klass
|
||||
MergeRequest
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,17 @@
|
||||
class NotesFinder
|
||||
def execute(project, current_user, params)
|
||||
target_type = params[:target_type]
|
||||
target_id = params[:target_id]
|
||||
|
||||
case target_type
|
||||
when "commit"
|
||||
project.notes.for_commit_id(target_id).not_inline.fresh
|
||||
when "issue"
|
||||
project.issues.find(target_id).notes.inc_author.fresh
|
||||
when "merge_request"
|
||||
project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh
|
||||
when "snippet"
|
||||
project.snippets.find(target_id).notes.fresh
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,63 @@
|
||||
class ProjectsFinder
|
||||
def execute(current_user, options)
|
||||
group = options[:group]
|
||||
|
||||
if group
|
||||
group_projects(current_user, group)
|
||||
else
|
||||
all_projects(current_user)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def group_projects(current_user, group)
|
||||
if current_user
|
||||
if group.users.include?(current_user)
|
||||
# User is group member
|
||||
#
|
||||
# Return ALL group projects
|
||||
group.projects
|
||||
else
|
||||
projects_members = UsersProject.where(
|
||||
project_id: group.projects,
|
||||
user_id: current_user
|
||||
)
|
||||
|
||||
if projects_members.any?
|
||||
# User is a project member
|
||||
#
|
||||
# Return only:
|
||||
# public projects
|
||||
# internal projects
|
||||
# joined projects
|
||||
#
|
||||
group.projects.where(
|
||||
"projects.id IN (?) OR projects.visibility_level IN (?)",
|
||||
projects_members.pluck(:project_id),
|
||||
Project.public_and_internal_levels
|
||||
)
|
||||
else
|
||||
# User has no access to group or group projects
|
||||
#
|
||||
# Return only:
|
||||
# public projects
|
||||
# internal projects
|
||||
#
|
||||
group.projects.public_and_internal_only
|
||||
end
|
||||
end
|
||||
else
|
||||
# Not authenticated
|
||||
#
|
||||
# Return only:
|
||||
# public projects
|
||||
group.projects.public_only
|
||||
end
|
||||
end
|
||||
|
||||
def all_projects
|
||||
# TODO: implement
|
||||
raise 'Not implemented yet'
|
||||
end
|
||||
end
|
||||
@@ -54,7 +54,7 @@ module ApplicationHelper
|
||||
if group && group.avatar.present?
|
||||
group.avatar.url
|
||||
else
|
||||
'/assets/no_group_avatar.png'
|
||||
image_path('no_group_avatar.png')
|
||||
end
|
||||
end
|
||||
|
||||
@@ -89,16 +89,15 @@ module ApplicationHelper
|
||||
"Never"
|
||||
end
|
||||
|
||||
def grouped_options_refs(destination = :tree)
|
||||
def grouped_options_refs
|
||||
repository = @project.repository
|
||||
|
||||
options = [
|
||||
["Branches", repository.branch_names],
|
||||
["Tags", repository.tag_names]
|
||||
["Tags", VersionSorter.rsort(repository.tag_names)]
|
||||
]
|
||||
|
||||
# If reference is commit id -
|
||||
# we should add it to branch/tag selectbox
|
||||
# If reference is commit id - we should add it to branch/tag selectbox
|
||||
if(@ref && !options.flatten.include?(@ref) &&
|
||||
@ref =~ /^[0-9a-zA-Z]{6,52}$/)
|
||||
options << ["Commit", [@ref]]
|
||||
@@ -147,8 +146,7 @@ module ApplicationHelper
|
||||
|
||||
def authbutton(provider, size = 64)
|
||||
file_name = "#{provider.to_s.split('_').first}_#{size}.png"
|
||||
image_tag("authbuttons/#{file_name}",
|
||||
alt: "Sign in with #{provider.to_s.titleize}")
|
||||
image_tag(image_path("authbuttons/#{file_name}"), alt: "Sign in with #{provider.to_s.titleize}")
|
||||
end
|
||||
|
||||
def simple_sanitize(str)
|
||||
|
||||
@@ -35,7 +35,6 @@ module GitlabMarkdownHelper
|
||||
# see https://github.com/vmg/redcarpet#darling-i-packed-you-a-couple-renderers-for-lunch-
|
||||
filter_html: true,
|
||||
with_toc_data: true,
|
||||
hard_wrap: true,
|
||||
safe_links_only: true
|
||||
}.merge(options))
|
||||
@markdown = Redcarpet::Markdown.new(gitlab_renderer,
|
||||
@@ -45,7 +44,7 @@ module GitlabMarkdownHelper
|
||||
fenced_code_blocks: true,
|
||||
autolink: true,
|
||||
strikethrough: true,
|
||||
lax_html_blocks: true,
|
||||
lax_spacing: true,
|
||||
space_after_headers: true,
|
||||
superscript: true)
|
||||
end
|
||||
@@ -64,8 +63,7 @@ module GitlabMarkdownHelper
|
||||
# project_path_with_namespace - namespace/projectname, eg. gitlabhq/gitlabhq
|
||||
# ref - name of the branch or reference, eg. stable
|
||||
# requested_path - path of request, eg. doc/api/README.md, used in special case when path is pointing to the .md file were the original request is coming from
|
||||
# wiki - whether the markdown is from wiki or not
|
||||
def create_relative_links(text, project, ref, requested_path, wiki = false)
|
||||
def create_relative_links(text, project, ref, requested_path)
|
||||
@path_to_satellite = project.satellite.path
|
||||
project_path_with_namespace = project.path_with_namespace
|
||||
paths = extract_paths(text)
|
||||
@@ -124,21 +122,23 @@ module GitlabMarkdownHelper
|
||||
end
|
||||
|
||||
def rebuild_path(path_with_namespace, path, requested_path, ref)
|
||||
path.gsub!(/(#.*)/, "")
|
||||
id = $1 || ""
|
||||
file_path = relative_file_path(path, requested_path)
|
||||
[
|
||||
path_with_namespace,
|
||||
path_with_ref(file_path, ref),
|
||||
file_path
|
||||
].compact.join("/")
|
||||
].compact.join("/").gsub(/\/*$/, '') + id
|
||||
end
|
||||
|
||||
# Checks if the path exists in the repo
|
||||
# eg. checks if doc/README.md exists, if it doesn't then it is a wiki link
|
||||
# eg. checks if doc/README.md exists, if not then link to blob
|
||||
def path_with_ref(path, ref)
|
||||
if file_exists?(path)
|
||||
"#{local_path(path)}/#{correct_ref(ref)}"
|
||||
else
|
||||
"wikis"
|
||||
"blob/#{correct_ref(ref)}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -154,6 +154,7 @@ module GitlabMarkdownHelper
|
||||
# If we are at doc/api and the README.md shown in below the tree view
|
||||
# this takes the rquest path(doc/api) and adds users.md so the path looks like doc/api/users.md
|
||||
def build_nested_path(path, request_path)
|
||||
return request_path if path == ""
|
||||
return path unless request_path
|
||||
if local_path(request_path) == "tree"
|
||||
base = request_path.split("/").push(path)
|
||||
@@ -166,7 +167,7 @@ module GitlabMarkdownHelper
|
||||
end
|
||||
|
||||
def file_exists?(path)
|
||||
return false if path.nil? || path.empty?
|
||||
return false if path.nil?
|
||||
return @repository.blob_at(current_sha, path).present? || @repository.tree(current_sha, path).entries.any?
|
||||
end
|
||||
|
||||
|
||||
@@ -6,6 +6,14 @@ module GroupsHelper
|
||||
def leave_group_message(group)
|
||||
"Are you sure you want to leave \"#{group}\" group?"
|
||||
end
|
||||
|
||||
def should_user_see_group_roles?(user, group)
|
||||
if user
|
||||
user.is_admin? || group.members.exists?(user_id: user.id)
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def group_head_title
|
||||
title = @group.name
|
||||
|
||||
@@ -85,11 +85,15 @@ module IssuesHelper
|
||||
options_from_collection_for_select(@project.milestones.active, 'id', 'title', object.milestone_id)
|
||||
end
|
||||
|
||||
def issue_alert_class(issue)
|
||||
if issue.closed?
|
||||
'alert-danger'
|
||||
def issue_box_class(item)
|
||||
if item.respond_to?(:expired?) && item.expired?
|
||||
'issue-box-expired'
|
||||
elsif item.respond_to?(:merged?) && item.merged?
|
||||
'issue-box-merged'
|
||||
elsif item.closed?
|
||||
'issue-box-closed'
|
||||
else
|
||||
'alert-success'
|
||||
'issue-box-open'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
module MergeRequestsHelper
|
||||
def new_mr_path_from_push_event(event)
|
||||
target_project = event.project.forked_from_project || event.project
|
||||
new_project_merge_request_path(
|
||||
event.project,
|
||||
new_mr_from_push_event(event, event.project)
|
||||
new_mr_from_push_event(event, target_project)
|
||||
)
|
||||
end
|
||||
|
||||
@@ -41,14 +42,4 @@ module MergeRequestsHelper
|
||||
"Branches: #{@merge_request.source_branch} #{separator} #{@merge_request.target_branch}"
|
||||
end
|
||||
end
|
||||
|
||||
def merge_request_alert_class(merge_request)
|
||||
if merge_request.merged?
|
||||
'alert-info'
|
||||
elsif merge_request.closed?
|
||||
'alert-danger'
|
||||
else
|
||||
'alert-success'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -10,7 +10,7 @@ module ProfileHelper
|
||||
end
|
||||
|
||||
def show_profile_social_tab?
|
||||
Gitlab.config.omniauth.enabled && !current_user.ldap_user?
|
||||
enabled_social_providers.any? && !current_user.ldap_user?
|
||||
end
|
||||
|
||||
def show_profile_remove_tab?
|
||||
|
||||
@@ -4,8 +4,7 @@ module SearchHelper
|
||||
|
||||
resources_results = [
|
||||
groups_autocomplete(term),
|
||||
projects_autocomplete(term),
|
||||
public_projects_autocomplete(term),
|
||||
projects_autocomplete(term)
|
||||
].flatten
|
||||
|
||||
generic_results = project_autocomplete + default_autocomplete + help_autocomplete
|
||||
@@ -82,17 +81,7 @@ module SearchHelper
|
||||
|
||||
# Autocomplete results for the current user's projects
|
||||
def projects_autocomplete(term, limit = 5)
|
||||
current_user.authorized_projects.search_by_title(term).non_archived.limit(limit).map do |p|
|
||||
{
|
||||
label: "project: #{search_result_sanitize(p.name_with_namespace)}",
|
||||
url: project_path(p)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
# Autocomplete results for the current user's projects
|
||||
def public_projects_autocomplete(term, limit = 5)
|
||||
Project.public_or_internal_only(current_user).search_by_title(term).non_archived.limit(limit).map do |p|
|
||||
Project.accessible_to(current_user).search_by_title(term).non_archived.limit(limit).map do |p|
|
||||
{
|
||||
label: "project: #{search_result_sanitize(p.name_with_namespace)}",
|
||||
url: project_path(p)
|
||||
|
||||
@@ -3,7 +3,7 @@ module Emails
|
||||
def group_access_granted_email(user_group_id)
|
||||
@membership = UsersGroup.find(user_group_id)
|
||||
@group = @membership.group
|
||||
|
||||
@target_url = group_url(@group)
|
||||
mail(to: @membership.user.email,
|
||||
subject: subject("Access to group was granted"))
|
||||
end
|
||||
|
||||
@@ -3,22 +3,30 @@ module Emails
|
||||
def new_issue_email(recipient_id, issue_id)
|
||||
@issue = Issue.find(issue_id)
|
||||
@project = @issue.project
|
||||
mail(to: recipient(recipient_id), subject: subject("New issue ##{@issue.iid}", @issue.title))
|
||||
@target_url = project_issue_url(@project, @issue)
|
||||
mail(from: sender(@issue.author_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@issue.title} (##{@issue.iid})"))
|
||||
end
|
||||
|
||||
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id)
|
||||
def reassigned_issue_email(recipient_id, issue_id, previous_assignee_id, updated_by_user_id)
|
||||
@issue = Issue.find(issue_id)
|
||||
@previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
|
||||
@project = @issue.project
|
||||
mail(to: recipient(recipient_id), subject: subject("Changed issue ##{@issue.iid}", @issue.title))
|
||||
@target_url = project_issue_url(@project, @issue)
|
||||
mail(from: sender(updated_by_user_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@issue.title} (##{@issue.iid})"))
|
||||
end
|
||||
|
||||
def closed_issue_email(recipient_id, issue_id, updated_by_user_id)
|
||||
@issue = Issue.find issue_id
|
||||
@project = @issue.project
|
||||
@updated_by = User.find updated_by_user_id
|
||||
mail(to: recipient(recipient_id),
|
||||
subject: subject("Closed issue ##{@issue.iid}", @issue.title))
|
||||
@target_url = project_issue_url(@project, @issue)
|
||||
mail(from: sender(updated_by_user_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@issue.title} (##{@issue.iid})"))
|
||||
end
|
||||
|
||||
def issue_status_changed_email(recipient_id, issue_id, status, updated_by_user_id)
|
||||
@@ -26,8 +34,10 @@ module Emails
|
||||
@issue_status = status
|
||||
@project = @issue.project
|
||||
@updated_by = User.find updated_by_user_id
|
||||
mail(to: recipient(recipient_id),
|
||||
subject: subject("Changed issue ##{@issue.iid}", @issue.title))
|
||||
@target_url = project_issue_url(@project, @issue)
|
||||
mail(from: sender(updated_by_user_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@issue.title} (##{@issue.iid})"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,27 +3,39 @@ module Emails
|
||||
def new_merge_request_email(recipient_id, merge_request_id)
|
||||
@merge_request = MergeRequest.find(merge_request_id)
|
||||
@project = @merge_request.project
|
||||
mail(to: recipient(recipient_id), subject: subject("New merge request ##{@merge_request.iid}", @merge_request.title))
|
||||
@target_url = project_merge_request_url(@project, @merge_request)
|
||||
mail(from: sender(@merge_request.author_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
|
||||
end
|
||||
|
||||
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id)
|
||||
def reassigned_merge_request_email(recipient_id, merge_request_id, previous_assignee_id, updated_by_user_id)
|
||||
@merge_request = MergeRequest.find(merge_request_id)
|
||||
@previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id
|
||||
@project = @merge_request.project
|
||||
mail(to: recipient(recipient_id), subject: subject("Changed merge request ##{@merge_request.iid}", @merge_request.title))
|
||||
@target_url = project_merge_request_url(@project, @merge_request)
|
||||
mail(from: sender(updated_by_user_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
|
||||
end
|
||||
|
||||
def closed_merge_request_email(recipient_id, merge_request_id, updated_by_user_id)
|
||||
@merge_request = MergeRequest.find(merge_request_id)
|
||||
@updated_by = User.find updated_by_user_id
|
||||
@project = @merge_request.project
|
||||
mail(to: recipient(recipient_id), subject: subject("Closed merge request ##{@merge_request.iid}", @merge_request.title))
|
||||
@target_url = project_merge_request_url(@project, @merge_request)
|
||||
mail(from: sender(updated_by_user_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
|
||||
end
|
||||
|
||||
def merged_merge_request_email(recipient_id, merge_request_id)
|
||||
@merge_request = MergeRequest.find(merge_request_id)
|
||||
@project = @merge_request.project
|
||||
mail(to: recipient(recipient_id), subject: subject("Accepted merge request ##{@merge_request.iid}", @merge_request.title))
|
||||
@target_url = project_merge_request_url(@project, @merge_request)
|
||||
mail(from: sender(@merge_request.author_id_of_changes),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -57,7 +69,7 @@ module Emails
|
||||
# >> subject('Lorem ipsum', 'Dolor sit amet')
|
||||
# => "GitLab Merge Request | Lorem ipsum | Dolor sit amet"
|
||||
def subject(*extra)
|
||||
subject = "GitLab Merge Request |"
|
||||
subject = "Merge Request | "
|
||||
if @merge_request.for_fork?
|
||||
subject << "#{@merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} >> #{@merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}"
|
||||
else
|
||||
|
||||
@@ -4,27 +4,39 @@ module Emails
|
||||
@note = Note.find(note_id)
|
||||
@commit = @note.noteable
|
||||
@project = @note.project
|
||||
mail(to: recipient(recipient_id), subject: subject("Note for commit #{@commit.short_id}", @commit.title))
|
||||
@target_url = project_commit_url(@project, @commit, anchor: "note_#{@note.id}")
|
||||
mail(from: sender(@note.author_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@commit.title} (#{@commit.short_id})"))
|
||||
end
|
||||
|
||||
def note_issue_email(recipient_id, note_id)
|
||||
@note = Note.find(note_id)
|
||||
@issue = @note.noteable
|
||||
@project = @note.project
|
||||
mail(to: recipient(recipient_id), subject: subject("Note for issue ##{@issue.iid}"))
|
||||
@target_url = project_issue_url(@project, @issue, anchor: "note_#{@note.id}")
|
||||
mail(from: sender(@note.author_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@issue.title} (##{@issue.iid})"))
|
||||
end
|
||||
|
||||
def note_merge_request_email(recipient_id, note_id)
|
||||
@note = Note.find(note_id)
|
||||
@merge_request = @note.noteable
|
||||
@project = @note.project
|
||||
mail(to: recipient(recipient_id), subject: subject("Note for merge request ##{@merge_request.iid}"))
|
||||
@target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{@note.id}")
|
||||
mail(from: sender(@note.author_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("#{@merge_request.title} (!#{@merge_request.iid})"))
|
||||
end
|
||||
|
||||
def note_wall_email(recipient_id, note_id)
|
||||
@note = Note.find(note_id)
|
||||
@project = @note.project
|
||||
mail(to: recipient(recipient_id), subject: subject("Note on wall"))
|
||||
@target_url = project_wall_url(@note.project, anchor: "note_#{@note.id}")
|
||||
mail(from: sender(@note.author_id),
|
||||
to: recipient(recipient_id),
|
||||
subject: subject("Note on wall"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,7 @@ module Emails
|
||||
def new_user_email(user_id, password)
|
||||
@user = User.find(user_id)
|
||||
@password = password
|
||||
@target_url = user_url(@user)
|
||||
mail(to: @user.email, subject: subject("Account was created for you"))
|
||||
end
|
||||
|
||||
@@ -15,6 +16,7 @@ module Emails
|
||||
def new_ssh_key_email(key_id)
|
||||
@key = Key.find(key_id)
|
||||
@user = @key.user
|
||||
@target_url = user_url(@user)
|
||||
mail(to: @user.email, subject: subject("SSH key was added to your account"))
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,7 @@ module Emails
|
||||
def project_access_granted_email(user_project_id)
|
||||
@users_project = UsersProject.find user_project_id
|
||||
@project = @users_project.project
|
||||
@target_url = project_url(@project)
|
||||
mail(to: @users_project.user.email,
|
||||
subject: subject("Access to project was granted"))
|
||||
end
|
||||
@@ -10,6 +11,7 @@ module Emails
|
||||
def project_was_moved_email(project_id, user_id)
|
||||
@user = User.find user_id
|
||||
@project = Project.find project_id
|
||||
@target_url = project_url(@project)
|
||||
mail(to: @user.email,
|
||||
subject: subject("Project was moved"))
|
||||
end
|
||||
@@ -21,8 +23,15 @@ module Emails
|
||||
@commits = Commit.decorate(compare.commits)
|
||||
@diffs = compare.diffs
|
||||
@branch = branch
|
||||
if @commits.length > 1
|
||||
@target_url = project_compare_url(@project, from: @commits.first, to: @commits.last)
|
||||
else
|
||||
@target_url = project_commit_url(@project, @compare.commit)
|
||||
end
|
||||
|
||||
mail(to: recipient, subject: subject("New push to repository"))
|
||||
mail(from: sender(author_id),
|
||||
to: recipient,
|
||||
subject: subject("New push to repository"))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -15,16 +15,33 @@ class Notify < ActionMailer::Base
|
||||
default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port?
|
||||
default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root
|
||||
|
||||
default from: Gitlab.config.gitlab.email_from
|
||||
default from: Proc.new { default_sender_address.format }
|
||||
default reply_to: "noreply@#{Gitlab.config.gitlab.host}"
|
||||
|
||||
# Just send email with 3 seconds delay
|
||||
# Just send email with 2 seconds delay
|
||||
def self.delay
|
||||
delay_for(2.seconds)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# The default email address to send emails from
|
||||
def default_sender_address
|
||||
address = Mail::Address.new(Gitlab.config.gitlab.email_from)
|
||||
address.display_name = "GitLab"
|
||||
address
|
||||
end
|
||||
|
||||
# Return an email address that displays the name of the sender.
|
||||
# Only the displayed name changes; the actual email address is always the same.
|
||||
def sender(sender_id)
|
||||
if sender = User.find(sender_id)
|
||||
address = default_sender_address
|
||||
address.display_name = sender.name
|
||||
address.format
|
||||
end
|
||||
end
|
||||
|
||||
# Look up a User by their ID and return their email address
|
||||
#
|
||||
# recipient_id - User ID
|
||||
@@ -43,21 +60,21 @@ class Notify < ActionMailer::Base
|
||||
# Examples
|
||||
#
|
||||
# >> subject('Lorem ipsum')
|
||||
# => "GitLab | Lorem ipsum"
|
||||
# => "Lorem ipsum"
|
||||
#
|
||||
# # Automatically inserts Project name when @project is set
|
||||
# >> @project = Project.last
|
||||
# => #<Project id: 1, name: "Ruby on Rails", path: "ruby_on_rails", ...>
|
||||
# >> subject('Lorem ipsum')
|
||||
# => "GitLab | Ruby on Rails | Lorem ipsum "
|
||||
# => "Ruby on Rails | Lorem ipsum "
|
||||
#
|
||||
# # Accepts multiple arguments
|
||||
# >> subject('Lorem ipsum', 'Dolor sit amet')
|
||||
# => "GitLab | Lorem ipsum | Dolor sit amet"
|
||||
# => "Lorem ipsum | Dolor sit amet"
|
||||
def subject(*extra)
|
||||
subject = "GitLab"
|
||||
subject << (@project ? " | #{@project.name_with_namespace}" : "")
|
||||
subject << " | " + extra.join(' | ') if extra.present?
|
||||
subject = ""
|
||||
subject << "#{@project.name} | " if @project
|
||||
subject << extra.join(' | ') if extra.present?
|
||||
subject
|
||||
end
|
||||
end
|
||||
|
||||
@@ -43,7 +43,19 @@ class Ability
|
||||
:download_code
|
||||
]
|
||||
else
|
||||
[]
|
||||
group = if subject.kind_of?(Group)
|
||||
subject
|
||||
elsif subject.respond_to?(:group)
|
||||
subject.group
|
||||
else
|
||||
nil
|
||||
end
|
||||
|
||||
if group && group.has_projects_accessible_to?(nil)
|
||||
[:read_group]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -172,7 +184,7 @@ class Ability
|
||||
def group_abilities user, group
|
||||
rules = []
|
||||
|
||||
if group.users.include?(user) || user.admin?
|
||||
if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any?
|
||||
rules << :read_group
|
||||
end
|
||||
|
||||
@@ -228,6 +240,7 @@ class Ability
|
||||
can_manage = group_abilities(user, group).include?(:manage_group)
|
||||
if can_manage && (user != target_user)
|
||||
rules << :modify
|
||||
rules << :destroy
|
||||
end
|
||||
if !group.last_owner?(user) && (can_manage || (user == target_user))
|
||||
rules << :destroy
|
||||
|
||||
@@ -99,14 +99,16 @@ class Commit
|
||||
#
|
||||
# cut off, ellipses (`&hellp;`) are prepended to the commit message.
|
||||
def description
|
||||
description = safe_message
|
||||
title_end = safe_message.index(/\n/)
|
||||
@description ||= if (!title_end && safe_message.length > 100) || (title_end && title_end > 100)
|
||||
"…".html_safe << safe_message[80..-1]
|
||||
else
|
||||
safe_message.split(/\n/, 2)[1].try(:chomp)
|
||||
end
|
||||
end
|
||||
|
||||
title_end = description.index(/\n/)
|
||||
if (!title_end && description.length > 100) || (title_end && title_end > 100)
|
||||
"…".html_safe << description[80..-1]
|
||||
else
|
||||
description.split(/\n/, 2)[1].try(:chomp)
|
||||
end
|
||||
def description?
|
||||
description.present?
|
||||
end
|
||||
|
||||
# Regular expression that identifies commit message clauses that trigger issue closing.
|
||||
|
||||
@@ -23,7 +23,8 @@ module Issuable
|
||||
scope :assigned, -> { where("assignee_id IS NOT NULL") }
|
||||
scope :unassigned, -> { where("assignee_id IS NULL") }
|
||||
scope :of_projects, ->(ids) { where(project_id: ids) }
|
||||
|
||||
scope :opened, -> { with_state(:opened, :reopened) }
|
||||
scope :closed, -> { with_state(:closed) }
|
||||
|
||||
delegate :name,
|
||||
:email,
|
||||
|
||||
@@ -35,6 +35,12 @@ class Group < Namespace
|
||||
validates :avatar, file_size: { maximum: 100.kilobytes.to_i }
|
||||
|
||||
mount_uploader :avatar, AttachmentUploader
|
||||
|
||||
def self.accessible_to(user)
|
||||
accessible_ids = Project.accessible_to(user).pluck(:namespace_id)
|
||||
accessible_ids += user.groups.pluck(:id) if user
|
||||
where(id: accessible_ids)
|
||||
end
|
||||
|
||||
def human_name
|
||||
name
|
||||
|
||||
@@ -28,8 +28,6 @@ class Issue < ActiveRecord::Base
|
||||
|
||||
scope :of_group, ->(group) { where(project_id: group.project_ids) }
|
||||
scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
|
||||
scope :opened, -> { with_state(:opened, :reopened) }
|
||||
scope :closed, -> { with_state(:closed) }
|
||||
|
||||
attr_accessible :title, :assignee_id, :position, :description,
|
||||
:milestone_id, :label_list, :author_id_of_changes,
|
||||
@@ -50,9 +48,7 @@ class Issue < ActiveRecord::Base
|
||||
end
|
||||
|
||||
state :opened
|
||||
|
||||
state :reopened
|
||||
|
||||
state :closed
|
||||
end
|
||||
|
||||
|
||||
@@ -53,7 +53,7 @@ class Key < ActiveRecord::Base
|
||||
Tempfile.open('gitlab_key_file') do |file|
|
||||
file.puts key
|
||||
file.rewind
|
||||
cmd_output, cmd_status = popen("ssh-keygen -lf #{file.path}", '/tmp')
|
||||
cmd_output, cmd_status = popen(%W(ssh-keygen -lf #{file.path}), '/tmp')
|
||||
end
|
||||
|
||||
if cmd_status.zero?
|
||||
|
||||
@@ -100,8 +100,6 @@ class MergeRequest < ActiveRecord::Base
|
||||
|
||||
scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) }
|
||||
scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) }
|
||||
scope :opened, -> { with_state(:opened) }
|
||||
scope :closed, -> { with_state(:closed) }
|
||||
scope :merged, -> { with_state(:merged) }
|
||||
scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) }
|
||||
scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) }
|
||||
@@ -135,7 +133,7 @@ class MergeRequest < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def reload_code
|
||||
if merge_request_diff && opened?
|
||||
if merge_request_diff && open?
|
||||
merge_request_diff.reload_content
|
||||
end
|
||||
end
|
||||
@@ -160,6 +158,10 @@ class MergeRequest < ActiveRecord::Base
|
||||
MergeRequests::AutoMergeService.new.execute(self, current_user, commit_message)
|
||||
end
|
||||
|
||||
def open?
|
||||
opened? || reopened?
|
||||
end
|
||||
|
||||
def mr_and_commit_notes
|
||||
# Fetch comments only from last 100 commits
|
||||
commits_for_notes_limit = 100
|
||||
|
||||
@@ -69,12 +69,6 @@ class MergeRequestDiff < ActiveRecord::Base
|
||||
end
|
||||
end
|
||||
|
||||
# When Git::Diff is not able to get diff
|
||||
# because of git timeout it return this value
|
||||
def broken_diffs
|
||||
[Gitlab::Git::Diff::BROKEN_DIFF]
|
||||
end
|
||||
|
||||
# Collect array of Git::Commit objects
|
||||
# between target and source branches
|
||||
def unmerged_commits
|
||||
|
||||
@@ -47,6 +47,14 @@ class Namespace < ActiveRecord::Base
|
||||
def self.global_id
|
||||
'GLN'
|
||||
end
|
||||
|
||||
def projects_accessible_to(user)
|
||||
projects.accessible_to(user)
|
||||
end
|
||||
|
||||
def has_projects_accessible_to?(user)
|
||||
projects_accessible_to(user).present?
|
||||
end
|
||||
|
||||
def to_param
|
||||
path
|
||||
|
||||
@@ -28,6 +28,8 @@ class Project < ActiveRecord::Base
|
||||
include Gitlab::VisibilityLevel
|
||||
extend Enumerize
|
||||
|
||||
default_value_for :archived, false
|
||||
|
||||
ActsAsTaggableOn.strict_case_match = true
|
||||
|
||||
attr_accessible :name, :path, :description, :issues_tracker, :label_list,
|
||||
@@ -53,15 +55,13 @@ class Project < ActiveRecord::Base
|
||||
has_one :hipchat_service, dependent: :destroy
|
||||
has_one :flowdock_service, dependent: :destroy
|
||||
has_one :assembla_service, dependent: :destroy
|
||||
has_one :gemnasium_service, dependent: :destroy
|
||||
has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id"
|
||||
has_one :forked_from_project, through: :forked_project_link
|
||||
|
||||
# Merge Requests for target project should be removed with it
|
||||
has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id"
|
||||
|
||||
# Merge requests from source project should be kept when source project was removed
|
||||
has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest
|
||||
|
||||
has_many :issues, -> { order "state DESC, created_at DESC" }, dependent: :destroy
|
||||
has_many :services, dependent: :destroy
|
||||
has_many :events, dependent: :destroy
|
||||
@@ -70,10 +70,8 @@ class Project < ActiveRecord::Base
|
||||
has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet"
|
||||
has_many :hooks, dependent: :destroy, class_name: "ProjectHook"
|
||||
has_many :protected_branches, dependent: :destroy
|
||||
|
||||
has_many :users_projects, dependent: :destroy
|
||||
has_many :users, through: :users_projects
|
||||
|
||||
has_many :deploy_keys_projects, dependent: :destroy
|
||||
has_many :deploy_keys, through: :deploy_keys_projects
|
||||
|
||||
@@ -96,15 +94,12 @@ class Project < ActiveRecord::Base
|
||||
validates :issues_enabled, :wall_enabled, :merge_requests_enabled,
|
||||
:wiki_enabled, inclusion: { in: [true, false] }
|
||||
validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true
|
||||
|
||||
validates :namespace, presence: true
|
||||
validates_uniqueness_of :name, scope: :namespace_id
|
||||
validates_uniqueness_of :path, scope: :namespace_id
|
||||
|
||||
validates :import_url,
|
||||
format: { with: URI::regexp(%w(git http https)), message: "should be a valid url" },
|
||||
if: :import?
|
||||
|
||||
validate :check_limit, on: :create
|
||||
|
||||
# Scopes
|
||||
@@ -117,18 +112,57 @@ class Project < ActiveRecord::Base
|
||||
scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") }
|
||||
scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
|
||||
scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
|
||||
scope :public_only, -> { where(visibility_level: PUBLIC) }
|
||||
scope :public_or_internal_only, ->(user) { where("visibility_level IN (:levels)", levels: user ? [ INTERNAL, PUBLIC ] : [ PUBLIC ]) }
|
||||
|
||||
scope :public_only, -> { where(visibility_level: Project::PUBLIC) }
|
||||
scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) }
|
||||
scope :non_archived, -> { where(archived: false) }
|
||||
|
||||
enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab
|
||||
|
||||
state_machine :import_status, initial: :none do
|
||||
event :import_start do
|
||||
transition :none => :started
|
||||
end
|
||||
|
||||
event :import_finish do
|
||||
transition :started => :finished
|
||||
end
|
||||
|
||||
event :import_fail do
|
||||
transition :started => :failed
|
||||
end
|
||||
|
||||
event :import_retry do
|
||||
transition :failed => :started
|
||||
end
|
||||
|
||||
state :started
|
||||
state :finished
|
||||
state :failed
|
||||
|
||||
after_transition any => :started, :do => :add_import_job
|
||||
end
|
||||
|
||||
class << self
|
||||
def public_and_internal_levels
|
||||
[Project::PUBLIC, Project::INTERNAL]
|
||||
end
|
||||
|
||||
def abandoned
|
||||
where('projects.last_activity_at < ?', 6.months.ago)
|
||||
end
|
||||
|
||||
def publicish(user)
|
||||
visibility_levels = [Project::PUBLIC]
|
||||
visibility_levels += [Project::INTERNAL] if user
|
||||
where(visibility_level: visibility_levels)
|
||||
end
|
||||
|
||||
def accessible_to(user)
|
||||
accessible_ids = publicish(user).pluck(:id)
|
||||
accessible_ids += user.authorized_projects.pluck(:id) if user
|
||||
where(id: accessible_ids)
|
||||
end
|
||||
|
||||
def with_push
|
||||
includes(:events).where('events.action = ?', Event::PUSHED)
|
||||
end
|
||||
@@ -146,15 +180,13 @@ class Project < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def find_with_namespace(id)
|
||||
if id.include?("/")
|
||||
id = id.split("/")
|
||||
namespace = Namespace.find_by(path: id.first)
|
||||
return nil unless namespace
|
||||
return nil unless id.include?("/")
|
||||
|
||||
where(namespace_id: namespace.id).find_by(path: id.second)
|
||||
else
|
||||
where(path: id, namespace_id: nil).last
|
||||
end
|
||||
id = id.split("/")
|
||||
namespace = Namespace.find_by(path: id.first)
|
||||
return nil unless namespace
|
||||
|
||||
where(namespace_id: namespace.id).find_by(path: id.second)
|
||||
end
|
||||
|
||||
def visibility_levels
|
||||
@@ -184,12 +216,28 @@ class Project < ActiveRecord::Base
|
||||
id && persisted?
|
||||
end
|
||||
|
||||
def add_import_job
|
||||
RepositoryImportWorker.perform_in(2.seconds, id)
|
||||
end
|
||||
|
||||
def import?
|
||||
import_url.present?
|
||||
end
|
||||
|
||||
def imported?
|
||||
imported
|
||||
import_finished?
|
||||
end
|
||||
|
||||
def import_in_progress?
|
||||
import? && import_status == 'started'
|
||||
end
|
||||
|
||||
def import_failed?
|
||||
import_status == 'failed'
|
||||
end
|
||||
|
||||
def import_finished?
|
||||
import_status == 'finished'
|
||||
end
|
||||
|
||||
def check_limit
|
||||
@@ -259,7 +307,7 @@ class Project < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def available_services_names
|
||||
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push)
|
||||
%w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium)
|
||||
end
|
||||
|
||||
def gitlab_ci?
|
||||
@@ -343,18 +391,17 @@ class Project < ActiveRecord::Base
|
||||
branch_name = ref.gsub("refs/heads/", "")
|
||||
c_ids = self.repository.commits_between(oldrev, newrev).map(&:id)
|
||||
|
||||
# Update code for merge requests into project between project branches
|
||||
mrs = self.merge_requests.opened.by_branch(branch_name).to_a
|
||||
# Update code for merge requests between project and project fork
|
||||
mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a
|
||||
|
||||
mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked }
|
||||
|
||||
# Close merge requests
|
||||
mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a
|
||||
mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) }
|
||||
mrs.each { |merge_request| MergeRequests::MergeService.new.execute(merge_request, user, nil) }
|
||||
|
||||
# Update code for merge requests into project between project branches
|
||||
mrs = self.merge_requests.opened.by_branch(branch_name).to_a
|
||||
# Update code for merge requests between project and project fork
|
||||
mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a
|
||||
mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked }
|
||||
|
||||
true
|
||||
end
|
||||
|
||||
|
||||
@@ -17,9 +17,10 @@
|
||||
class ProjectHook < WebHook
|
||||
belongs_to :project
|
||||
|
||||
attr_accessible :push_events, :issues_events, :merge_requests_events
|
||||
attr_accessible :push_events, :issues_events, :merge_requests_events, :tag_push_events
|
||||
|
||||
scope :push_hooks, -> { where(push_events: true) }
|
||||
scope :tag_push_hooks, -> { where(tag_push_events: true) }
|
||||
scope :issue_hooks, -> { where(issues_events: true) }
|
||||
scope :merge_request_hooks, -> { where(merge_requests_events: true) }
|
||||
end
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# project_url :string(255)
|
||||
# subdomain :string(255)
|
||||
# room :string(255)
|
||||
# api_key :string(255)
|
||||
#
|
||||
|
||||
class AssemblaService < Service
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# project_url :string(255)
|
||||
# subdomain :string(255)
|
||||
# room :string(255)
|
||||
# api_key :string(255)
|
||||
#
|
||||
|
||||
class CampfireService < Service
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# project_url :string(255)
|
||||
# subdomain :string(255)
|
||||
# room :string(255)
|
||||
# api_key :string(255)
|
||||
#
|
||||
|
||||
class EmailsOnPushService < Service
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# project_url :string(255)
|
||||
# subdomain :string(255)
|
||||
# room :string(255)
|
||||
# api_key :string(255)
|
||||
#
|
||||
|
||||
require "flowdock-git-hook"
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: services
|
||||
#
|
||||
# id :integer not null, primary key
|
||||
# type :string(255)
|
||||
# title :string(255)
|
||||
# token :string(255)
|
||||
# project_id :integer not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# active :boolean default(FALSE), not null
|
||||
# project_url :string(255)
|
||||
# subdomain :string(255)
|
||||
# room :string(255)
|
||||
# api_key :string(255)
|
||||
#
|
||||
|
||||
require "gemnasium/gitlab_service"
|
||||
|
||||
class GemnasiumService < Service
|
||||
validates :token, :api_key, presence: true, if: :activated?
|
||||
|
||||
def title
|
||||
'Gemnasium'
|
||||
end
|
||||
|
||||
def description
|
||||
'Gemnasium monitors your project dependencies and alerts you about updates and security vulnerabilities.'
|
||||
end
|
||||
|
||||
def to_param
|
||||
'gemnasium'
|
||||
end
|
||||
|
||||
def fields
|
||||
[
|
||||
{ type: 'text', name: 'api_key', placeholder: 'Your personal API KEY on gemnasium.com ' },
|
||||
{ type: 'text', name: 'token', placeholder: 'The project\'s slug on gemnasium.com' }
|
||||
]
|
||||
end
|
||||
|
||||
def execute(push_data)
|
||||
repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git")
|
||||
Gemnasium::GitlabService.execute(
|
||||
ref: push_data[:ref],
|
||||
before: push_data[:before],
|
||||
after: push_data[:after],
|
||||
token: token,
|
||||
api_key: api_key,
|
||||
repo: repo_path
|
||||
)
|
||||
end
|
||||
end
|
||||
@@ -13,6 +13,7 @@
|
||||
# project_url :string(255)
|
||||
# subdomain :string(255)
|
||||
# room :string(255)
|
||||
# api_key :string(255)
|
||||
#
|
||||
|
||||
class GitlabCiService < Service
|
||||
@@ -36,7 +37,7 @@ class GitlabCiService < Service
|
||||
end
|
||||
|
||||
def commit_status sha
|
||||
response = HTTParty.get(commit_status_path(sha))
|
||||
response = HTTParty.get(commit_status_path(sha), verify: false)
|
||||
|
||||
if response.code == 200 and response["status"]
|
||||
response["status"]
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# project_url :string(255)
|
||||
# subdomain :string(255)
|
||||
# room :string(255)
|
||||
# api_key :string(255)
|
||||
#
|
||||
|
||||
class HipchatService < Service
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
# project_url :string(255)
|
||||
# subdomain :string(255)
|
||||
# room :string(255)
|
||||
# api_key :string(255)
|
||||
#
|
||||
|
||||
class PivotaltrackerService < Service
|
||||
|
||||
@@ -134,6 +134,7 @@ class Repository
|
||||
Rails.cache.delete(cache_key(:commit_count))
|
||||
Rails.cache.delete(cache_key(:graph_log))
|
||||
Rails.cache.delete(cache_key(:readme))
|
||||
Rails.cache.delete(cache_key(:contribution_guide))
|
||||
end
|
||||
|
||||
def graph_log
|
||||
@@ -167,6 +168,12 @@ class Repository
|
||||
end
|
||||
end
|
||||
|
||||
def contribution_guide
|
||||
Rails.cache.fetch(cache_key(:contribution_guide)) do
|
||||
tree(:head).contribution_guide
|
||||
end
|
||||
end
|
||||
|
||||
def head_commit
|
||||
commit(self.root_ref)
|
||||
end
|
||||
|
||||
@@ -13,12 +13,15 @@
|
||||
# project_url :string(255)
|
||||
# subdomain :string(255)
|
||||
# room :string(255)
|
||||
# api_key :string(255)
|
||||
#
|
||||
|
||||
# To add new service you should build a class inherited from Service
|
||||
# and implement a set of methods
|
||||
class Service < ActiveRecord::Base
|
||||
attr_accessible :title, :token, :type, :active
|
||||
default_value_for :active, false
|
||||
|
||||
attr_accessible :title, :token, :type, :active, :api_key
|
||||
|
||||
belongs_to :project
|
||||
has_one :service_hook
|
||||
|
||||