Commit 1b7b7909 authored by Sacred Seven's avatar Sacred Seven

merged v7.4.2

parents 03d9d5b8 477743a1
v 7.4.2
- Fix internal snippet exposing for unauthenticated users
v 7.4.1
- Fix LDAP authentication for Git HTTP access
- Fix LDAP config lookup for provider 'ldap'
- Fix public snippets
- Fix 500 error on projects with nested submodules
v 7.4.0
- Refactored membership logic
- Improve error reporting on users API (Julien Bianchi)
- Refactor test coverage tools usage. Use SIMPLECOV=true to generate it locally
- Default branch is protected by default
- Increase unicorn timeout to 60 seconds
- Sort search autocomplete projects by stars count so most popular go first
- Add README to tab on project show page
- Do not delete tmp/repositories itself during clean-up, only its contents
- Support for backup uploads to remote storage
- Prevent notes polling when there are not notes
- API: Add support for forking a project via the API (Bernhard Kaindl)
- API: filter project issues by milestone (Julien Bianchi)
- Fail harder in the backup script
- Changes to Slack service structure, only webhook url needed
- Zen mode for wiki and milestones (Robert Schilling)
- Move Emoji parsing to html-pipeline-gitlab (Robert Schilling)
- Font Awesome 4.2 integration (Sullivan Senechal)
- Add Pushover service integration (Sullivan Senechal)
- Add select field type for services options (Sullivan Senechal)
- Add cross-project references to the Markdown parser (Vinnie Okada)
- Add task lists to issue and merge request descriptions (Vinnie Okada)
- Snippets can be public, internal or private
- Improve danger zone: ask project path to confirm data-loss action
- Raise exception on forgery
- Show build coverage in Merge Requests (requires GitLab CI v5.1)
- New milestone and label links on issue edit form
- Improved repository graphs
- Improve event note display in dashboard and project activity views (Vinnie Okada)
- Add users sorting to admin area
- UI improvements
- Fix ambiguous sha problem with mentioned commit
- Fixed bug with apostrophe when at mentioning users
- Add active directory ldap option
- Developers can push to wiki repo. Protected branches does not affect wiki repo any more
- Faster rev list
- Fix branch removal
v 7.3.2 v 7.3.2
- Fix creating new file via web editor - Fix creating new file via web editor
- Use gitlab-shell v2.0.1 - Use gitlab-shell v2.0.1
......
...@@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic ...@@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic
## Security vulnerability disclosure ## Security vulnerability disclosure
Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities.
## Closing policy for issues and merge requests ## Closing policy for issues and merge requests
...@@ -22,7 +22,7 @@ Issues and merge requests should be in English and contain appropriate language ...@@ -22,7 +22,7 @@ Issues and merge requests should be in English and contain appropriate language
## Issue tracker ## 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#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/). 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://about.gitlab.com/subscription/) and [consulting services](http://about.gitlab.com/consultancy/) are available from [GitLab.com](http://about.gitlab.com/).
The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md). If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request. 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. The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md). If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request. 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.
...@@ -61,7 +61,7 @@ If you can, please submit a merge request with the fix or improvements including ...@@ -61,7 +61,7 @@ If you can, please submit a merge request with the fix or improvements including
1. Fork the project on GitLab Cloud 1. Fork the project on GitLab Cloud
1. Create a feature branch 1. Create a feature branch
1. Write [tests](README.md#run-the-tests) and code 1. Write [tests](README.md#run-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG) 1. Add your changes to the [CHANGELOG](CHANGELOG) insert your line at a [random point](doc/workflow/gitlab_flow.md#do-not-order-commits-with-rebase) in the current version
1. If you are changing the README, some documentation or other things which have no effect on the tests, add `[ci skip]` somewhere in the commit message 1. If you are changing the README, some documentation or other things which have no effect on the tests, add `[ci skip]` somewhere in the commit message
1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) 1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)
1. Push the commit to your fork 1. Push the commit to your fork
...@@ -92,6 +92,7 @@ For examples of feedback on merge requests please look at already [closed merge ...@@ -92,6 +92,7 @@ For examples of feedback on merge requests please look at already [closed merge
1. The change is as small as possible (see the above paragraph for details) 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. Include proper tests and make all tests pass (unless it contains a test exposing a bug in existing code)
1. All tests have to pass, if you suspect a failing CI build is unrelated to your contribution ask for tests to be restarted. See [the CI setup document](http://doc.gitlab.com/ce/development/ci_setup.html) on who you can ask for test restart.
1. Initially contains a single commit (please use `git rebase -i` to squash commits) 1. Initially contains a single commit (please use `git rebase -i` to squash commits)
1. Can merge without problems (if not please merge `master`, never rebase commits pushed to the remote server) 1. Can merge without problems (if not please merge `master`, never rebase commits pushed to the remote server)
1. Does not break any existing functionality 1. Does not break any existing functionality
......
...@@ -31,7 +31,7 @@ gem 'omniauth-shibboleth' ...@@ -31,7 +31,7 @@ gem 'omniauth-shibboleth'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '~> 6.0' gem "gitlab_git", '7.0.0.rc10'
# Ruby/Rack Git Smart-HTTP Server Handler # Ruby/Rack Git Smart-HTTP Server Handler
gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack'
...@@ -70,8 +70,8 @@ gem "carrierwave" ...@@ -70,8 +70,8 @@ gem "carrierwave"
gem 'dropzonejs-rails' gem 'dropzonejs-rails'
# for aws storage # for aws storage
gem "fog", "~> 1.14", group: :aws gem "fog", "~> 1.14"
gem "unf", group: :aws gem "unf"
# Authorization # Authorization
gem "six" gem "six"
...@@ -79,6 +79,9 @@ gem "six" ...@@ -79,6 +79,9 @@ gem "six"
# Seed data # Seed data
gem "seed-fu" gem "seed-fu"
# Markup pipeline for GitLab
gem 'html-pipeline-gitlab', '~> 0.1.0'
# Markdown to HTML # Markdown to HTML
gem "github-markup" gem "github-markup"
...@@ -86,7 +89,7 @@ gem "github-markup" ...@@ -86,7 +89,7 @@ gem "github-markup"
gem 'redcarpet', '~> 3.1.2' gem 'redcarpet', '~> 3.1.2'
gem 'RedCloth' gem 'RedCloth'
gem 'rdoc', '~>3.6' gem 'rdoc', '~>3.6'
gem 'org-ruby' gem 'org-ruby', '= 0.9.9'
gem 'creole', '~>0.3.6' gem 'creole', '~>0.3.6'
gem 'wikicloth', '=0.8.1' gem 'wikicloth', '=0.8.1'
gem 'asciidoctor', '= 0.1.4' gem 'asciidoctor', '= 0.1.4'
...@@ -157,7 +160,7 @@ gem "rack-attack" ...@@ -157,7 +160,7 @@ gem "rack-attack"
# Ace editor # Ace editor
gem 'ace-rails-ap' gem 'ace-rails-ap'
# Keyboard shortcuts # Keyboard shortcuts
gem 'mousetrap-rails' gem 'mousetrap-rails'
# Semantic UI Sass for Sidebar # Semantic UI Sass for Sidebar
...@@ -177,7 +180,7 @@ gem "jquery-ui-rails" ...@@ -177,7 +180,7 @@ gem "jquery-ui-rails"
gem "jquery-scrollto-rails" gem "jquery-scrollto-rails"
gem "raphael-rails", "~> 2.1.2" gem "raphael-rails", "~> 2.1.2"
gem 'bootstrap-sass', '~> 3.0' gem 'bootstrap-sass', '~> 3.0'
gem "font-awesome-rails", '~> 3.2' gem "font-awesome-rails", '~> 4.2'
gem "gitlab_emoji", "~> 0.0.1.1" gem "gitlab_emoji", "~> 0.0.1.1"
gem "gon", '~> 5.0.0' gem "gon", '~> 5.0.0'
gem 'nprogress-rails' gem 'nprogress-rails'
......
...@@ -106,7 +106,7 @@ GEM ...@@ -106,7 +106,7 @@ GEM
devise (~> 3.2) devise (~> 3.2)
diff-lcs (1.2.5) diff-lcs (1.2.5)
diffy (3.0.3) diffy (3.0.3)
docile (1.1.1) docile (1.1.5)
dotenv (0.9.0) dotenv (0.9.0)
dropzonejs-rails (0.4.14) dropzonejs-rails (0.4.14)
rails (> 3.1) rails (> 3.1)
...@@ -152,7 +152,7 @@ GEM ...@@ -152,7 +152,7 @@ GEM
net-ssh (>= 2.1.3) net-ssh (>= 2.1.3)
fog-json (1.0.0) fog-json (1.0.0)
multi_json (~> 1.0) multi_json (~> 1.0)
font-awesome-rails (3.2.1.3) font-awesome-rails (4.2.0.0)
railties (>= 3.2, < 5.0) railties (>= 3.2, < 5.0)
foreman (0.63.0) foreman (0.63.0)
dotenv (>= 0.7) dotenv (>= 0.7)
...@@ -168,7 +168,7 @@ GEM ...@@ -168,7 +168,7 @@ GEM
multi_json multi_json
gitlab-grack (2.0.0.pre) gitlab-grack (2.0.0.pre)
rack (~> 1.5.1) rack (~> 1.5.1)
gitlab-grit (2.6.11) gitlab-grit (2.6.12)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
diff-lcs (~> 1.1) diff-lcs (~> 1.1)
mime-types (~> 1.15) mime-types (~> 1.15)
...@@ -179,10 +179,9 @@ GEM ...@@ -179,10 +179,9 @@ GEM
mime-types (~> 1.19) mime-types (~> 1.19)
gitlab_emoji (0.0.1.1) gitlab_emoji (0.0.1.1)
emoji (~> 1.0.1) emoji (~> 1.0.1)
gitlab_git (6.2.1) gitlab_git (7.0.0.rc10)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
gitlab-grit (~> 2.6)
gitlab-linguist (~> 3.0) gitlab-linguist (~> 3.0)
rugged (~> 0.21.0) rugged (~> 0.21.0)
gitlab_meta (7.0) gitlab_meta (7.0)
...@@ -239,6 +238,14 @@ GEM ...@@ -239,6 +238,14 @@ GEM
hipchat (0.14.0) hipchat (0.14.0)
httparty httparty
httparty httparty
html-pipeline (1.11.0)
activesupport (>= 2)
nokogiri (~> 1.4)
html-pipeline-gitlab (0.1.5)
actionpack (~> 4)
gitlab_emoji (~> 0.0.1)
html-pipeline (~> 1.11.0)
sanitize (~> 2.1)
http_parser.rb (0.5.3) http_parser.rb (0.5.3)
httparty (0.13.0) httparty (0.13.0)
json (~> 1.8) json (~> 1.8)
...@@ -327,7 +334,7 @@ GEM ...@@ -327,7 +334,7 @@ GEM
omniauth-twitter (1.0.1) omniauth-twitter (1.0.1)
multi_json (~> 1.3) multi_json (~> 1.3)
omniauth-oauth (~> 1.0) omniauth-oauth (~> 1.0)
org-ruby (0.9.8) org-ruby (0.9.9)
rubypants (~> 0.2) rubypants (~> 0.2)
orm_adapter (0.5.0) orm_adapter (0.5.0)
pg (0.15.1) pg (0.15.1)
...@@ -471,7 +478,7 @@ GEM ...@@ -471,7 +478,7 @@ GEM
redis (>= 3.0.4) redis (>= 3.0.4)
redis-namespace (>= 1.3.1) redis-namespace (>= 1.3.1)
simple_oauth (0.1.9) simple_oauth (0.1.9)
simplecov (0.8.2) simplecov (0.9.0)
docile (~> 1.1.0) docile (~> 1.1.0)
multi_json multi_json
simplecov-html (~> 0.8.0) simplecov-html (~> 0.8.0)
...@@ -609,7 +616,7 @@ DEPENDENCIES ...@@ -609,7 +616,7 @@ DEPENDENCIES
factory_girl_rails factory_girl_rails
ffaker ffaker
fog (~> 1.14) fog (~> 1.14)
font-awesome-rails (~> 3.2) font-awesome-rails (~> 4.2)
foreman foreman
gemnasium-gitlab-service (~> 0.2) gemnasium-gitlab-service (~> 0.2)
github-markup github-markup
...@@ -617,7 +624,7 @@ DEPENDENCIES ...@@ -617,7 +624,7 @@ DEPENDENCIES
gitlab-grack (~> 2.0.0.pre) gitlab-grack (~> 2.0.0.pre)
gitlab-linguist (~> 3.0.0) gitlab-linguist (~> 3.0.0)
gitlab_emoji (~> 0.0.1.1) gitlab_emoji (~> 0.0.1.1)
gitlab_git (~> 6.0) gitlab_git (= 7.0.0.rc10)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.1.0) gitlab_omniauth-ldap (= 1.1.0)
gollum-lib (~> 3.0.0) gollum-lib (~> 3.0.0)
...@@ -629,6 +636,7 @@ DEPENDENCIES ...@@ -629,6 +636,7 @@ DEPENDENCIES
guard-spinach guard-spinach
haml-rails haml-rails
hipchat (~> 0.14.0) hipchat (~> 0.14.0)
html-pipeline-gitlab (~> 0.1.0)
httparty httparty
jasmine (= 2.0.2) jasmine (= 2.0.2)
jquery-atwho-rails (~> 0.3.3) jquery-atwho-rails (~> 0.3.3)
...@@ -649,7 +657,7 @@ DEPENDENCIES ...@@ -649,7 +657,7 @@ DEPENDENCIES
omniauth-google-oauth2 omniauth-google-oauth2
omniauth-shibboleth omniauth-shibboleth
omniauth-twitter omniauth-twitter
org-ruby org-ruby (= 0.9.9)
pg pg
poltergeist (~> 1.5.1) poltergeist (~> 1.5.1)
pry pry
......
...@@ -18,7 +18,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co ...@@ -18,7 +18,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
- Responds to merge requests the issue team mentions them in and monitors for new merge requests - Responds to merge requests the issue team mentions them in and monitors for new merge requests
- Provides feedback to the merge request submitter to improve the merge request (style, tests, etc.) - Provides feedback to the merge request submitter to improve the merge request (style, tests, etc.)
- Mark merge requests 'ready-for-merge' when they meet the contribution guidelines - Mark merge requests 'ready-for-merge' when they meet the contribution guidelines
- Mention developer(s) based on the [list of members and their specialities](https://www.gitlab.com/core-team/) - Mention developer(s) based on the [list of members and their specialities](https://about.gitlab.com/core-team/)
- Closes merge requests with no feedback from the reporter for two weeks - Closes merge requests with no feedback from the reporter for two weeks
## Priorities of the issue team ## Priorities of the issue team
...@@ -30,7 +30,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co ...@@ -30,7 +30,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co
## Mentioning people ## 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](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. 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://about.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 ## Workflow labels
...@@ -79,7 +79,7 @@ Thanks for the issue report but we only support issues for the latest stable ver ...@@ -79,7 +79,7 @@ Thanks for the issue report but we only support issues for the latest stable ver
### Support requests and configuration questions ### Support requests and configuration questions
Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the #gitlab IRC channel on Freenode or the http://www.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information. Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the #gitlab IRC channel on Freenode or the http://about.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information.
### Code format ### Code format
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) - [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) - [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
- [![PullReview stats](https://www.pullreview.com/gitlab/gitlab-org/gitlab-ce/badges/master.svg?)](https://www.pullreview.com/gitlab.gitlab.com/gitlab-org/gitlab-ce/reviews/master) - [![PullReview stats](https://www.pullreview.com/gitlab/gitlab-org/gitlab-ce/badges/master.svg?)](https://www.pullreview.com/gitlab.gitlab.com/gitlab-org/gitlab-ce/reviews/master)
...@@ -38,17 +38,6 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a ...@@ -38,17 +38,6 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a
- [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations. - [GitLab Enterprise Edition](https://about.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations.
- [GitLab CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab. - [GitLab CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab.
## Third-party applications
Access GitLab from multiple platforms with applications below.
These applications are maintained by contributors, GitLab B.V. does not offer support for them.
- [iPhone app](http://gitlabcontrol.com/)
- [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en)
- [Chrome app](https://chrome.google.com/webstore/detail/chrome-gitlab-notifier/eageapgbnjicdjjihgclpclilenjbobi)
- [Command line client](https://github.com/drewblessing/gitlab-cli)
- [Ruby API wrapper](https://github.com/NARKOZ/gitlab)
## Requirements ## Requirements
- Ubuntu/Debian/CentOS/RHEL** - Ubuntu/Debian/CentOS/RHEL**
...@@ -61,7 +50,19 @@ These applications are maintained by contributors, GitLab B.V. does not offer su ...@@ -61,7 +50,19 @@ These applications are maintained by contributors, GitLab B.V. does not offer su
## Installation ## Installation
Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/). Please see [the installation page on the GitLab website](https://about.gitlab.com/installation/) for the various options.
Since a manual installation is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm).
## Third-party applications
Access GitLab from multiple platforms with applications below.
These applications are maintained by contributors, GitLab B.V. does not offer support for them.
- [iPhone app](http://gitlabcontrol.com/)
- [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en)
- [Chrome app](https://chrome.google.com/webstore/detail/chrome-gitlab-notifier/eageapgbnjicdjjihgclpclilenjbobi)
- [Command line client](https://github.com/drewblessing/gitlab-cli)
- [Ruby API wrapper](https://github.com/NARKOZ/gitlab)
### New versions ### New versions
......
...@@ -46,10 +46,10 @@ class Admin ...@@ -46,10 +46,10 @@ class Admin
modal.hide() modal.hide()
$('.change-owner-link').show() $('.change-owner-link').show()
$('li.users_project').bind 'ajax:success', -> $('li.project_member').bind 'ajax:success', ->
Turbolinks.visit(location.href) Turbolinks.visit(location.href)
$('li.users_group').bind 'ajax:success', -> $('li.group_member').bind 'ajax:success', ->
Turbolinks.visit(location.href) Turbolinks.visit(location.href)
@Admin = Admin @Admin = Admin
...@@ -15,13 +15,14 @@ ...@@ -15,13 +15,14 @@
#= require jquery.atwho #= require jquery.atwho
#= require jquery.scrollTo #= require jquery.scrollTo
#= require jquery.blockUI #= require jquery.blockUI
#= require turbolinks
#= require jquery.turbolinks #= require jquery.turbolinks
#= require turbolinks
#= require bootstrap #= require bootstrap
#= require select2 #= require select2
#= require raphael #= require raphael
#= require g.raphael-min #= require g.raphael-min
#= require g.bar-min #= require g.bar-min
#= require chart-lib.min
#= require branch-graph #= require branch-graph
#= require highlight.pack #= require highlight.pack
#= require ace/ace #= require ace/ace
...@@ -149,7 +150,6 @@ $ -> ...@@ -149,7 +150,6 @@ $ ->
if (flash = $(".flash-container")).length > 0 if (flash = $(".flash-container")).length > 0
flash.click -> $(@).fadeOut() flash.click -> $(@).fadeOut()
flash.show() flash.show()
setTimeout (-> flash.fadeOut()), 5000
# Disable form buttons while a form is submitting # Disable form buttons while a form is submitting
$('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) -> $('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) ->
...@@ -172,14 +172,21 @@ $ -> ...@@ -172,14 +172,21 @@ $ ->
# Show/hide comments on diff # Show/hide comments on diff
$("body").on "click", ".js-toggle-diff-comments", (e) -> $("body").on "click", ".js-toggle-diff-comments", (e) ->
$(@).find('i'). $(@).find('i').
toggleClass('icon-chevron-down'). toggleClass('fa fa-chevron-down').
toggleClass('icon-chevron-up') toggleClass('fa fa-chevron-up')
$(@).closest(".diff-file").find(".notes_holder").toggle() $(@).closest(".diff-file").find(".notes_holder").toggle()
e.preventDefault() e.preventDefault()
# RTL Support in wiki pages # RTL Support in wiki pages
$('.wiki').find('*').not('li').attr('dir', 'auto'); $('.wiki').find('*').not('li').attr('dir', 'auto');
$(document).on "click", '.js-confirm-danger', (e) ->
e.preventDefault()
btn = $(e.target)
text = btn.data("confirm-danger-message")
form = btn.closest("form")
new ConfirmDangerModal(form, text)
(($) -> (($) ->
# Disable an element and add the 'disabled' Bootstrap class # Disable an element and add the 'disabled' Bootstrap class
$.fn.extend disable: -> $.fn.extend disable: ->
......
window.updateTaskState = (taskableType) ->
objType = taskableType.data
isChecked = $(this).prop("checked")
if $(this).is(":checked")
stateEvent = "task_check"
else
stateEvent = "task_uncheck"
taskableUrl = $("form.edit-" + objType).first().attr("action")
taskableNum = taskableUrl.match(/\d+$/)
taskNum = 0
$("li.task-list-item input:checkbox").each( (index, e) =>
if e == this
taskNum = index + 1
)
$.ajax
type: "PATCH"
url: taskableUrl
data: objType + "[state_event]=" + stateEvent +
"&" + objType + "[task_num]=" + taskNum
...@@ -8,7 +8,7 @@ $ -> ...@@ -8,7 +8,7 @@ $ ->
# #
$("body").on "click", ".js-toggle-button", (e) -> $("body").on "click", ".js-toggle-button", (e) ->
$(@).find('i'). $(@).find('i').
toggleClass('icon-chevron-down'). toggleClass('fa fa-chevron-down').
toggleClass('icon-chevron-up') toggleClass('fa fa-chevron-up')
$(@).closest(".js-toggle-container").find(".js-toggle-content").toggle() $(@).closest(".js-toggle-container").find(".js-toggle-content").toggle()
e.preventDefault() e.preventDefault()
...@@ -90,11 +90,15 @@ class @BranchGraph ...@@ -90,11 +90,15 @@ class @BranchGraph
renderPartialGraph: -> renderPartialGraph: ->
start = Math.floor((@element.scrollTop() - @offsetY) / @unitTime) - 10 start = Math.floor((@element.scrollTop() - @offsetY) / @unitTime) - 10
start = 0 if start < 0 if start < 0
isGraphEdge = true
start = 0
end = start + 40 end = start + 40
end = @commits.length if @commits.length < end if @commits.length < end
isGraphEdge = true
end = @commits.length
if @prev_start == -1 or Math.abs(@prev_start - start) > 10 if @prev_start == -1 or Math.abs(@prev_start - start) > 10 or isGraphEdge
i = start i = start
@prev_start = start @prev_start = start
......
@Chart =
labels: []
values: []
init: (labels, values, title) ->
r = Raphael('activity-chart')
fin = ->
@flag = r.popup(@bar.x, @bar.y, @bar.value or "0").insertBefore(this)
fout = ->
@flag.animate
opacity: 0, 300, -> @remove()
r.text(160, 10, title).attr font: "13px sans-serif"
r.barchart(
10, 20, 560, 200,
[values],
{colors:["#456"]}
).label(labels, true)
.hover(fin, fout)
class ConfirmDangerModal
constructor: (form, text) ->
@form = form
$('.js-confirm-text').text(text || '')
$('.js-confirm-danger-input').val('')
$('#modal-confirm-danger').modal('show')
project_path = $('.js-confirm-danger-match').text()
submit = $('.js-confirm-danger-submit')
submit.disable()
$('.js-confirm-danger-input').on 'input', ->
if rstrip($(@).val()) is project_path
submit.enable()
else
submit.disable()
$('.js-confirm-danger-submit').on 'click', =>
@form.submit()
@ConfirmDangerModal = ConfirmDangerModal
...@@ -27,6 +27,8 @@ class Dispatcher ...@@ -27,6 +27,8 @@ class Dispatcher
new ZenMode() new ZenMode()
when 'projects:milestones:show' when 'projects:milestones:show'
new Milestone() new Milestone()
when 'projects:milestones:new'
new ZenMode()
when 'projects:issues:new','projects:issues:edit' when 'projects:issues:new','projects:issues:edit'
GitLab.GfmAutoComplete.setup() GitLab.GfmAutoComplete.setup()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
...@@ -87,6 +89,7 @@ class Dispatcher ...@@ -87,6 +89,7 @@ class Dispatcher
when 'wikis' when 'wikis'
new Wikis() new Wikis()
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
new ZenMode()
when 'snippets', 'labels', 'graphs' when 'snippets', 'labels', 'graphs'
shortcut_handler = new ShortcutsNavigation() shortcut_handler = new ShortcutsNavigation()
when 'team_members', 'deploy_keys', 'hooks', 'services', 'protected_branches' when 'team_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
......
...@@ -10,6 +10,5 @@ class Flash ...@@ -10,6 +10,5 @@ class Flash
flash.click -> $(@).fadeOut() flash.click -> $(@).fadeOut()
flash.show() flash.show()
setTimeout (-> flash.fadeOut()), 5000
@Flash = Flash @Flash = Flash
class GroupMembers class GroupMembers
constructor: -> constructor: ->
$('li.users_group').bind 'ajax:success', -> $('li.group_member').bind 'ajax:success', ->
$(this).fadeOut() $(this).fadeOut()
@GroupMembers = GroupMembers @GroupMembers = GroupMembers
......
...@@ -6,4 +6,14 @@ class Issue ...@@ -6,4 +6,14 @@ class Issue
$(".issue-box .inline-update").on "change", "#issue_assignee_id", -> $(".issue-box .inline-update").on "change", "#issue_assignee_id", ->
$(this).submit() $(this).submit()
if $("a.btn-close").length
$("li.task-list-item input:checkbox").prop("disabled", false)
$(".task-list-item input:checkbox").on(
"click"
null
"issue"
updateTaskState
)
@Issue = Issue @Issue = Issue
...@@ -7,18 +7,18 @@ $(document).ready -> ...@@ -7,18 +7,18 @@ $(document).ready ->
divHover = "<div class=\"div-dropzone-hover\"></div>" divHover = "<div class=\"div-dropzone-hover\"></div>"
divSpinner = "<div class=\"div-dropzone-spinner\"></div>" divSpinner = "<div class=\"div-dropzone-spinner\"></div>"
divAlert = "<div class=\"" + alertClass + "\"></div>" divAlert = "<div class=\"" + alertClass + "\"></div>"
iconPicture = "<i class=\"icon-picture div-dropzone-icon\"></i>" iconPicture = "<i class=\"fa fa-picture-o div-dropzone-icon\"></i>"
iconSpinner = "<i class=\"icon-spinner icon-spin div-dropzone-icon\"></i>" iconSpinner = "<i class=\"fa fa-spinner fa-spin div-dropzone-icon\"></i>"
btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>" btnAlert = "<button type=\"button\"" + alertAttr + ">&times;</button>"
project_image_path_upload = window.project_image_path_upload or null project_image_path_upload = window.project_image_path_upload or null
$("textarea.markdown-area").wrap "<div class=\"div-dropzone\"></div>" $("textarea.markdown-area").wrap "<div class=\"div-dropzone\"></div>"
$(".div-dropzone").parent().addClass "div-dropzone-wrapper" $(".div-dropzone").parent().addClass "div-dropzone-wrapper"
$(".div-dropzone").append divHover $(".div-dropzone").append divHover
$(".div-dropzone-hover").append iconPicture $(".div-dropzone-hover").append iconPicture
$(".div-dropzone").append divSpinner $(".div-dropzone").append divSpinner
$(".div-dropzone-spinner").append iconSpinner $(".div-dropzone-spinner").append iconSpinner
$(".div-dropzone-spinner").css $(".div-dropzone-spinner").css
"opacity": 0 "opacity": 0
...@@ -27,12 +27,12 @@ $(document).ready -> ...@@ -27,12 +27,12 @@ $(document).ready ->
dropzone = $(".div-dropzone").dropzone( dropzone = $(".div-dropzone").dropzone(
url: project_image_path_upload url: project_image_path_upload
dictDefaultMessage: "" dictDefaultMessage: ""
clickable: false clickable: true
paramName: "markdown_img" paramName: "markdown_img"
maxFilesize: 10 maxFilesize: 10
uploadMultiple: false uploadMultiple: false
acceptedFiles: "image/jpg,image/jpeg,image/gif,image/png" acceptedFiles: "image/jpg,image/jpeg,image/gif,image/png"
headers: headers:
"X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content") "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content")
previewContainer: false previewContainer: false
...@@ -91,7 +91,7 @@ $(document).ready -> ...@@ -91,7 +91,7 @@ $(document).ready ->
handlePaste = (e) -> handlePaste = (e) ->
e.preventDefault() e.preventDefault()
my_event = e.originalEvent my_event = e.originalEvent
if my_event.clipboardData and my_event.clipboardData.items if my_event.clipboardData and my_event.clipboardData.items
processItem(my_event) processItem(my_event)
...@@ -115,7 +115,7 @@ $(document).ready -> ...@@ -115,7 +115,7 @@ $(document).ready ->
return item return item
i++ i++
return false return false
pasteText = (text) -> pasteText = (text) ->
caretStart = $(child)[0].selectionStart caretStart = $(child)[0].selectionStart
caretEnd = $(child)[0].selectionEnd caretEnd = $(child)[0].selectionEnd
...@@ -126,12 +126,12 @@ $(document).ready -> ...@@ -126,12 +126,12 @@ $(document).ready ->
$(child).val beforeSelection + text + afterSelection $(child).val beforeSelection + text + afterSelection
$(".markdown-area").trigger "input" $(".markdown-area").trigger "input"
getFilename = (e) -> getFilename = (e) ->
if window.clipboardData and window.clipboardData.getData if window.clipboardData and window.clipboardData.getData
value = window.clipboardData.getData("Text") value = window.clipboardData.getData("Text")
else if e.clipboardData and e.clipboardData.getData else if e.clipboardData and e.clipboardData.getData
value = e.clipboardData.getData("text/plain") value = e.clipboardData.getData("text/plain")
value = value.split("\r") value = value.split("\r")
value.first() value.first()
...@@ -154,7 +154,7 @@ $(document).ready -> ...@@ -154,7 +154,7 @@ $(document).ready ->
success: (e, textStatus, response) -> success: (e, textStatus, response) ->
insertToTextArea(filename, formatLink(response.responseJSON.link)) insertToTextArea(filename, formatLink(response.responseJSON.link))
error: (response) -> error: (response) ->
showError(response.responseJSON.message) showError(response.responseJSON.message)
...@@ -190,7 +190,7 @@ $(document).ready -> ...@@ -190,7 +190,7 @@ $(document).ready ->
$(".markdown-selector").click (e) -> $(".markdown-selector").click (e) ->
e.preventDefault() e.preventDefault()
$(@).closest(".div-dropzone-wrapper").find(".div-dropzone").click() $(@).closest('.gfm-form').find('.div-dropzone').click()
return return
return return
...@@ -15,8 +15,10 @@ class MergeRequest ...@@ -15,8 +15,10 @@ class MergeRequest
modal = $('#modal_merge_info').modal(show: false) modal = $('#modal_merge_info').modal(show: false)
disableButtonIfEmptyField '#merge_commit_message', '.accept_merge_request' disableButtonIfEmptyField '#commit_message', '.accept_merge_request'
if $("a.btn-close").length
$("li.task-list-item input:checkbox").prop("disabled", false)
# Local jQuery finder # Local jQuery finder
$: (selector) -> $: (selector) ->
...@@ -40,6 +42,8 @@ class MergeRequest ...@@ -40,6 +42,8 @@ class MergeRequest
if @opts.ci_enable if @opts.ci_enable
$.get @opts.url_to_ci_check, (data) => $.get @opts.url_to_ci_check, (data) =>
this.showCiState data.status this.showCiState data.status
if data.coverage
this.showCiCoverage data.coverage
, 'json' , 'json'
bindEvents: -> bindEvents: ->
...@@ -70,6 +74,13 @@ class MergeRequest ...@@ -70,6 +74,13 @@ class MergeRequest
this.$('.remove_source_branch_in_progress').hide() this.$('.remove_source_branch_in_progress').hide()
this.$('.remove_source_branch_widget.failed').show() this.$('.remove_source_branch_widget.failed').show()
$(".task-list-item input:checkbox").on(
"click"
null
"merge_request"
updateTaskState
)
activateTab: (action) -> activateTab: (action) ->
this.$('.merge-request-tabs li').removeClass 'active' this.$('.merge-request-tabs li').removeClass 'active'
this.$('.tab-content').hide() this.$('.tab-content').hide()
...@@ -94,15 +105,11 @@ class MergeRequest ...@@ -94,15 +105,11 @@ class MergeRequest
else else
$('.ci_widget.ci-error').show() $('.ci_widget.ci-error').show()
switch state showCiCoverage: (coverage) ->
when "success" cov_html = $('<span>')
$('.mr-state-widget').addClass("panel-success") cov_html.addClass('ci-coverage')
when "failed" cov_html.text('Coverage ' + coverage + '%')
$('.mr-state-widget').addClass("panel-danger") $('.ci_widget:visible').append(cov_html)
when "running", "pending"
$('.mr-state-widget').addClass("panel-warning")
loadDiff: (event) -> loadDiff: (event) ->
$.ajax $.ajax
......
...@@ -6,6 +6,7 @@ class Notes ...@@ -6,6 +6,7 @@ class Notes
@notes_url = gon.relative_url_root + @notes_url if gon.relative_url_root? @notes_url = gon.relative_url_root + @notes_url if gon.relative_url_root?
@note_ids = note_ids @note_ids = note_ids
@last_fetched_at = last_fetched_at @last_fetched_at = last_fetched_at
@noteable_url = document.URL
@initRefresh() @initRefresh()
@setupMainTargetNoteForm() @setupMainTargetNoteForm()
@cleanBinding() @cleanBinding()
...@@ -95,7 +96,8 @@ class Notes ...@@ -95,7 +96,8 @@ class Notes
, 15000 , 15000
refresh: -> refresh: ->
@getContent() unless document.hidden unless document.hidden or (@noteable_url != document.URL)
@getContent()
getContent: -> getContent: ->
$.ajax $.ajax
......
...@@ -51,3 +51,12 @@ $ -> ...@@ -51,3 +51,12 @@ $ ->
$(@).toggleClass('on').find('.count').html(data.star_count) $(@).toggleClass('on').find('.count').html(data.star_count)
.on 'ajax:error', (e, xhr, status, error) -> .on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert') new Flash('Star toggle failed. Try again later.', 'alert')
$("a[data-toggle='tab']").on "shown.bs.tab", (e) ->
$.cookie "default_view", $(e.target).attr("href")
defaultView = $.cookie("default_view")
if defaultView
$("a[href=" + defaultView + "]").tab "show"
else
$("a[data-toggle='tab']:first").tab "show"
...@@ -24,22 +24,7 @@ class window.ContributorsStatGraph ...@@ -24,22 +24,7 @@ class window.ContributorsStatGraph
class: 'graph-author-commits-count' class: 'graph-author-commits-count'
}) })
commits.text(author.commits + " commits") commits.text(author.commits + " commits")
additions = $('<span/>', {
class: 'graph-additions'
})
additions.text(author.additions + " ++")
deletions = $('<span/>', {
class: 'graph-deletions'
})
deletions.text(author.deletions + " --")
$('<span/>').append(commits) $('<span/>').append(commits)
.append(" / ")
.append(additions)
.append(" / ")
.append(deletions)
create_author_header: (author) -> create_author_header: (author) ->
list_item = $('<li/>', { list_item = $('<li/>', {
......
...@@ -90,4 +90,4 @@ window.ContributorsStatGraphUtil = ...@@ -90,4 +90,4 @@ window.ContributorsStatGraphUtil =
true true
else else
false false
...@@ -32,6 +32,8 @@ class @ZenMode ...@@ -32,6 +32,8 @@ class @ZenMode
@active_zen_area = @active_checkbox.parent().find('textarea') @active_zen_area = @active_checkbox.parent().find('textarea')
@active_zen_area.focus() @active_zen_area.focus()
window.location.hash = ZenMode.fullscreen_prefix + @active_checkbox.prop('id') window.location.hash = ZenMode.fullscreen_prefix + @active_checkbox.prop('id')
# Disable dropzone in ZEN mode
Dropzone.forElement('.div-dropzone').disable()
exitZenMode: => exitZenMode: =>
if @active_zen_area isnt null if @active_zen_area isnt null
...@@ -41,6 +43,8 @@ class @ZenMode ...@@ -41,6 +43,8 @@ class @ZenMode
@active_checkbox = null @active_checkbox = null
window.location.hash = '' window.location.hash = ''
window.scrollTo(window.pageXOffset, @scroll_position) window.scrollTo(window.pageXOffset, @scroll_position)
# Enable dropzone when leaving ZEN mode
Dropzone.forElement('.div-dropzone').enable()
checkboxFromLocationHash: (e) -> checkboxFromLocationHash: (e) ->
id = $.trim(window.location.hash.replace('#' + ZenMode.fullscreen_prefix, '')) id = $.trim(window.location.hash.replace('#' + ZenMode.fullscreen_prefix, ''))
......
...@@ -356,3 +356,6 @@ table { ...@@ -356,3 +356,6 @@ table {
font-size: 42px; font-size: 42px;
} }
.task-status {
margin-left: 10px;
}
...@@ -60,7 +60,6 @@ ...@@ -60,7 +60,6 @@
.highlight { .highlight {
margin-bottom: 9px; margin-bottom: 9px;
@include border-radius(4px);
> pre { > pre {
margin: 0; margin: 0;
......
.flash-container { .flash-container {
display: none;
cursor: pointer; cursor: pointer;
margin: 0; margin: 0;
text-align: center;
color: #fff;
font-size: 14px; font-size: 14px;
position: fixed;
bottom: 0;
width: 100%; width: 100%;
opacity: 0.8;
z-index: 100; z-index: 100;
.flash-notice { .flash-notice {
background: #49C; @extend .alert;
padding: 10px; @extend .alert-info;
text-shadow: 0 1px 1px #178;
} }
.flash-alert { .flash-alert {
background: #C67; @extend .alert;
text-shadow: 0 1px 1px #945; @extend .alert-danger;
padding: 10px;
} }
} }
textarea {
resize: vertical;
}
input[type='search'].search-text-input { input[type='search'].search-text-input {
background-image: image-url("icon-search.png"); background-image: image-url("icon-search.png");
background-repeat: no-repeat; background-repeat: no-repeat;
......
/**
* Styles that apply to all GFM related forms.
*/
.issue-form, .merge-request-form, .wiki-form {
.description {
height: 20em;
}
}
.wiki-form {
.description {
height: 26em;
}
}
.milestone-form {
.description {
height: 14em;
}
}
\ No newline at end of file
/**
* Styles that apply to both issues and merge requests.
*/
.issue-form, .merge-request-form {
.description {
height: 20em;
}
}
...@@ -8,43 +8,50 @@ ...@@ -8,43 +8,50 @@
*/ */
.issue-box { .issue-box {
color: #666; color: #555;
margin:20px 0; margin:20px 0;
background: #FFF; background: $box_bg;
border: 1px solid #EEE; @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05));
&.issue-box-closed { &.issue-box-closed {
border-color: $border_danger;
.state { .state {
background-color: #F3CECE;
border-color: $border_danger;
}
.state-label {
background-color: $bg_danger; background-color: $bg_danger;
color: #FFF; color: #FFF;
border-color: $border_danger;
} }
} }
&.issue-box-merged { &.issue-box-merged {
border-color: $border_primary;
.state { .state {
background-color: #B7CEE7;
border-color: $border_primary;
}
.state-label {
background-color: $bg_primary; background-color: $bg_primary;
color: #FFF; color: #FFF;
border-color: $border_primary;
} }
} }
&.issue-box-open { &.issue-box-open {
border-color: $border_success;
.state { .state {
border-color: $border_success; background-color: #D6F1D7;
border-color: $bg_success;
}
.state-label {
background-color: $bg_success; background-color: $bg_success;
color: #FFF; color: #FFF;
} }
} }
&.issue-box-expired { &.issue-box-expired {
border-color: #cea61b;
.state { .state {
background-color: #EEE9B3;
border-color: #faebcc; border-color: #faebcc;
}
.state-label {
background: #cea61b; background: #cea61b;
color: #FFF; color: #FFF;
} }
...@@ -55,8 +62,7 @@ ...@@ -55,8 +62,7 @@
} }
.state { .state {
border-bottom: 1px solid #DDD; background-color: #f9f9f9;
padding: 10px 15px;
} }
.title { .title {
...@@ -104,12 +110,13 @@ ...@@ -104,12 +110,13 @@
font-size: 14px; font-size: 14px;
float: left; float: left;
font-weight: bold; font-weight: bold;
padding: 10px 15px;
} }
.creator { .creator {
float: right; float: right;
padding: 10px 15px;
a { a {
color: #FFF;
text-decoration: underline; text-decoration: underline;
} }
} }
......
...@@ -122,3 +122,7 @@ ul.bordered-list { ...@@ -122,3 +122,7 @@ ul.bordered-list {
} }
} }
} }
li.task-list-item {
list-style-type: none;
}
...@@ -124,7 +124,7 @@ $list-group-active-bg: $bg_primary; ...@@ -124,7 +124,7 @@ $list-group-active-bg: $bg_primary;
color: #888; color: #888;
text-shadow: 0 1px 1px #fff; text-shadow: 0 1px 1px #fff;
} }
i[class^="icon-"] { i[class~="fa"] {
line-height: 14px; line-height: 14px;
} }
} }
...@@ -233,8 +233,8 @@ $list-group-active-bg: $bg_primary; ...@@ -233,8 +233,8 @@ $list-group-active-bg: $bg_primary;
} }
.form-actions { .form-actions {
margin-bottom: 0; margin: -15px;
background: #FFF; margin-top: 18px;
} }
} }
...@@ -262,53 +262,33 @@ $list-group-active-bg: $bg_primary; ...@@ -262,53 +262,33 @@ $list-group-active-bg: $bg_primary;
} }
.panel-danger { .panel-danger {
border-color: $border_danger; @include panel-colored;
.panel-heading { .panel-heading {
color: #ffffff; color: $border_danger;
background-color: $bg_danger;
border-color: $border_danger; border-color: $border_danger;
a {
color: #FFF;
text-decoration: underline;
}
} }
} }
.panel-success { .panel-success {
border-color: $border_success; @include panel-colored;
.panel-heading { .panel-heading {
color: #ffffff; color: $border_success;
background-color: $bg_success;
border-color: $border_success; border-color: $border_success;
a {
color: #FFF;
text-decoration: underline;
}
} }
} }
.panel-primary { .panel-primary {
border-color: $border_primary; @include panel-colored;
.panel-heading { .panel-heading {
color: #ffffff; color: $border_primary;
background-color: $bg_primary;
border-color: $border_primary; border-color: $border_primary;
a {
color: #FFF;
text-decoration: underline;
}
} }
} }
.panel-warning { .panel-warning {
border-color: $border_warning; @include panel-colored;
.panel-heading { .panel-heading {
color: #ffffff; color: $border_warning;
background-color: $bg_warning;
border-color: $border_warning; border-color: $border_warning;
a {
color: #FFF;
text-decoration: underline;
}
} }
} }
.white { .white {
background-color: #fff;
.line.hll { .line.hll {
background: #FFA; background: #FFA;
} }
.highlight{
border-left: 1px solid #eee;
}
pre { pre {
background-color: #fff; background-color: #fff;
color: #333; color: #333;
...@@ -179,8 +173,16 @@ ...@@ -179,8 +173,16 @@
@include box-shadow(0 5px 15px #000); @include box-shadow(0 5px 15px #000);
} }
.wiki, .note-body { .file-content {
.highlight { &.code .white {
border: 1px solid #DDD; .highlight {
border-left: 1px solid #eee;
}
}
&.wiki .white {
.highlight, pre, .hljs {
background: #F9F9F9;
}
} }
} }
...@@ -132,3 +132,14 @@ ...@@ -132,3 +132,14 @@
white-space: nowrap; white-space: nowrap;
max-width: $max_width; max-width: $max_width;
} }
@mixin panel-colored {
border: none;
background: $box_bg;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
.panel-heading {
font-weight: bold;
background-color: $box_bg;
}
}
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
*/ */
$style_color: #474D57; $style_color: #474D57;
$hover: #FFECDB; $hover: #FFECDB;
$box_bg: #F9F9F9;
/* /*
* Link colors * Link colors
......
...@@ -244,7 +244,6 @@ li.commit { ...@@ -244,7 +244,6 @@ li.commit {
font-family: inherit; font-family: inherit;
padding-left: $left; padding-left: $left;
position: relative; position: relative;
resize: vertical;
z-index: 2; z-index: 2;
} }
} }
...@@ -104,15 +104,63 @@ ...@@ -104,15 +104,63 @@
} }
.mr-state-widget { .mr-state-widget {
.panel-body { background: $box_bg;
margin-bottom: 20px;
@include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09));
.ci_widget {
padding: 10px 15px;
font-size: 15px;
border-bottom: 1px solid #BBB;
color: #777;
&.ci-success {
color: $bg_success;
border-color: $border_success;
}
&.ci-pending {
color: #548;
border-color: #548;
}
&.ci-running {
color: $bg_warning;
border-color: $border_warning;
}
&.ci-failed {
color: $bg_danger;
border-color: $border_danger;
}
&.ci-error {
color: $bg_danger;
border-color: $border_danger;
}
}
.mr-widget-body {
padding: 10px 15px;
h4 { h4 {
margin-top: 0px; font-size: 20px;
font-weight: normal;
} }
p:last-child { p:last-child {
margin-bottom: 0; margin-bottom: 0;
} }
} }
.mr-widget-footer {
padding: 10px 15px;
border-top: 1px solid #EEE;
}
.ci-coverage {
float: right;
}
} }
.merge-request-show-labels .label { .merge-request-show-labels .label {
......
...@@ -8,8 +8,6 @@ ...@@ -8,8 +8,6 @@
ul { ul {
padding: 0; padding: 0;
margin: auto; margin: auto;
height: 40px;
overflow: hidden;
.count { .count {
font-weight: normal; font-weight: normal;
display: inline-block; display: inline-block;
...@@ -37,53 +35,28 @@ ...@@ -37,53 +35,28 @@
a { a {
color: $link_color; color: $link_color;
font-weight: bold; font-weight: bold;
&:after { border-bottom: 3px solid $link_color;
content: '';
display: block;
position: relative;
bottom: -1px;
border-color: $link_color;
border-style: solid;
border-width: 2px;
}
} }
} }
&:hover { &:hover {
a { a {
color: $link_hover_color; color: $link_hover_color;
&:after { border-bottom: 3px solid $link_hover_color;
content: '';
display: block;
position: relative;
bottom: -1px;
border-color: $link_hover_color;
border-style: solid;
border-width: 2px;
}
}
}
&.home {
a {
i {
font-size: 20px;
position: relative;
top: 4px;
}
} }
} }
} }
a { a {
display: block; display: block;
text-align: center; text-align: center;
font-weight: 500; font-weight: bold;
height: 38px; height: 42px;
line-height: 34px; line-height: 39px;
color: #777; color: #777;
text-shadow: 0 1px 1px white; text-shadow: 0 1px 1px white;
text-decoration: none; text-decoration: none;
padding-top: 2px; overflow: hidden;
margin-bottom: -1px;
} }
} }
......
...@@ -19,6 +19,10 @@ ul.notes { ...@@ -19,6 +19,10 @@ ul.notes {
@extend .cgray; @extend .cgray;
padding-bottom: 15px; padding-bottom: 15px;
a:hover {
text-decoration: none;
}
.avatar { .avatar {
float: left; float: left;
margin-right: 10px; margin-right: 10px;
...@@ -115,8 +119,7 @@ ul.notes { ...@@ -115,8 +119,7 @@ ul.notes {
display: none; display: none;
float: right; float: right;
[class^="icon-"], [class~="fa"] {
[class*="icon-"] {
font-size: 16px; font-size: 16px;
line-height: 16px; line-height: 16px;
vertical-align: middle; vertical-align: middle;
...@@ -143,8 +146,14 @@ ul.notes { ...@@ -143,8 +146,14 @@ ul.notes {
*/ */
.diff-file tr.line_holder { .diff-file tr.line_holder {
@mixin show-add-diff-note {
filter: alpha(opacity=100);
opacity: 1.0;
}
.add-diff-note { .add-diff-note {
background: image-url("diff_note_add.png") no-repeat left 0; background: image-url("diff_note_add.png") no-repeat left 0;
border: none;
height: 22px; height: 22px;
margin-left: -65px; margin-left: -65px;
position: absolute; position: absolute;
...@@ -156,8 +165,7 @@ ul.notes { ...@@ -156,8 +165,7 @@ ul.notes {
filter: alpha(opacity=0); filter: alpha(opacity=0);
&:hover { &:hover {
opacity: 1.0; @include show-add-diff-note;
filter: alpha(opacity=100);
} }
} }
...@@ -166,8 +174,7 @@ ul.notes { ...@@ -166,8 +174,7 @@ ul.notes {
background: $hover !important; background: $hover !important;
.add-diff-note { .add-diff-note {
opacity: 1.0; @include show-add-diff-note;
filter: alpha(opacity=100);
} }
} }
} }
......
...@@ -48,6 +48,10 @@ ...@@ -48,6 +48,10 @@
float: right; float: right;
margin-left: 20px; margin-left: 20px;
a:hover {
text-decoration: none;
}
.count { .count {
margin-left: 5px; margin-left: 5px;
} }
......
...@@ -8,7 +8,7 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -8,7 +8,7 @@ class Admin::GroupsController < Admin::ApplicationController
end end
def show def show
@members = @group.members.order("group_access DESC").page(params[:members_page]).per(30) @members = @group.members.order("access_level DESC").page(params[:members_page]).per(30)
@projects = @group.projects.page(params[:projects_page]).per(30) @projects = @group.projects.page(params[:projects_page]).per(30)
end end
...@@ -40,7 +40,7 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -40,7 +40,7 @@ class Admin::GroupsController < Admin::ApplicationController
end end
def project_teams_update def project_teams_update
@group.add_users(params[:user_ids].split(','), params[:group_access]) @group.add_users(params[:user_ids].split(','), params[:access_level])
redirect_to [:admin, @group], notice: 'Users were successfully added.' redirect_to [:admin, @group], notice: 'Users were successfully added.'
end end
......
...@@ -16,10 +16,10 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -16,10 +16,10 @@ class Admin::ProjectsController < Admin::ApplicationController
def show def show
if @group if @group
@group_members = @group.members.order("group_access DESC").page(params[:group_members_page]).per(30) @group_members = @group.members.order("access_level DESC").page(params[:group_members_page]).per(30)
end end
@project_members = @project.users_projects.page(params[:project_members_page]).per(30) @project_members = @project.project_members.page(params[:project_members_page]).per(30)
end end
def transfer def transfer
......
...@@ -4,6 +4,7 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -4,6 +4,7 @@ class Admin::UsersController < Admin::ApplicationController
def index def index
@users = User.filter(params[:filter]) @users = User.filter(params[:filter])
@users = @users.search(params[:name]) if params[:name].present? @users = @users.search(params[:name]) if params[:name].present?
@users = @users.sort(@sort = params[:sort])
@users = @users.alphabetically.page(params[:page]) @users = @users.alphabetically.page(params[:page])
end end
......
...@@ -13,7 +13,7 @@ class ApplicationController < ActionController::Base ...@@ -13,7 +13,7 @@ class ApplicationController < ActionController::Base
before_filter :configure_permitted_parameters, if: :devise_controller? before_filter :configure_permitted_parameters, if: :devise_controller?
before_filter :require_email, unless: :devise_controller? before_filter :require_email, unless: :devise_controller?
protect_from_forgery protect_from_forgery with: :exception
helper_method :abilities, :can? helper_method :abilities, :can?
...@@ -62,7 +62,7 @@ class ApplicationController < ActionController::Base ...@@ -62,7 +62,7 @@ class ApplicationController < ActionController::Base
end end
end end
def after_sign_in_path_for resource def after_sign_in_path_for(resource)
if resource.is_a?(User) && resource.respond_to?(:blocked?) && resource.blocked? if resource.is_a?(User) && resource.respond_to?(:blocked?) && resource.blocked?
sign_out resource sign_out resource
flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it." flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it."
......
class ConfirmationsController < Devise::ConfirmationsController
protected
def after_confirmation_path_for(resource_name, resource)
if signed_in?(resource_name)
signed_in_root_path(resource)
else
sign_in(resource)
if signed_in?(resource_name)
signed_in_root_path(resource)
else
new_session_path(resource_name)
end
end
end
end
class UsersGroupsController < ApplicationController class Groups::GroupMembersController < ApplicationController
before_filter :group before_filter :group
# Authorize # Authorize
...@@ -7,18 +7,19 @@ class UsersGroupsController < ApplicationController ...@@ -7,18 +7,19 @@ class UsersGroupsController < ApplicationController
layout 'group' layout 'group'
def create def create
@group.add_users(params[:user_ids].split(','), params[:group_access]) @group.add_users(params[:user_ids].split(','), params[:access_level])
redirect_to members_group_path(@group), notice: 'Users were successfully added.' redirect_to members_group_path(@group), notice: 'Users were successfully added.'
end end
def update def update
@member = @group.users_groups.find(params[:id]) @member = @group.group_members.find(params[:id])
@member.update_attributes(member_params) @member.update_attributes(member_params)
end end
def destroy def destroy
@users_group = @group.users_groups.find(params[:id]) @users_group = @group.group_members.find(params[:id])
if can?(current_user, :destroy, @users_group) # May fail if last owner. if can?(current_user, :destroy, @users_group) # May fail if last owner.
@users_group.destroy @users_group.destroy
respond_to do |format| respond_to do |format|
...@@ -43,6 +44,6 @@ class UsersGroupsController < ApplicationController ...@@ -43,6 +44,6 @@ class UsersGroupsController < ApplicationController
end end
def member_params def member_params
params.require(:users_group).permit(:group_access, :user_id) params.require(:group_member).permit(:access_level, :user_id)
end end
end end
...@@ -65,15 +65,15 @@ class GroupsController < ApplicationController ...@@ -65,15 +65,15 @@ class GroupsController < ApplicationController
def members def members
@project = group.projects.find(params[:project_id]) if params[:project_id] @project = group.projects.find(params[:project_id]) if params[:project_id]
@members = group.users_groups @members = group.group_members
if params[:search].present? if params[:search].present?
users = group.users.search(params[:search]).to_a users = group.users.search(params[:search]).to_a
@members = @members.where(user_id: users) @members = @members.where(user_id: users)
end end
@members = @members.order('group_access DESC').page(params[:page]).per(50) @members = @members.order('access_level DESC').page(params[:page]).per(50)
@users_group = UsersGroup.new @users_group = GroupMember.new
end end
def edit def edit
......
...@@ -15,15 +15,17 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -15,15 +15,17 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
error.to_s.humanize if error error.to_s.humanize if error
end end
# We only find ourselves here
# if the authentication to LDAP was successful.
def ldap def ldap
# We only find ourselves here @user = Gitlab::LDAP::User.new(oauth)
# if the authentication to LDAP was successful. @user.save if @user.changed? # will also save new users
@user = Gitlab::LDAP::User.find_or_create(oauth) gl_user = @user.gl_user
@user.remember_me = true if @user.persisted? gl_user.remember_me = true if @user.persisted?
# Do additional LDAP checks for the user filter and EE features # Do additional LDAP checks for the user filter and EE features
if Gitlab::LDAP::Access.allowed?(@user) if @user.allowed?
sign_in_and_redirect(@user) sign_in_and_redirect(gl_user)
else else
flash[:alert] = "Access denied for your LDAP account." flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path redirect_to new_user_session_path
...@@ -46,26 +48,28 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -46,26 +48,28 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
current_user.save current_user.save
redirect_to profile_path redirect_to profile_path
else else
@user = Gitlab::OAuth::User.find(oauth) @user = Gitlab::OAuth::User.new(oauth)
@user.save
# Create user if does not exist # Only allow properly saved users to login.
# and allow_single_sign_on is true if @user.persisted? && @user.valid?
if Gitlab.config.omniauth['allow_single_sign_on'] && !@user sign_in_and_redirect(@user.gl_user)
@user, errors = Gitlab::OAuth::User.create(oauth)
end
if @user && !errors
sign_in_and_redirect(@user)
else else
if errors error_message =
error_message = errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ") if @user.gl_user.errors.any?
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return @user.gl_user.errors.map do |attribute, message|
else "#{attribute} #{message}"
flash[:notice] = "There's no such user!" end.join(", ")
end else
redirect_to new_user_session_path ''
end
redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return
end end
end end
rescue StandardError
flash[:notice] = "There's no such user!"
redirect_to new_user_session_path
end end
def oauth def oauth
......
...@@ -2,11 +2,11 @@ class Profiles::GroupsController < ApplicationController ...@@ -2,11 +2,11 @@ class Profiles::GroupsController < ApplicationController
layout "profile" layout "profile"
def index def index
@user_groups = current_user.users_groups.page(params[:page]).per(20) @user_groups = current_user.group_members.page(params[:page]).per(20)
end end
def leave def leave
@users_group = group.users_groups.where(user_id: current_user.id).first @users_group = group.group_members.where(user_id: current_user.id).first
if can?(current_user, :destroy, @users_group) if can?(current_user, :destroy, @users_group)
@users_group.destroy @users_group.destroy
redirect_to(profile_groups_path, info: "You left #{group.name} group.") redirect_to(profile_groups_path, info: "You left #{group.name} group.")
......
...@@ -3,8 +3,8 @@ class Profiles::NotificationsController < ApplicationController ...@@ -3,8 +3,8 @@ class Profiles::NotificationsController < ApplicationController
def show def show
@notification = current_user.notification @notification = current_user.notification
@users_projects = current_user.users_projects @project_members = current_user.project_members
@users_groups = current_user.users_groups @group_members = current_user.group_members
end end
def update def update
...@@ -14,13 +14,13 @@ class Profiles::NotificationsController < ApplicationController ...@@ -14,13 +14,13 @@ class Profiles::NotificationsController < ApplicationController
current_user.notification_level = params[:notification_level] current_user.notification_level = params[:notification_level]
current_user.save current_user.save
elsif type == 'group' elsif type == 'group'
users_group = current_user.users_groups.find(params[:notification_id]) users_group = current_user.group_members.find(params[:notification_id])
users_group.notification_level = params[:notification_level] users_group.notification_level = params[:notification_level]
users_group.save users_group.save
else else
users_project = current_user.users_projects.find(params[:notification_id]) project_member = current_user.project_members.find(params[:notification_id])
users_project.notification_level = params[:notification_level] project_member.notification_level = params[:notification_level]
users_project.save project_member.save
end end
end end
end end
...@@ -20,7 +20,7 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -20,7 +20,7 @@ class Projects::BlobController < Projects::ApplicationController
flash[:notice] = "Your changes have been successfully committed" flash[:notice] = "Your changes have been successfully committed"
redirect_to project_tree_path(@project, @ref) redirect_to project_tree_path(@project, @ref)
else else
flash[:alert] = result[:error] flash[:alert] = result[:message]
render :show render :show
end end
end end
......
...@@ -17,10 +17,8 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -17,10 +17,8 @@ class Projects::BranchesController < Projects::ApplicationController
end end
def create def create
result = CreateBranchService.new.execute(project, result = CreateBranchService.new(project, current_user).
params[:branch_name], execute(params[:branch_name], params[:ref])
params[:ref],
current_user)
if result[:status] == :success if result[:status] == :success
@branch = result[:branch] @branch = result[:branch]
redirect_to project_tree_path(@project, @branch.name) redirect_to project_tree_path(@project, @branch.name)
...@@ -31,7 +29,7 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -31,7 +29,7 @@ class Projects::BranchesController < Projects::ApplicationController
end end
def destroy def destroy
DeleteBranchService.new.execute(project, params[:id], current_user) DeleteBranchService.new(project, current_user).execute(params[:id])
@branch_name = params[:id] @branch_name = params[:id]
respond_to do |format| respond_to do |format|
......
...@@ -12,20 +12,8 @@ class Projects::CommitController < Projects::ApplicationController ...@@ -12,20 +12,8 @@ class Projects::CommitController < Projects::ApplicationController
return git_not_found! unless @commit return git_not_found! unless @commit
@line_notes = project.notes.for_commit_id(commit.id).inline @line_notes = project.notes.for_commit_id(commit.id).inline
@branches = project.repository.branch_names_contains(commit.id)
@branches = begin @diffs = @commit.diffs
project.repository.branch_names_contains(commit.id)
rescue Grit::Git::GitTimeout
[]
end
begin
@diffs = @commit.diffs
rescue Grit::Git::GitTimeout
@diffs = []
@diff_timeout = true
end
@note = project.build_commit_note(commit) @note = project.build_commit_note(commit)
@notes_count = project.notes.for_commit_id(commit.id).count @notes_count = project.notes.for_commit_id(commit.id).count
@notes = project.notes.for_commit_id(@commit.id).not_inline.fresh @notes = project.notes.for_commit_id(@commit.id).not_inline.fresh
......
...@@ -17,7 +17,7 @@ class Projects::CommitsController < Projects::ApplicationController ...@@ -17,7 +17,7 @@ class Projects::CommitsController < Projects::ApplicationController
group(:commit_id).count group(:commit_id).count
respond_to do |format| respond_to do |format|
format.html # index.html.erb format.html
format.json { pager_json("projects/commits/_commits", @commits.size) } format.json { pager_json("projects/commits/_commits", @commits.size) }
format.atom { render layout: false } format.atom { render layout: false }
end end
......
...@@ -10,7 +10,8 @@ class Projects::EditTreeController < Projects::BaseTreeController ...@@ -10,7 +10,8 @@ class Projects::EditTreeController < Projects::BaseTreeController
end end
def update def update
result = Files::UpdateService.new(@project, current_user, params, @ref, @path).execute result = Files::UpdateService.
new(@project, current_user, params, @ref, @path).execute
if result[:status] == :success if result[:status] == :success
flash[:notice] = "Your changes have been successfully committed" flash[:notice] = "Your changes have been successfully committed"
...@@ -21,7 +22,7 @@ class Projects::EditTreeController < Projects::BaseTreeController ...@@ -21,7 +22,7 @@ class Projects::EditTreeController < Projects::BaseTreeController
redirect_to after_edit_path redirect_to after_edit_path
else else
flash[:alert] = result[:error] flash[:alert] = result[:message]
render :show render :show
end end
end end
......
...@@ -7,19 +7,34 @@ class Projects::GraphsController < Projects::ApplicationController ...@@ -7,19 +7,34 @@ class Projects::GraphsController < Projects::ApplicationController
def show def show
respond_to do |format| respond_to do |format|
format.html format.html
format.js do format.json do
fetch_graph fetch_graph
end end
end end
end end
def commits
@commits = @project.repository.commits(nil, nil, 2000, 0, true)
@commits_graph = Gitlab::Graphs::Commits.new(@commits)
@commits_per_week_days = @commits_graph.commits_per_week_days
@commits_per_time = @commits_graph.commits_per_time
@commits_per_month = @commits_graph.commits_per_month
end
private private
def fetch_graph def fetch_graph
@log = @project.repository.graph_log.to_json @commits = @project.repository.commits(nil, nil, 6000, 0, true)
@success = true
rescue => ex
@log = [] @log = []
@success = false
@commits.each do |commit|
@log << {
author_name: commit.author_name.force_encoding('UTF-8'),
author_email: commit.author_email.force_encoding('UTF-8'),
date: commit.committed_date.strftime("%Y-%m-%d")
}
end
render json: @log.to_json
end end
end end
...@@ -28,7 +28,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -28,7 +28,7 @@ class Projects::IssuesController < Projects::ApplicationController
@milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero? @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero?
sort_param = params[:sort] || 'newest' sort_param = params[:sort] || 'newest'
@sort = sort_param.humanize unless sort_param.empty? @sort = sort_param.humanize unless sort_param.empty?
@assignees = User.where(id: @project.issues.pluck(:assignee_id)) @assignees = User.where(id: @project.issues.pluck(:assignee_id)).active
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -152,7 +152,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -152,7 +152,7 @@ class Projects::IssuesController < Projects::ApplicationController
def issue_params def issue_params
params.require(:issue).permit( params.require(:issue).permit(
:title, :assignee_id, :position, :description, :title, :assignee_id, :position, :description,
:milestone_id, :state_event, label_ids: [] :milestone_id, :state_event, :task_num, label_ids: []
) )
end end
end end
...@@ -122,7 +122,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -122,7 +122,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
if @merge_request.open? && @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.should_remove_source_branch = params[:should_remove_source_branch]
@merge_request.automerge!(current_user, params[:merge_commit_message]) @merge_request.automerge!(current_user, params[:commit_message])
@status = true @status = true
else else
@status = false @status = false
...@@ -143,7 +143,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -143,7 +143,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController
def update_branches def update_branches
@target_project = selected_target_project @target_project = selected_target_project
@target_branches = @target_project.repository.branch_names @target_branches = @target_project.repository.branch_names
@target_branches
respond_to do |format| respond_to do |format|
format.js format.js
...@@ -151,8 +150,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -151,8 +150,17 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
def ci_status def ci_status
status = @merge_request.source_project.ci_service.commit_status(merge_request.last_commit.sha) ci_service = @merge_request.source_project.ci_service
response = {status: status} status = ci_service.commit_status(merge_request.last_commit.sha)
if ci_service.respond_to?(:commit_coverage)
coverage = ci_service.commit_coverage(merge_request.last_commit.sha)
end
response = {
status: status,
coverage: coverage
}
render json: response render json: response
end end
...@@ -242,7 +250,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -242,7 +250,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
params.require(:merge_request).permit( params.require(:merge_request).permit(
:title, :assignee_id, :source_project_id, :source_branch, :title, :assignee_id, :source_project_id, :source_branch,
:target_project_id, :target_branch, :milestone_id, :target_project_id, :target_branch, :milestone_id,
:state_event, :description, label_ids: [] :state_event, :description, :task_num, label_ids: []
) )
end end
end end
...@@ -13,7 +13,7 @@ class Projects::NewTreeController < Projects::BaseTreeController ...@@ -13,7 +13,7 @@ class Projects::NewTreeController < Projects::BaseTreeController
flash[:notice] = "Your changes have been successfully committed" flash[:notice] = "Your changes have been successfully committed"
redirect_to project_blob_path(@project, File.join(@ref, file_path)) redirect_to project_blob_path(@project, File.join(@ref, file_path))
else else
flash[:alert] = result[:error] flash[:alert] = result[:message]
render :show render :show
end end
end end
......
...@@ -4,11 +4,6 @@ class Projects::RepositoriesController < Projects::ApplicationController ...@@ -4,11 +4,6 @@ class Projects::RepositoriesController < Projects::ApplicationController
before_filter :authorize_code_access! before_filter :authorize_code_access!
before_filter :require_non_empty_project before_filter :require_non_empty_project
def stats
@stats = Gitlab::Git::Stats.new(@repository.raw, @repository.root_ref)
@graph = @stats.graph
end
def archive def archive
unless can?(current_user, :download_code, @project) unless can?(current_user, :download_code, @project)
render_404 and return render_404 and return
......
...@@ -40,7 +40,8 @@ class Projects::ServicesController < Projects::ApplicationController ...@@ -40,7 +40,8 @@ class Projects::ServicesController < Projects::ApplicationController
def service_params def service_params
params.require(:service).permit( params.require(:service).permit(
:title, :token, :type, :active, :api_key, :subdomain, :title, :token, :type, :active, :api_key, :subdomain,
:room, :recipients, :project_url :room, :recipients, :project_url, :webhook,
:user_key, :device, :priority, :sound
) )
end end
end end
...@@ -17,7 +17,10 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -17,7 +17,10 @@ class Projects::SnippetsController < Projects::ApplicationController
respond_to :html respond_to :html
def index def index
@snippets = @project.snippets.fresh.non_expired @snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_project,
project: @project
})
end end
def new def new
...@@ -88,6 +91,6 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -88,6 +91,6 @@ class Projects::SnippetsController < Projects::ApplicationController
end end
def snippet_params def snippet_params
params.require(:project_snippet).permit(:title, :content, :file_name, :private) params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level)
end end
end end
...@@ -13,9 +13,8 @@ class Projects::TagsController < Projects::ApplicationController ...@@ -13,9 +13,8 @@ class Projects::TagsController < Projects::ApplicationController
end end
def create def create
result = CreateTagService.new.execute(@project, params[:tag_name], result = CreateTagService.new(@project, current_user).
params[:ref], params[:message], execute(params[:tag_name], params[:ref], params[:message])
current_user)
if result[:status] == :success if result[:status] == :success
@tag = result[:tag] @tag = result[:tag]
redirect_to project_tags_path(@project) redirect_to project_tags_path(@project)
......
...@@ -6,17 +6,17 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -6,17 +6,17 @@ class Projects::TeamMembersController < Projects::ApplicationController
def index def index
@group = @project.group @group = @project.group
@users_projects = @project.users_projects.order('project_access DESC') @project_members = @project.project_members.order('access_level DESC')
end end
def new def new
@user_project_relation = project.users_projects.new @user_project_relation = project.project_members.new
end end
def create def create
users = User.where(id: params[:user_ids].split(',')) users = User.where(id: params[:user_ids].split(','))
@project.team << [users, params[:project_access]] @project.team << [users, params[:access_level]]
if params[:redirect_to] if params[:redirect_to]
redirect_to params[:redirect_to] redirect_to params[:redirect_to]
...@@ -26,7 +26,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -26,7 +26,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end end
def update def update
@user_project_relation = project.users_projects.find_by(user_id: member) @user_project_relation = project.project_members.find_by(user_id: member)
@user_project_relation.update_attributes(member_params) @user_project_relation.update_attributes(member_params)
unless @user_project_relation.valid? unless @user_project_relation.valid?
...@@ -36,7 +36,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -36,7 +36,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end end
def destroy def destroy
@user_project_relation = project.users_projects.find_by(user_id: member) @user_project_relation = project.project_members.find_by(user_id: member)
@user_project_relation.destroy @user_project_relation.destroy
respond_to do |format| respond_to do |format|
...@@ -46,7 +46,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -46,7 +46,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
end end
def leave def leave
project.users_projects.find_by(user_id: current_user).destroy project.project_members.find_by(user_id: current_user).destroy
respond_to do |format| respond_to do |format|
format.html { redirect_to :back } format.html { redirect_to :back }
...@@ -69,6 +69,6 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -69,6 +69,6 @@ class Projects::TeamMembersController < Projects::ApplicationController
end end
def member_params def member_params
params.require(:team_member).permit(:user_id, :project_access) params.require(:project_member).permit(:user_id, :access_level)
end end
end end
...@@ -15,11 +15,11 @@ class RegistrationsController < Devise::RegistrationsController ...@@ -15,11 +15,11 @@ class RegistrationsController < Devise::RegistrationsController
super super
end end
def after_sign_up_path_for resource def after_sign_up_path_for(resource)
new_user_session_path new_user_session_path
end end
def after_inactive_sign_up_path_for resource def after_inactive_sign_up_path_for(resource)
new_user_session_path new_user_session_path
end end
......
...@@ -18,6 +18,10 @@ class SessionsController < Devise::SessionsController ...@@ -18,6 +18,10 @@ class SessionsController < Devise::SessionsController
store_location_for(:redirect, redirect_path) store_location_for(:redirect, redirect_path)
end end
if Gitlab.config.ldap.enabled
@ldap_servers = Gitlab::LDAP::Config.servers
end
super super
end end
......
...@@ -9,12 +9,14 @@ class SnippetsController < ApplicationController ...@@ -9,12 +9,14 @@ class SnippetsController < ApplicationController
before_filter :set_title before_filter :set_title
skip_before_filter :authenticate_user!, only: [:index, :user_index, :show]
respond_to :html respond_to :html
layout 'navless' layout :determine_layout
def index def index
@snippets = Snippet.are_public.fresh.non_expired.page(params[:page]).per(20) @snippets = SnippetsFinder.new.execute(current_user, filter: :all).page(params[:page]).per(20)
end end
def user_index def user_index
...@@ -22,22 +24,11 @@ class SnippetsController < ApplicationController ...@@ -22,22 +24,11 @@ class SnippetsController < ApplicationController
render_404 and return unless @user render_404 and return unless @user
@snippets = @user.snippets.fresh.non_expired @snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_user,
if @user == current_user user: @user,
@snippets = case params[:scope] scope: params[:scope]}).
when 'are_public' then page(params[:page]).per(20)
@snippets.are_public
when 'are_private' then
@snippets.are_private
else
@snippets
end
else
@snippets = @snippets.are_public
end
@snippets = @snippets.page(params[:page]).per(20)
if @user == current_user if @user == current_user
render 'current_user_index' render 'current_user_index'
...@@ -95,7 +86,14 @@ class SnippetsController < ApplicationController ...@@ -95,7 +86,14 @@ class SnippetsController < ApplicationController
protected protected
def snippet def snippet
@snippet ||= PersonalSnippet.where('author_id = :user_id or private is false', user_id: current_user.id).find(params[:id]) @snippet ||= if current_user
PersonalSnippet.where("author_id = ? OR visibility_level IN (?)",
current_user.id,
[Snippet::PUBLIC, Snippet::INTERNAL]).
find(params[:id])
else
PersonalSnippet.are_public.find(params[:id])
end
end end
def authorize_modify_snippet! def authorize_modify_snippet!
...@@ -111,6 +109,10 @@ class SnippetsController < ApplicationController ...@@ -111,6 +109,10 @@ class SnippetsController < ApplicationController
end end
def snippet_params def snippet_params
params.require(:personal_snippet).permit(:title, :content, :file_name, :private) params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level)
end
def determine_layout
current_user ? 'navless' : 'public_users'
end end
end end
# Finders # Finders
This type of classes responsible for collectiong items based on different conditions. This type of classes responsible for collection items based on different conditions.
To prevent lookup methods in models like this: To prevent lookup methods in models like this:
```ruby ```ruby
class Project class Project
...@@ -13,10 +13,10 @@ end ...@@ -13,10 +13,10 @@ end
issues = project.issues_for_user_filtered_by(user, params) issues = project.issues_for_user_filtered_by(user, params)
``` ```
Better use this: Better use this:
```ruby ```ruby
issues = IssuesFinder.new.execute(project, user, filter) issues = IssuesFinder.new.execute(project, user, filter)
``` ```
It will help keep models thiner It will help keep models thiner.
...@@ -19,10 +19,8 @@ class ProjectsFinder ...@@ -19,10 +19,8 @@ class ProjectsFinder
# Return ALL group projects # Return ALL group projects
group.projects group.projects
else else
projects_members = UsersProject.where( projects_members = ProjectMember.in_projects(group.projects).
project_id: group.projects, with_user(current_user)
user_id: current_user
)
if projects_members.any? if projects_members.any?
# User is a project member # User is a project member
...@@ -34,7 +32,7 @@ class ProjectsFinder ...@@ -34,7 +32,7 @@ class ProjectsFinder
# #
group.projects.where( group.projects.where(
"projects.id IN (?) OR projects.visibility_level IN (?)", "projects.id IN (?) OR projects.visibility_level IN (?)",
projects_members.pluck(:project_id), projects_members.pluck(:source_id),
Project.public_and_internal_levels Project.public_and_internal_levels
) )
else else
......
class SnippetsFinder
def execute(current_user, params = {})
filter = params[:filter]
case filter
when :all then
snippets(current_user).fresh.non_expired
when :by_user then
by_user(current_user, params[:user], params[:scope])
when :by_project
by_project(current_user, params[:project])
end
end
private
def snippets(current_user)
if current_user
Snippet.public_and_internal
else
# Not authenticated
#
# Return only:
# public snippets
Snippet.are_public
end
end
def by_user(current_user, user, scope)
snippets = user.snippets.fresh.non_expired
return snippets.are_public unless current_user
if user == current_user
case scope
when 'are_internal' then
snippets.are_internal
when 'are_private' then
snippets.are_private
when 'are_public' then
snippets.are_public
else
snippets
end
else
snippets.public_and_internal
end
end
def by_project(current_user, project)
snippets = project.snippets.fresh.non_expired
if current_user
if project.team.member?(current_user.id)
snippets
else
snippets.public_and_internal
end
else
snippets.are_public
end
end
end
...@@ -151,12 +151,6 @@ module ApplicationHelper ...@@ -151,12 +151,6 @@ module ApplicationHelper
sanitize(str, tags: %w(a span)) sanitize(str, tags: %w(a span))
end end
def image_url(source)
# prevent relative_root_path being added twice (it's part of root_url and path_to_image)
root_url.sub(/#{root_path}$/, path_to_image(source))
end
alias_method :url_to_image, :image_url
def body_data_page def body_data_page
path = controller.controller_path.split('/') path = controller.controller_path.split('/')
...@@ -187,13 +181,6 @@ module ApplicationHelper ...@@ -187,13 +181,6 @@ module ApplicationHelper
end end
end end
def first_line(str)
lines = str.split("\n")
line = lines.first
line += "..." if lines.size > 1
line
end
def broadcast_message def broadcast_message
BroadcastMessage.current BroadcastMessage.current
end end
...@@ -242,7 +229,7 @@ module ApplicationHelper ...@@ -242,7 +229,7 @@ module ApplicationHelper
css_class << " hide" unless visible css_class << " hide" unless visible
content_tag :div, class: css_class do content_tag :div, class: css_class do
content_tag(:i, nil, class: 'icon-spinner icon-spin') + text content_tag(:i, nil, class: 'fa fa-spinner fa-spin') + text
end end
end end
...@@ -272,4 +259,16 @@ module ApplicationHelper ...@@ -272,4 +259,16 @@ module ApplicationHelper
super super
end end
def escaped_autolink(text)
auto_link ERB::Util.html_escape(text), link: :urls
end
def promo_host
'about.gitlab.com'
end
def promo_url
'https://' + promo_host
end
end end
...@@ -120,4 +120,8 @@ module CommitsHelper ...@@ -120,4 +120,8 @@ module CommitsHelper
class: 'commit-short-id') class: 'commit-short-id')
end end
end end
def truncate_sha(sha)
Commit.truncate_sha(sha)
end
end end
...@@ -37,40 +37,31 @@ module DashboardHelper ...@@ -37,40 +37,31 @@ module DashboardHelper
end end
def assigned_entities_count(current_user, entity, scope = nil) def assigned_entities_count(current_user, entity, scope = nil)
items = current_user.send("assigned_" + entity.pluralize).opened items = current_user.send('assigned_' + entity.pluralize)
get_count(items, scope)
if scope.kind_of?(Group)
items = items.of_group(scope)
elsif scope.kind_of?(Project)
items = items.of_projects(scope)
end
items.count
end end
def authored_entities_count(current_user, entity, scope = nil) def authored_entities_count(current_user, entity, scope = nil)
items = current_user.send(entity.pluralize).opened items = current_user.send(entity.pluralize)
get_count(items, scope)
if scope.kind_of?(Group)
items = items.of_group(scope)
elsif scope.kind_of?(Project)
items = items.of_projects(scope)
end
items.count
end end
def authorized_entities_count(current_user, entity, scope = nil) def authorized_entities_count(current_user, entity, scope = nil)
items = entity.classify.constantize.opened items = entity.classify.constantize
get_count(items, scope, true, current_user)
end
protected
def get_count(items, scope, get_authorized = false, current_user = nil)
items = items.opened
if scope.kind_of?(Group) if scope.kind_of?(Group)
items = items.of_group(scope) items = items.of_group(scope)
elsif scope.kind_of?(Project) elsif scope.kind_of?(Project)
items = items.of_projects(scope) items = items.of_projects(scope)
else elsif get_authorized
items = items.of_projects(current_user.authorized_projects) items = items.of_projects(current_user.authorized_projects)
end end
items.count items.count
end end
end end
...@@ -19,7 +19,7 @@ module EventsHelper ...@@ -19,7 +19,7 @@ module EventsHelper
[event.action_name, target].join(" ") [event.action_name, target].join(" ")
end end
def event_filter_link key, tooltip def event_filter_link(key, tooltip)
key = key.to_s key = key.to_s
inactive = if @event_filter.active? key inactive = if @event_filter.active? key
nil nil
...@@ -36,10 +36,10 @@ module EventsHelper ...@@ -36,10 +36,10 @@ module EventsHelper
def icon_for_event def icon_for_event
{ {
EventFilter.push => "icon-upload-alt", EventFilter.push => 'fa fa-upload',
EventFilter.merged => "icon-check", EventFilter.merged => 'fa fa-check-square-o',
EventFilter.comments => "icon-comments", EventFilter.comments => 'fa fa-comments',
EventFilter.team => "icon-user", EventFilter.team => 'fa fa-user',
} }
end end
...@@ -136,9 +136,8 @@ module EventsHelper ...@@ -136,9 +136,8 @@ module EventsHelper
end end
def event_note(text) def event_note(text)
text = first_line(text) text = first_line_in_markdown(text, 150)
text = truncate(text, length: 150) sanitize(text, tags: %w(a img b pre code p))
sanitize(markdown(text), tags: %w(a img b pre p))
end end
def event_commit_title(message) def event_commit_title(message)
......
...@@ -51,6 +51,16 @@ module GitlabMarkdownHelper ...@@ -51,6 +51,16 @@ module GitlabMarkdownHelper
@markdown.render(text).html_safe @markdown.render(text).html_safe
end end
# Return the first line of +text+, up to +max_chars+, after parsing the line
# as Markdown. HTML tags in the parsed output are not counted toward the
# +max_chars+ limit. If the length limit falls within a tag's contents, then
# the tag contents are truncated without removing the closing tag.
def first_line_in_markdown(text, max_chars = nil)
md = markdown(text).strip
truncate_visible(md, max_chars || md.length) if md.present?
end
def render_wiki_content(wiki_page) def render_wiki_content(wiki_page)
if wiki_page.format == :markdown if wiki_page.format == :markdown
markdown(wiki_page.content) markdown(wiki_page.content)
...@@ -196,4 +206,52 @@ module GitlabMarkdownHelper ...@@ -196,4 +206,52 @@ module GitlabMarkdownHelper
def correct_ref def correct_ref
@ref ? @ref : "master" @ref ? @ref : "master"
end end
private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML
# tags.
def truncate_visible(text, max_chars)
doc = Nokogiri::HTML.fragment(text)
content_length = 0
truncated = false
doc.traverse do |node|
if node.text? || node.content.empty?
if truncated
node.remove
next
end
# Handle line breaks within a node
if node.content.strip.lines.length > 1
node.content = "#{node.content.lines.first.chomp}..."
truncated = true
end
num_remaining = max_chars - content_length
if node.content.length > num_remaining
node.content = node.content.truncate(num_remaining)
truncated = true
end
content_length += node.content.length
end
truncated = truncate_if_block(node, truncated)
end
doc.to_html
end
# Used by #truncate_visible. If +node+ is the first block element, and the
# text hasn't already been truncated, then append "..." to the node contents
# and return true. Otherwise return false.
def truncate_if_block(node, truncated)
if node.element? && node.description.block? && !truncated
node.content = "#{node.content}..." if node.next_sibling
true
else
truncated
end
end
end end
module IconsHelper module IconsHelper
def boolean_to_icon(value) def boolean_to_icon(value)
if value.to_s == "true" if value.to_s == "true"
content_tag :i, nil, class: 'icon-circle cgreen' content_tag :i, nil, class: 'fa fa-circle cgreen'
else else
content_tag :i, nil, class: 'icon-off clgray' content_tag :i, nil, class: 'fa fa-power-off clgray'
end end
end end
def public_icon def public_icon
content_tag :i, nil, class: 'icon-globe' content_tag :i, nil, class: 'fa fa-globe'
end end
def internal_icon def internal_icon
content_tag :i, nil, class: 'icon-shield' content_tag :i, nil, class: 'fa fa-shield'
end end
def private_icon def private_icon
content_tag :i, nil, class: 'icon-lock' content_tag :i, nil, class: 'fa fa-lock'
end end
end end
module IssuesHelper module IssuesHelper
def issue_css_classes issue def issue_css_classes(issue)
classes = "issue" classes = "issue"
classes << " closed" if issue.closed? classes << " closed" if issue.closed?
classes << " today" if issue.today? classes << " today" if issue.today?
...@@ -84,7 +84,7 @@ module IssuesHelper ...@@ -84,7 +84,7 @@ module IssuesHelper
'id', 'name', object.assignee_id) 'id', 'name', object.assignee_id)
end end
def milestone_options object def milestone_options(object)
options_from_collection_for_select(object.project.milestones.active, options_from_collection_for_select(object.project.milestones.active,
'id', 'title', object.milestone_id) 'id', 'title', object.milestone_id)
end end
......
...@@ -19,19 +19,18 @@ module MergeRequestsHelper ...@@ -19,19 +19,18 @@ module MergeRequestsHelper
source_project_id: event.project.id, source_project_id: event.project.id,
target_project_id: target_project.id, target_project_id: target_project.id,
source_branch: event.branch_name, source_branch: event.branch_name,
target_branch: target_project.repository.root_ref, target_branch: target_project.repository.root_ref
title: event.branch_name.titleize.humanize
} }
end end
def mr_css_classes mr def mr_css_classes(mr)
classes = "merge-request" classes = "merge-request"
classes << " closed" if mr.closed? classes << " closed" if mr.closed?
classes << " merged" if mr.merged? classes << " merged" if mr.merged?
classes classes
end end
def ci_build_details_path merge_request def ci_build_details_path(merge_request)
merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha) merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha)
end end
......
...@@ -52,8 +52,8 @@ module NotesHelper ...@@ -52,8 +52,8 @@ module NotesHelper
discussion_id: discussion_id discussion_id: discussion_id
} }
link_to "", "javascript:;", class: "add-diff-note js-add-diff-note-button", button_tag '', class: 'btn add-diff-note js-add-diff-note-button',
data: data, title: "Add a comment to this line" data: data, title: 'Add a comment to this line'
end end
def link_to_reply_diff(note) def link_to_reply_diff(note)
...@@ -67,11 +67,10 @@ module NotesHelper ...@@ -67,11 +67,10 @@ module NotesHelper
discussion_id: note.discussion_id discussion_id: note.discussion_id
} }
link_to "javascript:;", class: "btn reply-btn js-discussion-reply-button", button_tag class: 'btn reply-btn js-discussion-reply-button',
data: data, title: "Add a reply" do data: data, title: 'Add a reply' do
link_text = "" link_text = content_tag(:i, nil, class: 'fa fa-comment')
link_text < content_tag(:i, nil, class: 'icon-comment') link_text << ' Reply'
link_text << "Reply" end
end
end end
end end
module NotificationsHelper module NotificationsHelper
def notification_icon(notification) def notification_icon(notification)
if notification.disabled? if notification.disabled?
content_tag :i, nil, class: 'icon-volume-off ns-mute' content_tag :i, nil, class: 'fa fa-volume-off ns-mute'
elsif notification.participating? elsif notification.participating?
content_tag :i, nil, class: 'icon-volume-down ns-part' content_tag :i, nil, class: 'fa fa-volume-down ns-part'
elsif notification.watch? elsif notification.watch?
content_tag :i, nil, class: 'icon-volume-up ns-watch' content_tag :i, nil, class: 'fa fa-volume-up ns-watch'
else else
content_tag :i, nil, class: 'icon-circle-blank ns-default' content_tag :i, nil, class: 'fa fa-circle-o ns-default'
end end
end end
end end
module OauthHelper module OauthHelper
def ldap_enabled? def ldap_enabled?
Devise.omniauth_providers.include?(:ldap) Gitlab.config.ldap.enabled
end end
def default_providers def default_providers
......
module ProfileHelper module ProfileHelper
def oauth_active_class provider def oauth_active_class(provider)
if current_user.provider == provider.to_s if current_user.provider == provider.to_s
'active' 'active'
end end
......
...@@ -3,7 +3,7 @@ module ProjectsHelper ...@@ -3,7 +3,7 @@ module ProjectsHelper
"You are going to remove #{user.name} from #{project.name} project team. Are you sure?" "You are going to remove #{user.name} from #{project.name} project team. Are you sure?"
end end
def link_to_project project def link_to_project(project)
link_to project do link_to project do
title = content_tag(:span, project.name, class: 'project-name') title = content_tag(:span, project.name, class: 'project-name')
...@@ -39,7 +39,7 @@ module ProjectsHelper ...@@ -39,7 +39,7 @@ module ProjectsHelper
end end
end end
def project_title project def project_title(project)
if project.group if project.group
content_tag :span do content_tag :span do
link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name
...@@ -56,6 +56,10 @@ module ProjectsHelper ...@@ -56,6 +56,10 @@ module ProjectsHelper
"You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?" "You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?"
end end
def transfer_project_message(project)
"You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?"
end
def project_nav_tabs def project_nav_tabs
@nav_tabs ||= get_project_nav_tabs(@project, current_user) @nav_tabs ||= get_project_nav_tabs(@project, current_user)
end end
...@@ -128,12 +132,12 @@ module ProjectsHelper ...@@ -128,12 +132,12 @@ module ProjectsHelper
toggle_html = content_tag('span', class: 'toggle') do toggle_html = content_tag('span', class: 'toggle') do
toggle_text = if starred toggle_text = if starred
'Unstar' ' Unstar'
else else
'Star' ' Star'
end end
content_tag('i', ' ', class: 'icon-star') + toggle_text content_tag('i', ' ', class: 'fa fa-star') + toggle_text
end end
count_html = content_tag('span', class: 'count') do count_html = content_tag('span', class: 'count') do
...@@ -156,6 +160,14 @@ module ProjectsHelper ...@@ -156,6 +160,14 @@ module ProjectsHelper
end end
end end
def link_to_toggle_fork
out = content_tag(:i, '', class: 'fa fa-code-fork')
out << ' Fork'
out << content_tag(:span, class: 'count') do
@project.forks_count.to_s
end
end
private private
def get_project_nav_tabs(project, current_user) def get_project_nav_tabs(project, current_user)
......
...@@ -80,7 +80,8 @@ module SearchHelper ...@@ -80,7 +80,8 @@ module SearchHelper
# Autocomplete results for the current user's projects # Autocomplete results for the current user's projects
def projects_autocomplete(term, limit = 5) def projects_autocomplete(term, limit = 5)
ProjectsFinder.new.execute(current_user).search_by_title(term).non_archived.limit(limit).map do |p| ProjectsFinder.new.execute(current_user).search_by_title(term).
sorted_by_stars.non_archived.limit(limit).map do |p|
{ {
label: "project: #{search_result_sanitize(p.name_with_namespace)}", label: "project: #{search_result_sanitize(p.name_with_namespace)}",
url: project_path(p) url: project_path(p)
......
...@@ -89,7 +89,7 @@ module TabHelper ...@@ -89,7 +89,7 @@ module TabHelper
end end
# Use nav_tab for save controller/action but different params # Use nav_tab for save controller/action but different params
def nav_tab key, value, &block def nav_tab(key, value, &block)
o = {} o = {}
o[:class] = "" o[:class] = ""
......
module TagsHelper module TagsHelper
def tag_path tag def tag_path(tag)
"/tags/#{tag}" "/tags/#{tag}"
end end
def tag_list project def tag_list(project)
html = '' html = ''
project.tag_list.each do |tag| project.tag_list.each do |tag|
html += link_to tag, tag_path(tag) html += link_to tag, tag_path(tag)
......
...@@ -36,9 +36,9 @@ module TreeHelper ...@@ -36,9 +36,9 @@ module TreeHelper
# type - String type of the tree item; either 'folder' or 'file' # type - String type of the tree item; either 'folder' or 'file'
def tree_icon(type) def tree_icon(type)
icon_class = if type == 'folder' icon_class = if type == 'folder'
'icon-folder-close' 'fa fa-folder'
else else
'icon-file-alt' 'fa fa-file-o'
end end
content_tag :i, nil, class: icon_class content_tag :i, nil, class: icon_class
...@@ -80,7 +80,7 @@ module TreeHelper ...@@ -80,7 +80,7 @@ module TreeHelper
end end
end end
def up_dir_path tree def up_dir_path(tree)
file = File.join(@path, "..") file = File.join(@path, "..")
tree_join(@ref, file) tree_join(@ref, file)
end end
...@@ -90,7 +90,7 @@ module TreeHelper ...@@ -90,7 +90,7 @@ module TreeHelper
end end
def editing_preview_title(filename) def editing_preview_title(filename)
if gitlab_markdown?(filename) || markup?(filename) if Gitlab::MarkdownHelper.previewable?(filename)
'Preview' 'Preview'
else else
'Diff' 'Diff'
......
...@@ -28,6 +28,23 @@ module VisibilityLevelHelper ...@@ -28,6 +28,23 @@ module VisibilityLevelHelper
end end
end end
def snippet_visibility_level_description(level)
capture_haml do
haml_tag :span do
case level
when Gitlab::VisibilityLevel::PRIVATE
haml_concat "The snippet is visible only for me"
when Gitlab::VisibilityLevel::INTERNAL
haml_concat "The snippet is visible for any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
haml_concat "The snippet can be accessed"
haml_concat "without any"
haml_concat "authentication."
end
end
end
end
def visibility_level_icon(level) def visibility_level_icon(level)
case level case level
when Gitlab::VisibilityLevel::PRIVATE when Gitlab::VisibilityLevel::PRIVATE
......
module Emails module Emails
module Groups module Groups
def group_access_granted_email(user_group_id) def group_access_granted_email(user_group_id)
@membership = UsersGroup.find(user_group_id) @membership = GroupMember.find(user_group_id)
@group = @membership.group @group = @membership.group
@target_url = group_url(@group) @target_url = group_url(@group)
mail(to: @membership.user.email, mail(to: @membership.user.email,
......
module Emails module Emails
module Projects module Projects
def project_access_granted_email(user_project_id) def project_access_granted_email(user_project_id)
@users_project = UsersProject.find user_project_id @project_member = ProjectMember.find user_project_id
@project = @users_project.project @project = @project_member.project
@target_url = project_url(@project) @target_url = project_url(@project)
mail(to: @users_project.user.email, mail(to: @project_member.user.email,
subject: subject("Access to project was granted")) subject: subject("Access to project was granted"))
end end
......
...@@ -14,7 +14,7 @@ class Ability ...@@ -14,7 +14,7 @@ class Ability
when "MergeRequest" then merge_request_abilities(user, subject) when "MergeRequest" then merge_request_abilities(user, subject)
when "Group" then group_abilities(user, subject) when "Group" then group_abilities(user, subject)
when "Namespace" then namespace_abilities(user, subject) when "Namespace" then namespace_abilities(user, subject)
when "UsersGroup" then users_group_abilities(user, subject) when "GroupMember" then users_group_abilities(user, subject)
else [] else []
end.concat(global_abilities(user)) end.concat(global_abilities(user))
end end
...@@ -184,7 +184,7 @@ class Ability ...@@ -184,7 +184,7 @@ class Ability
] ]
end end
def group_abilities user, group def group_abilities(user, group)
rules = [] rules = []
if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any? if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any?
...@@ -209,7 +209,7 @@ class Ability ...@@ -209,7 +209,7 @@ class Ability
rules.flatten rules.flatten
end end
def namespace_abilities user, namespace def namespace_abilities(user, namespace)
rules = [] rules = []
# Only namespace owner and administrators can manage it # Only namespace owner and administrators can manage it
......
...@@ -19,13 +19,24 @@ class Commit ...@@ -19,13 +19,24 @@ class Commit
class << self class << self
def decorate(commits) def decorate(commits)
commits.map { |c| self.new(c) } commits.map do |commit|
if commit.kind_of?(Commit)
commit
else
self.new(commit)
end
end
end end
# Calculate number of lines to render for diffs # Calculate number of lines to render for diffs
def diff_line_count(diffs) def diff_line_count(diffs)
diffs.reduce(0) { |sum, d| sum + d.diff.lines.count } diffs.reduce(0) { |sum, d| sum + d.diff.lines.count }
end end
# Truncate sha to 8 characters
def truncate_sha(sha)
sha[0..7]
end
end end
attr_accessor :raw attr_accessor :raw
...@@ -88,15 +99,30 @@ class Commit ...@@ -88,15 +99,30 @@ class Commit
description.present? description.present?
end end
def hook_attrs(project)
path_with_namespace = project.path_with_namespace
{
id: id,
message: safe_message,
timestamp: committed_date.xmlschema,
url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{id}",
author: {
name: author_name,
email: author_email
}
}
end
# Discover issues should be closed when this commit is pushed to a project's # Discover issues should be closed when this commit is pushed to a project's
# default branch. # default branch.
def closes_issues project def closes_issues(project)
Gitlab::ClosingIssueExtractor.closed_by_message_in_project(safe_message, project) Gitlab::ClosingIssueExtractor.closed_by_message_in_project(safe_message, project)
end end
# Mentionable override. # Mentionable override.
def gfm_reference def gfm_reference
"commit #{sha[0..5]}" "commit #{id}"
end end
def method_missing(m, *args, &block) def method_missing(m, *args, &block)
...@@ -108,4 +134,13 @@ class Commit ...@@ -108,4 +134,13 @@ class Commit
super super
end end
# Truncate sha to 8 characters
def short_id
@raw.short_id(7)
end
def parents
@parents ||= Commit.decorate(super)
end
end end
...@@ -134,7 +134,7 @@ module Issuable ...@@ -134,7 +134,7 @@ module Issuable
def to_hook_data def to_hook_data
{ {
object_kind: self.class.name.underscore, object_kind: self.class.name.underscore,
object_attributes: self.attributes object_attributes: hook_attrs
} }
end end
......
...@@ -10,7 +10,7 @@ module Mentionable ...@@ -10,7 +10,7 @@ module Mentionable
module ClassMethods module ClassMethods
# Indicate which attributes of the Mentionable to search for GFM references. # Indicate which attributes of the Mentionable to search for GFM references.
def attr_mentionable *attrs def attr_mentionable(*attrs)
mentionable_attrs.concat(attrs.map(&:to_s)) mentionable_attrs.concat(attrs.map(&:to_s))
end end
...@@ -38,7 +38,7 @@ module Mentionable ...@@ -38,7 +38,7 @@ module Mentionable
# Determine whether or not a cross-reference Note has already been created between this Mentionable and # Determine whether or not a cross-reference Note has already been created between this Mentionable and
# the specified target. # the specified target.
def has_mentioned? target def has_mentioned?(target)
Note.cross_reference_exists?(target, local_reference) Note.cross_reference_exists?(target, local_reference)
end end
...@@ -64,15 +64,17 @@ module Mentionable ...@@ -64,15 +64,17 @@ module Mentionable
end end
# Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference. # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference.
def references p = project, text = mentionable_text def references(p = project, text = mentionable_text)
return [] if text.blank? return [] if text.blank?
ext = Gitlab::ReferenceExtractor.new ext = Gitlab::ReferenceExtractor.new
ext.analyze(text) ext.analyze(text, p)
(ext.issues_for(p) + ext.merge_requests_for(p) + ext.commits_for(p)).uniq - [local_reference] (ext.issues_for +
ext.merge_requests_for +
ext.commits_for).uniq - [local_reference]
end end
# Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+.
def create_cross_references! p = project, a = author, without = [] def create_cross_references!(p = project, a = author, without = [])
refs = references(p) - without refs = references(p) - without
refs.each do |ref| refs.each do |ref|
Note.create_cross_reference_note(ref, local_reference, a, p) Note.create_cross_reference_note(ref, local_reference, a, p)
...@@ -81,7 +83,7 @@ module Mentionable ...@@ -81,7 +83,7 @@ module Mentionable
# If the mentionable_text field is about to change, locate any *added* references and create cross references for # If the mentionable_text field is about to change, locate any *added* references and create cross references for
# them. Invoke from an observer's #before_save implementation. # them. Invoke from an observer's #before_save implementation.
def notice_added_references p = project, a = author def notice_added_references(p = project, a = author)
ch = changed_attributes ch = changed_attributes
original, mentionable_changed = "", false original, mentionable_changed = "", false
self.class.mentionable_attrs.each do |attr| self.class.mentionable_attrs.each do |attr|
......
# == Notifiable concern # == Notifiable concern
# #
# Contains notification functionality shared between UsersProject and UsersGroup # Contains notification functionality
# #
module Notifiable module Notifiable
extend ActiveSupport::Concern extend ActiveSupport::Concern
......
# Contains functionality for objects that can have task lists in their
# descriptions. Task list items can be added with Markdown like "* [x] Fix
# bugs".
#
# Used by MergeRequest and Issue
module Taskable
TASK_PATTERN_MD = /^(?<bullet> *[*-] *)\[(?<checked>[ xX])\]/.freeze
TASK_PATTERN_HTML = /^<li>\[(?<checked>[ xX])\]/.freeze
# Change the state of a task list item for this Taskable. Edit the object's
# description by finding the nth task item and changing its checkbox
# placeholder to "[x]" if +checked+ is true, or "[ ]" if it's false.
# Note: task numbering starts with 1
def update_nth_task(n, checked)
index = 0
check_char = checked ? 'x' : ' '
# Do this instead of using #gsub! so that ActiveRecord detects that a field
# has changed.
self.description = self.description.gsub(TASK_PATTERN_MD) do |match|
index += 1
case index
when n then "#{$LAST_MATCH_INFO[:bullet]}[#{check_char}]"
else match
end
end
save
end
# Return true if this object's description has any task list items.
def tasks?
description && description.match(TASK_PATTERN_MD)
end
# Return a string that describes the current state of this Taskable's task
# list items, e.g. "20 tasks (12 done, 8 unfinished)"
def task_status
return nil unless description
num_tasks = 0
num_done = 0
description.scan(TASK_PATTERN_MD) do
num_tasks += 1
num_done += 1 unless $LAST_MATCH_INFO[:checked] == ' '
end
"#{num_tasks} tasks (#{num_done} done, #{num_tasks - num_done} unfinished)"
end
end
...@@ -266,7 +266,7 @@ class Event < ActiveRecord::Base ...@@ -266,7 +266,7 @@ class Event < ActiveRecord::Base
end end
def note_short_commit_id def note_short_commit_id
note_commit_id[0..8] Commit.truncate_sha(note_commit_id)
end end
def note_commit? def note_commit?
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment