Commit 90ce0be4 authored by Jacob Vosmaer's avatar Jacob Vosmaer

Merge branch 'master' of dev.gitlab.org:gitlab/gitlab-ee into feature-120-multiple-ldap-groups

Conflicts:
	app/models/group.rb
	app/views/groups/members.html.haml
	db/schema.rb
parents 5238efd8 a81d806b
...@@ -17,8 +17,8 @@ env: ...@@ -17,8 +17,8 @@ env:
- TASK=jasmine:ci DB=postgresql - TASK=jasmine:ci DB=postgresql
before_install: before_install:
- sudo apt-get install libicu-dev -y - sudo apt-get install libicu-dev -y
install: install:
- "bundle install --deployment --without production" - "travis_retry bundle install --deployment --without production --retry 5"
branches: branches:
only: only:
- 'master' - 'master'
......
v 7.2.0
- Explore page
- Add project stars (Ciro Santilli)
- Log Sidekiq arguments
- Better labels: colors, ability to rename and remove
- Improve the way merge request collects diffs
- Improve compare page for large diffs
- Expose the full commit message via API
- Fix 500 error on repository rename
- Fix bug when MR download patch return invalid diff
- Test gitlab-shell integration
- Repository import timeout increased from 2 to 4 minutes allowing larger repos to be imported
- API for labels (Robert Schilling)
- API: ability to set an import url when creating project for specific user
v 7.1.1
- Fix cpu usage issue in Firefox
- Fix redirect loop when changing password by new user
- Fix 500 error on new merge request page
v 7.1.0 v 7.1.0
- Remove observers - Remove observers
- Improve MR discussions - Improve MR discussions
...@@ -8,7 +28,7 @@ v 7.1.0 ...@@ -8,7 +28,7 @@ v 7.1.0
- Dont show reply button if user is not signed in - Dont show reply button if user is not signed in
- Expose more information for issues with webhook - Expose more information for issues with webhook
- Add a mention of the merge request into the default merge request commit message - Add a mention of the merge request into the default merge request commit message
- Imrpove code highlight, introduce support for more languages like Go, Clojure, Erlang etc - Improve code highlight, introduce support for more languages like Go, Clojure, Erlang etc
- Fix concurrency issue in repository download - Fix concurrency issue in repository download
- Dont allow repository name start with ? - Dont allow repository name start with ?
- Improve email threading (Pierre de La Morinerie) - Improve email threading (Pierre de La Morinerie)
...@@ -53,7 +73,7 @@ v 7.0.0 ...@@ -53,7 +73,7 @@ v 7.0.0
- Show notice if your profile is public - Show notice if your profile is public
- UI improvements for mobile devices - UI improvements for mobile devices
- Improve diff rendering performance - Improve diff rendering performance
- Drag-n-drop for issues and merge requests between states at milestone page - Drag-n-drop for issues and merge requests between states at milestone page
- Fix '0 commits' message for huge repositories on project home page - Fix '0 commits' message for huge repositories on project home page
- Prevent 500 error page when visit commit page from large repo - Prevent 500 error page when visit commit page from large repo
- Add notice about huge push over http to unicorn config - Add notice about huge push over http to unicorn config
...@@ -63,7 +83,7 @@ v 7.0.0 ...@@ -63,7 +83,7 @@ v 7.0.0
- Be more selective when killing stray Sidekiqs - Be more selective when killing stray Sidekiqs
- Check LDAP user filter during sign-in - Check LDAP user filter during sign-in
- Remove wall feature (no data loss - you can take it from database) - Remove wall feature (no data loss - you can take it from database)
- Dont expose user emails via API unless you are admin - Dont expose user emails via API unless you are admin
- Detect issues closed by Merge Request description - Detect issues closed by Merge Request description
- Better email subject lines from email on push service (Alex Elman) - Better email subject lines from email on push service (Alex Elman)
- Enable identicon for gravatar be default - Enable identicon for gravatar be default
...@@ -141,7 +161,7 @@ v 6.7.0 ...@@ -141,7 +161,7 @@ v 6.7.0
- Blob and tree gfm links to anchors work - Blob and tree gfm links to anchors work
- Piwik Integration (Sebastian Winkler) - Piwik Integration (Sebastian Winkler)
- Show contribution guide link for new issue form (Jeroen van Baarsen) - Show contribution guide link for new issue form (Jeroen van Baarsen)
- Fix CI status for merge requests from fork - Fix CI status for merge requests from fork
- Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard) - Added option to remove issue assignee on project issue page and issue edit page (Jason Blanchard)
- New page load indicator that includes a spinner that scrolls with the page - New page load indicator that includes a spinner that scrolls with the page
- Converted all the help sections into markdown - Converted all the help sections into markdown
...@@ -332,7 +352,7 @@ v 6.2.0 ...@@ -332,7 +352,7 @@ v 6.2.0
- Store the sessions in Redis instead of the cookie store - Store the sessions in Redis instead of the cookie store
- Fixed relative links in markdown - Fixed relative links in markdown
- User must confirm their email if signup enabled - User must confirm their email if signup enabled
- User must confirm changed email - User must confirm changed email
v 6.1.0 v 6.1.0
- Project specific IDs for issues, mr, milestones - Project specific IDs for issues, mr, milestones
......
v 7.3.0
- Add an option to change the LDAP sync time from default 1 hour
- User will receive an email when unsubscribed from admin notifications
v 7.2.0
- Improve Redmine integration
- Better logging for the JIRA issue closing service
- Administrators can now send email to all users through the admin interface
- JIRA issue transition ID is now customizable
- LDAP group settings are now visible in admin group show page and group members page
v 7.1.0 v 7.1.0
- Synchronize LDAP-enabled GitLab administrators with an LDAP group (Marvin Frick, sponsored by SinnerSchrader) - Synchronize LDAP-enabled GitLab administrators with an LDAP group (Marvin Frick, sponsored by SinnerSchrader)
- Synchronize SSH keys with LDAP (Oleg Girko (Jolla) and Marvin Frick (SinnerSchrader)) - Synchronize SSH keys with LDAP (Oleg Girko (Jolla) and Marvin Frick (SinnerSchrader))
- Support Jenkins jobs with multiple modules (Marvin Frick, sponsored by SinnerSchrader) - Support Jenkins jobs with multiple modules (Marvin Frick, sponsored by SinnerSchrader)
v 7.0.0 v 7.0.0
- Fix: empty brand images are displayed as empty image_tag on login page (Marvin Frick, sponsored by SinnerSchrader) - Fix: empty brand images are displayed as empty image_tag on login page (Marvin Frick, sponsored by SinnerSchrader)
v 6.9.4 v 6.9.4
- Fix bug in JIRA Issue closing triggered by commit messages - Fix bug in JIRA Issue closing triggered by commit messages
......
...@@ -80,16 +80,20 @@ gem "six" ...@@ -80,16 +80,20 @@ gem "six"
gem "seed-fu" gem "seed-fu"
# Markdown to HTML # Markdown to HTML
gem "redcarpet", "~> 2.2.2"
gem "github-markup" gem "github-markup"
gem "org-ruby" # For rendering .org files
# Required markup gems by github-markdown
gem 'redcarpet', '~> 2.2.2'
gem 'RedCloth'
gem 'rdoc', '~>3.6'
gem 'org-ruby'
gem 'creole', '~>0.3.6'
gem 'wikicloth', '=0.8.1'
gem 'asciidoctor', '= 0.1.4'
# Diffs # Diffs
gem 'diffy', '~> 3.0.3' gem 'diffy', '~> 3.0.3'
# Asciidoc to HTML
gem "asciidoctor"
# Application server # Application server
group :unicorn do group :unicorn do
gem "unicorn", '~> 4.6.3' gem "unicorn", '~> 4.6.3'
...@@ -175,6 +179,7 @@ gem "gitlab_emoji", "~> 0.0.1.1" ...@@ -175,6 +179,7 @@ gem "gitlab_emoji", "~> 0.0.1.1"
gem "gon", '~> 5.0.0' gem "gon", '~> 5.0.0'
gem 'nprogress-rails' gem 'nprogress-rails'
gem 'request_store' gem 'request_store'
gem "virtus"
group :development do group :development do
gem "annotate", "~> 2.6.0.beta2" gem "annotate", "~> 2.6.0.beta2"
......
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
RedCloth (4.2.9)
ace-rails-ap (2.0.1) ace-rails-ap (2.0.1)
actionmailer (4.1.1) actionmailer (4.1.1)
actionpack (= 4.1.1) actionpack (= 4.1.1)
...@@ -86,6 +87,7 @@ GEM ...@@ -86,6 +87,7 @@ GEM
thor thor
crack (0.4.1) crack (0.4.1)
safe_yaml (~> 0.9.0) safe_yaml (~> 0.9.0)
creole (0.3.8)
d3_rails (3.1.10) d3_rails (3.1.10)
railties (>= 3.1.0) railties (>= 3.1.0)
daemons (1.1.9) daemons (1.1.9)
...@@ -121,12 +123,13 @@ GEM ...@@ -121,12 +123,13 @@ GEM
eventmachine (1.0.3) eventmachine (1.0.3)
excon (0.32.1) excon (0.32.1)
execjs (2.0.2) execjs (2.0.2)
expression_parser (0.9.0)
factory_girl (4.3.0) factory_girl (4.3.0)
activesupport (>= 3.0.0) activesupport (>= 3.0.0)
factory_girl_rails (4.3.0) factory_girl_rails (4.3.0)
factory_girl (~> 4.3.0) factory_girl (~> 4.3.0)
railties (>= 3.0.0) railties (>= 3.0.0)
faraday (0.8.8) faraday (0.8.9)
multipart-post (~> 1.2.0) multipart-post (~> 1.2.0)
faraday_middleware (0.9.0) faraday_middleware (0.9.0)
faraday (>= 0.7.4, < 0.9) faraday (>= 0.7.4, < 0.9)
...@@ -155,7 +158,7 @@ GEM ...@@ -155,7 +158,7 @@ GEM
dotenv (>= 0.7) dotenv (>= 0.7)
thor (>= 0.13.6) thor (>= 0.13.6)
formatador (0.2.4) formatador (0.2.4)
gemnasium-gitlab-service (0.2.1) gemnasium-gitlab-service (0.2.2)
rugged (~> 0.19) rugged (~> 0.19)
gherkin-ruby (0.3.1) gherkin-ruby (0.3.1)
racc racc
...@@ -176,12 +179,12 @@ GEM ...@@ -176,12 +179,12 @@ 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.0.1) gitlab_git (6.2.1)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.6) charlock_holmes (~> 0.6)
gitlab-grit (~> 2.6) gitlab-grit (~> 2.6)
gitlab-linguist (~> 3.0) gitlab-linguist (~> 3.0)
rugged (~> 0.19.0) rugged (~> 0.21.0)
gitlab_meta (7.0) gitlab_meta (7.0)
gitlab_omniauth-ldap (1.0.4) gitlab_omniauth-ldap (1.0.4)
net-ldap (~> 0.3.1) net-ldap (~> 0.3.1)
...@@ -231,7 +234,7 @@ GEM ...@@ -231,7 +234,7 @@ GEM
activesupport (>= 4.0.1) activesupport (>= 4.0.1)
haml (>= 3.1, < 5.0) haml (>= 3.1, < 5.0)
railties (>= 4.0.1) railties (>= 4.0.1)
hashie (2.0.5) hashie (2.1.2)
hike (1.2.3) hike (1.2.3)
hipchat (0.14.0) hipchat (0.14.0)
httparty httparty
...@@ -240,8 +243,8 @@ GEM ...@@ -240,8 +243,8 @@ GEM
httparty (0.13.0) httparty (0.13.0)
json (~> 1.8) json (~> 1.8)
multi_xml (>= 0.5.2) multi_xml (>= 0.5.2)
httpauth (0.2.0) httpauth (0.2.1)
i18n (0.6.9) i18n (0.6.11)
ice_nine (0.10.0) ice_nine (0.10.0)
jasmine (2.0.2) jasmine (2.0.2)
jasmine-core (~> 2.0.0) jasmine-core (~> 2.0.0)
...@@ -261,7 +264,7 @@ GEM ...@@ -261,7 +264,7 @@ GEM
jquery-ui-rails (4.2.1) jquery-ui-rails (4.2.1)
railties (>= 3.2.16) railties (>= 3.2.16)
json (1.8.1) json (1.8.1)
jwt (0.1.8) jwt (0.1.13)
multi_json (>= 1.5) multi_json (>= 1.5)
kaminari (0.15.1) kaminari (0.15.1)
actionpack (>= 3.0.0) actionpack (>= 3.0.0)
...@@ -308,9 +311,9 @@ GEM ...@@ -308,9 +311,9 @@ GEM
omniauth-github (1.1.1) omniauth-github (1.1.1)
omniauth (~> 1.0) omniauth (~> 1.0)
omniauth-oauth2 (~> 1.1) omniauth-oauth2 (~> 1.1)
omniauth-google-oauth2 (0.2.1) omniauth-google-oauth2 (0.2.5)
omniauth (~> 1.0) omniauth (> 1.0)
omniauth-oauth2 omniauth-oauth2 (~> 1.1)
omniauth-oauth (1.0.1) omniauth-oauth (1.0.1)
oauth oauth
omniauth (~> 1.0) omniauth (~> 1.0)
...@@ -320,8 +323,8 @@ GEM ...@@ -320,8 +323,8 @@ 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.6) org-ruby (0.9.8)
rubypants (>= 0.2.0) rubypants (~> 0.2)
orm_adapter (0.5.0) orm_adapter (0.5.0)
pg (0.15.1) pg (0.15.1)
phantomjs (1.9.2.0) phantomjs (1.9.2.0)
...@@ -331,7 +334,7 @@ GEM ...@@ -331,7 +334,7 @@ GEM
multi_json (~> 1.0) multi_json (~> 1.0)
websocket-driver (>= 0.2.0) websocket-driver (>= 0.2.0)
polyglot (0.3.4) polyglot (0.3.4)
posix-spawn (0.3.8) posix-spawn (0.3.9)
pry (0.9.12.4) pry (0.9.12.4)
coderay (~> 1.0) coderay (~> 1.0)
method_source (~> 0.8) method_source (~> 0.8)
...@@ -413,6 +416,7 @@ GEM ...@@ -413,6 +416,7 @@ GEM
require_all (1.3.2) require_all (1.3.2)
rest-client (1.6.7) rest-client (1.6.7)
mime-types (>= 1.16) mime-types (>= 1.16)
rinku (1.7.3)
rouge (1.3.3) rouge (1.3.3)
rspec (2.14.1) rspec (2.14.1)
rspec-core (~> 2.14.0) rspec-core (~> 2.14.0)
...@@ -432,7 +436,7 @@ GEM ...@@ -432,7 +436,7 @@ GEM
ruby-progressbar (1.2.0) ruby-progressbar (1.2.0)
rubyntlm (0.1.1) rubyntlm (0.1.1)
rubypants (0.2.0) rubypants (0.2.0)
rugged (0.19.0) rugged (0.21.0)
safe_yaml (0.9.7) safe_yaml (0.9.7)
sanitize (2.1.0) sanitize (2.1.0)
nokogiri (>= 1.4.4) nokogiri (>= 1.4.4)
...@@ -536,7 +540,7 @@ GEM ...@@ -536,7 +540,7 @@ GEM
eventmachine (>= 0.12.8) eventmachine (>= 0.12.8)
http_parser.rb (~> 0.5.1) http_parser.rb (~> 0.5.1)
simple_oauth (~> 0.1.4) simple_oauth (~> 0.1.4)
tzinfo (1.2.1) tzinfo (1.2.2)
thread_safe (~> 0.1) thread_safe (~> 0.1)
uglifier (2.3.2) uglifier (2.3.2)
execjs (>= 0.3.0) execjs (>= 0.3.0)
...@@ -563,6 +567,10 @@ GEM ...@@ -563,6 +567,10 @@ GEM
addressable (>= 2.2.7) addressable (>= 2.2.7)
crack (>= 0.3.2) crack (>= 0.3.2)
websocket-driver (0.3.3) websocket-driver (0.3.3)
wikicloth (0.8.1)
builder
expression_parser
rinku
xpath (2.0.0) xpath (2.0.0)
nokogiri (~> 1.3) nokogiri (~> 1.3)
...@@ -570,10 +578,11 @@ PLATFORMS ...@@ -570,10 +578,11 @@ PLATFORMS
ruby ruby
DEPENDENCIES DEPENDENCIES
RedCloth
ace-rails-ap ace-rails-ap
acts-as-taggable-on acts-as-taggable-on
annotate (~> 2.6.0.beta2) annotate (~> 2.6.0.beta2)
asciidoctor asciidoctor (= 0.1.4)
awesome_print awesome_print
better_errors better_errors
binding_of_caller binding_of_caller
...@@ -583,6 +592,7 @@ DEPENDENCIES ...@@ -583,6 +592,7 @@ DEPENDENCIES
coffee-rails coffee-rails
colored colored
coveralls coveralls
creole (~> 0.3.6)
d3_rails (~> 3.1.4) d3_rails (~> 3.1.4)
database_cleaner database_cleaner
default_value_for (~> 3.0.0) default_value_for (~> 3.0.0)
...@@ -647,6 +657,7 @@ DEPENDENCIES ...@@ -647,6 +657,7 @@ DEPENDENCIES
raphael-rails (~> 2.1.2) raphael-rails (~> 2.1.2)
rb-fsevent rb-fsevent
rb-inotify rb-inotify
rdoc (~> 3.6)
redcarpet (~> 2.2.2) redcarpet (~> 2.2.2)
redis-rails redis-rails
request_store request_store
...@@ -682,4 +693,6 @@ DEPENDENCIES ...@@ -682,4 +693,6 @@ DEPENDENCIES
unicorn (~> 4.6.3) unicorn (~> 4.6.3)
unicorn-worker-killer unicorn-worker-killer
version_sorter version_sorter
virtus
webmock webmock
wikicloth (= 0.8.1)
...@@ -34,7 +34,7 @@ The most important thing is making sure valid issues receive feedback from the d ...@@ -34,7 +34,7 @@ The most important thing is making sure valid issues receive feedback from the d
## Workflow labels ## Workflow labels
Workflow labels are purposely not very detailed since that would be hard to keep updated as you would need to reevaluate them after every comment. We optionally use functional labels on demand when want to group related issues to get an overview (for example all issues related to RVM, to tackle them in one go) and to add details to the issue. Workflow labels are purposely not very detailed since that would be hard to keep updated as you would need to re-evaluate them after every comment. We optionally use functional labels on demand when want to group related issues to get an overview (for example all issues related to RVM, to tackle them in one go) and to add details to the issue.
- *Awaiting feedback*: Feedback pending from the reporter - *Awaiting feedback*: Feedback pending from the reporter
- *Awaiting confirmation of fix*: The issue should already be solved in **master** (generally you can avoid this workflow item and just close the issue right away) - *Awaiting confirmation of fix*: The issue should already be solved in **master** (generally you can avoid this workflow item and just close the issue right away)
...@@ -61,7 +61,7 @@ If an issue is complex and needs the attention of a specific person, assignment ...@@ -61,7 +61,7 @@ If an issue is complex and needs the attention of a specific person, assignment
## Be kind ## Be kind
Be kind to people trying to contribute. Be aware that people can be a non-native or a native English speaker, they might not understand thing or they might be very sensitive to how your word things. Use emoji to express your feelings (heart, star, smile, etc.). Some good tips about giving feedback to merge requests is in the [Thoughtbot code review guide](https://github.com/thoughtbot/guides/tree/master/code-review). Be kind to people trying to contribute. Be aware that people may be a non-native English speaker, they might not understand things or they might be very sensitive as to how you word things. Use Emoji to express your feelings (heart, star, smile, etc.). Some good tips about giving feedback to merge requests is in the [Thoughtbot code review guide](https://github.com/thoughtbot/guides/tree/master/code-review).
## Copy & paste responses ## Copy & paste responses
......
...@@ -85,7 +85,7 @@ or by directly calling the script: ...@@ -85,7 +85,7 @@ or by directly calling the script:
sudo /etc/init.d/gitlab start sudo /etc/init.d/gitlab start
Please login with `root` / `5iveL!fe`. Please login with `root` / `5iveL!fe`
## Install a development environment ## Install a development environment
......
7.1.0-ee 7.3.0.pre-ee
...@@ -53,15 +53,40 @@ window.split = (val) -> ...@@ -53,15 +53,40 @@ window.split = (val) ->
window.extractLast = (term) -> window.extractLast = (term) ->
return split( term ).pop() return split( term ).pop()
window.rstrip = (val) ->
return val.replace(/\s+$/, '')
# Disable button if text field is empty # Disable button if text field is empty
window.disableButtonIfEmptyField = (field_selector, button_selector) -> window.disableButtonIfEmptyField = (field_selector, button_selector) ->
field = $(field_selector) field = $(field_selector)
closest_submit = field.closest("form").find(button_selector) closest_submit = field.closest('form').find(button_selector)
closest_submit.disable() if rstrip(field.val()) is ""
field.on 'input', ->
if rstrip($(@).val()) is ""
closest_submit.disable()
else
closest_submit.enable()
# Disable button if any input field with given selector is empty
window.disableButtonIfAnyEmptyField = (form, form_selector, button_selector) ->
closest_submit = form.find(button_selector)
empty = false
form.find('input').filter(form_selector).each ->
empty = true if rstrip($(this).val()) is ""
if empty
closest_submit.disable()
else
closest_submit.enable()
closest_submit.disable() if field.val() is "" form.keyup ->
empty = false
form.find('input').filter(form_selector).each ->
empty = true if rstrip($(this).val()) is ""
field.on "input", -> if empty
if $(@).val() is ""
closest_submit.disable() closest_submit.disable()
else else
closest_submit.enable() closest_submit.enable()
......
class Diff
UNFOLD_COUNT = 20
constructor: ->
$(document).on('click', '.js-unfold', (event) =>
target = $(event.target)
unfoldBottom = target.hasClass('js-unfold-bottom')
unfold = true
[old_line, line_number] = @lineNumbers(target.parent())
offset = line_number - old_line
if unfoldBottom
line_number += 1
since = line_number
to = line_number + UNFOLD_COUNT
else
[prev_old_line, prev_new_line] = @lineNumbers(target.parent().prev())
line_number -= 1
to = line_number
if line_number - UNFOLD_COUNT > prev_new_line + 1
since = line_number - UNFOLD_COUNT
else
since = prev_new_line + 1
unfold = false
link = target.parents('.diff-file').attr('data-blob-diff-path')
params =
since: since
to: to
bottom: unfoldBottom
offset: offset
unfold: unfold
$.get(link, params, (response) =>
target.parent().replaceWith(response)
)
)
lineNumbers: (line) ->
return ([0, 0]) unless line.children().length
lines = line.children().slice(0, 2)
line_numbers = ($(l).attr('data-linenumber') for l in lines)
(parseInt(line_number) for line_number in line_numbers)
@Diff = Diff
...@@ -23,13 +23,21 @@ class Dispatcher ...@@ -23,13 +23,21 @@ class Dispatcher
new Issue() new Issue()
when 'projects:milestones:show' when 'projects:milestones:show'
new Milestone() new Milestone()
when 'projects:issues:new', 'projects:merge_requests:new' when 'projects:issues:new'
GitLab.GfmAutoComplete.setup() GitLab.GfmAutoComplete.setup()
when 'projects:merge_requests:new'
GitLab.GfmAutoComplete.setup()
new Diff()
when 'projects:merge_requests:show'
new Diff()
when "projects:merge_requests:diffs"
new Diff()
when 'dashboard:show' when 'dashboard:show'
new Dashboard() new Dashboard()
new Activities() new Activities()
when 'projects:commit:show' when 'projects:commit:show'
new Commit() new Commit()
new Diff()
when 'groups:show', 'projects:show' when 'groups:show', 'projects:show'
new Activities() new Activities()
when 'projects:new', 'projects:edit' when 'projects:new', 'projects:edit'
...@@ -42,6 +50,8 @@ class Dispatcher ...@@ -42,6 +50,8 @@ class Dispatcher
new TreeView() new TreeView()
when 'projects:blob:show' when 'projects:blob:show'
new BlobView() new BlobView()
when 'projects:labels:new', 'projects:labels:edit'
new Labels()
switch path.first() switch path.first()
when 'admin' then new Admin() when 'admin' then new Admin()
......
class Labels
constructor: ->
form = $('.label-form')
@setupLabelForm(form)
@cleanBinding()
@addBinding()
@updateColorPreview()
addBinding: ->
$(document).on 'click', '.suggest-colors a', @setSuggestedColor
$(document).on 'input', 'input#label_color', @updateColorPreview
cleanBinding: ->
$(document).off 'click', '.suggest-colors a'
$(document).off 'input', 'input#label_color'
# Initializes the form to disable the save button if no color or title is entered
setupLabelForm: (form) ->
disableButtonIfAnyEmptyField form, '.form-control', form.find('.js-save-button')
# Updates the the preview color with the hex-color input
updateColorPreview: =>
previewColor = $('input#label_color').val()
$('div.label-color-preview').css('background-color', previewColor)
# Updates the preview color with a click on a suggested color
setSuggestedColor: (e) =>
color = $(e.currentTarget).data('color')
$('input#label_color').val(color)
@updateColorPreview()
# Notify the form, that color has changed
$('.label-form').trigger('keyup')
e.preventDefault()
@Labels = Labels
...@@ -24,3 +24,5 @@ $ -> ...@@ -24,3 +24,5 @@ $ ->
formatResult: ldapGroupResult formatResult: ldapGroupResult
formatSelection: groupFormatSelection formatSelection: groupFormatSelection
dropdownCssClass: "ajax-groups-dropdown" dropdownCssClass: "ajax-groups-dropdown"
formatNoMatches: (nomatch) ->
"Match not found; try refining your search query."
...@@ -20,6 +20,9 @@ $(document).ready -> ...@@ -20,6 +20,9 @@ $(document).ready ->
$(".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
"opacity": 0
"display": "none"
dropzone = $(".div-dropzone").dropzone( dropzone = $(".div-dropzone").dropzone(
url: project_image_path_upload url: project_image_path_upload
...@@ -66,13 +69,17 @@ $(document).ready -> ...@@ -66,13 +69,17 @@ $(document).ready ->
return return
sending: -> sending: ->
$(".div-dropzone-spinner").css "opacity", 0.7 $(".div-dropzone-spinner").css
"opacity": 0.7
"display": "inherit"
return return
complete: -> complete: ->
$(".dz-preview").remove() $(".dz-preview").remove()
$(".markdown-area").trigger "input" $(".markdown-area").trigger "input"
$(".div-dropzone-spinner").css "opacity", 0 $(".div-dropzone-spinner").css
"opacity": 0
"display": "none"
return return
) )
...@@ -163,10 +170,14 @@ $(document).ready -> ...@@ -163,10 +170,14 @@ $(document).ready ->
val + url + "\n" val + url + "\n"
showSpinner = (e) -> showSpinner = (e) ->
$(".div-dropzone-spinner").css "opacity", 0.7 $(".div-dropzone-spinner").css
"opacity": 0.7
"display": "inherit"
closeSpinner = -> closeSpinner = ->
$(".div-dropzone-spinner").css "opacity", 0 $(".div-dropzone-spinner").css
"opacity": 0
"display": "none"
showError = (message) -> showError = (message) ->
checkIfMsgExists = $(".error-alert").children().length checkIfMsgExists = $(".error-alert").children().length
......
...@@ -321,7 +321,9 @@ class Notes ...@@ -321,7 +321,9 @@ class Notes
GitLab.GfmAutoComplete.setup() GitLab.GfmAutoComplete.setup()
form = note.find(".note-edit-form") form = note.find(".note-edit-form")
form.show() form.show()
form.find("textarea").focus() textarea = form.find("textarea")
textarea.focus()
disableButtonIfEmptyField textarea, form.find(".js-comment-button")
### ###
Called in response to clicking the edit note link Called in response to clicking the edit note link
......
@Pager = @Pager =
limit: 0 init: (@limit = 0, preload, @disable = false) ->
offset: 0 @loading = $(".loading")
disable: false
init: (limit, preload) ->
@limit = limit
if preload if preload
@offset = 0 @offset = 0
@getOld() @getOld()
else else
@offset = limit @offset = @limit
@initLoadMore() @initLoadMore()
getOld: -> getOld: ->
$(".loading").show() @loading.show()
$.ajax $.ajax
type: "GET" type: "GET"
url: location.href url: location.href
data: "limit=" + @limit + "&offset=" + @offset data: "limit=" + @limit + "&offset=" + @offset
complete: -> complete: =>
$(".loading").hide() @loading.hide()
success: (data) -> success: (data) ->
Pager.append(data.count, data.html) Pager.append(data.count, data.html)
dataType: "json" dataType: "json"
...@@ -39,6 +36,7 @@ ...@@ -39,6 +36,7 @@
ceaseFire: -> ceaseFire: ->
Pager.disable Pager.disable
callback: (i) -> callback: (i) =>
$(".loading").show() unless @loading.is(':visible')
Pager.getOld() @loading.show()
Pager.getOld()
...@@ -54,3 +54,8 @@ $ -> ...@@ -54,3 +54,8 @@ $ ->
$.cookie('hide_no_ssh_message', 'false', { path: path }) $.cookie('hide_no_ssh_message', 'false', { path: path })
$(@).parents('.no-ssh-key-message').hide() $(@).parents('.no-ssh-key-message').hide()
e.preventDefault() e.preventDefault()
$('.project-side .star').on 'ajax:success', (e, data, status, xhr) ->
$(@).toggleClass('on').find('.count').html(data.star_count)
.on 'ajax:error', (e, xhr, status, error) ->
new Flash('Star toggle failed. Try again later.', 'alert')
...@@ -59,4 +59,4 @@ ...@@ -59,4 +59,4 @@
/** /**
* Styles for responsive sidebar * Styles for responsive sidebar
*/ */
@import "semantic-ui/modules/sidebar" @import "semantic-ui/modules/sidebar";
...@@ -4,3 +4,9 @@ ...@@ -4,3 +4,9 @@
.js-details-container .content.hide { display: block; } .js-details-container .content.hide { display: block; }
.js-details-container.open .content { display: block; } .js-details-container.open .content { display: block; }
.js-details-container.open .content.hide { display: none; } .js-details-container.open .content.hide { display: none; }
// Toggle between two states.
.js-toggler-container .turn-on { display: block; }
.js-toggler-container .turn-off { display: none; }
.js-toggler-container.on .turn-on { display: none; }
.js-toggler-container.on .turn-off { display: block; }
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
vertical-align: middle; vertical-align: middle;
cursor: pointer; cursor: pointer;
background-image: none; background-image: none;
border: 1px solid transparent; border: $btn-border;
white-space: nowrap; white-space: nowrap;
padding: 6px 12px; padding: 6px 12px;
font-size: 13px; font-size: 13px;
...@@ -19,7 +19,6 @@ ...@@ -19,7 +19,6 @@
user-select: none; user-select: none;
color: #444444; color: #444444;
background-color: #fff; background-color: #fff;
border-color: #ccc;
text-shadow: none; text-shadow: none;
&.hover, &.hover,
......
...@@ -350,3 +350,18 @@ table { ...@@ -350,3 +350,18 @@ table {
.footer-links a { .footer-links a {
margin-right: 15px; margin-right: 15px;
} }
.search_box {
position: relative;
padding: 30px;
text-align: center;
background-color: #F9F9F9;
border: 1px solid #DDDDDD;
border-radius: 0px;
}
.search_glyph {
color: #555;
font-size: 42px;
}
...@@ -10,6 +10,8 @@ $hover: #D9EDF7; ...@@ -10,6 +10,8 @@ $hover: #D9EDF7;
$link_color: #446e9b; $link_color: #446e9b;
$link_hover_color: #2FA0BB; $link_hover_color: #2FA0BB;
$btn-border: 1px solid #ccc;
/* /*
* Success colors (green) * Success colors (green)
*/ */
......
...@@ -244,6 +244,7 @@ li.commit { ...@@ -244,6 +244,7 @@ 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;
} }
} }
...@@ -89,6 +89,10 @@ ...@@ -89,6 +89,10 @@
} }
} }
.project-description {
overflow: hidden;
}
.project-access-icon { .project-access-icon {
margin-left: 10px; margin-left: 10px;
float: left; float: left;
......
...@@ -40,14 +40,17 @@ ...@@ -40,14 +40,17 @@
font-size: 12px; font-size: 12px;
.old { .old {
span.idiff { span.idiff {
background-color: #FAA; background-color: #F99;
} }
} }
.new { .new {
span.idiff { span.idiff {
background-color: #AFA; background-color: #8F8;
} }
} }
.unfold {
cursor: pointer;
}
.file-mode-changed { .file-mode-changed {
padding: 10px; padding: 10px;
......
.explore-title {
text-align: center;
h3 {
font-weight: normal;
font-size: 30px;
}
}
...@@ -63,26 +63,10 @@ ...@@ -63,26 +63,10 @@
@media (min-width: 800px) { .issues_bulk_update .select2-container { min-width: 120px; } } @media (min-width: 800px) { .issues_bulk_update .select2-container { min-width: 120px; } }
@media (min-width: 1200px) { .issues_bulk_update .select2-container { min-width: 160px; } } @media (min-width: 1200px) { .issues_bulk_update .select2-container { min-width: 160px; } }
.issues-holder { .issues_bulk_update {
.issues_filters { .select2-container .select2-choice {
} color: #444 !important;
font-weight: 500;
.issues_bulk_update {
margin: 0;
form {
float:left;
}
.update_selected_issues {
margin-left: 4px;
}
.select2-container .select2-choice {
height: 32px;
line-height: 28px;
color: #444 !important;
font-weight: 500;
}
} }
} }
...@@ -110,7 +94,7 @@ ...@@ -110,7 +94,7 @@
} }
} }
.issue-show-labels .label { .issue-show-labels .color-label {
padding: 6px 10px; padding: 6px 10px;
} }
......
.suggest-colors {
margin-top: 5px;
a {
@include border-radius(4px);
width: 30px;
height: 30px;
display: inline-block;
margin-right: 10px;
}
}
.manage-labels-list {
.label {
padding: 9px;
font-size: 14px;
}
}
.color-label {
padding: 3px 4px;
}
...@@ -13,6 +13,10 @@ ...@@ -13,6 +13,10 @@
max-width: 100%; max-width: 100%;
margin-bottom: 20px; margin-bottom: 20px;
} }
&.default-brand-image {
margin: 0 80px;
}
} }
.login-logo{ .login-logo{
......
/** /**
* MR -> show: Automerge widget * MR -> show: Automerge widget
* *
*/ */
.automerge_widget { .automerge_widget {
...@@ -48,10 +48,10 @@ ...@@ -48,10 +48,10 @@
.label-branch { .label-branch {
@include border-radius(4px); @include border-radius(4px);
padding: 2px 4px; padding: 3px 4px;
border: none; border: none;
background: #555; background: $hover;
color: #fff; color: #333;
font-family: $monospace_font; font-family: $monospace_font;
font-weight: normal; font-weight: normal;
overflow: hidden; overflow: hidden;
......
...@@ -190,28 +190,40 @@ ul.nav.nav-projects-tabs { ...@@ -190,28 +190,40 @@ ul.nav.nav-projects-tabs {
.project-side { .project-side {
.btn-block { .btn-block {
background-image: none; background-image: none;
.btn,
&.btn, .btn, &.btn {
&.btn-group ul.dropdown-menu { white-space: normal;
text-align: left;
padding: 10px 15px;
background-color: #F1f1f1; background-color: #F1f1f1;
border-color: #EEE; border-color: #EEE;
&:hover { &:hover {
background-color: #eee; background-color: #eee;
border-color: #DDD; border-color: #DDD;
} }
} }
&.btn-group-justified {
.btn { .count {
width: 100%; float: right;
} font-weight: 500;
.dropdown-toggle { text-shadow: 0 1px #FFF;
width: 26px;
}
} }
ul {
&.btn-group-justified {
.btn {
width: 100%;
}
.dropdown-toggle {
width: 30px;
padding: 10px;
}
ul {
width: 100%; width: 100%;
}
} }
} }
.project-fork-icon { .project-fork-icon {
float: left; float: left;
font-size: 26px; font-size: 26px;
......
class Admin::EmailsController < Admin::ApplicationController
def show
end
def create
AdminEmailsWorker.perform_async(params[:recipients], params[:subject], params[:body])
redirect_to admin_email_path, notice: 'Email sent'
end
end
...@@ -39,12 +39,13 @@ class Admin::UsersController < Admin::ApplicationController ...@@ -39,12 +39,13 @@ class Admin::UsersController < Admin::ApplicationController
def create def create
opts = { opts = {
force_random_password: true, force_random_password: true,
password_expires_at: Time.now password_expires_at: nil
} }
@user = User.new(user_params.merge(opts)) @user = User.new(user_params.merge(opts))
@user.created_by_id = current_user.id @user.created_by_id = current_user.id
@user.generate_password @user.generate_password
@user.generate_reset_token
@user.skip_confirmation! @user.skip_confirmation!
respond_to do |format| respond_to do |format|
......
...@@ -201,16 +201,10 @@ class ApplicationController < ActionController::Base ...@@ -201,16 +201,10 @@ class ApplicationController < ActionController::Base
def ldap_security_check def ldap_security_check
if current_user && current_user.requires_ldap_check? if current_user && current_user.requires_ldap_check?
gitlab_ldap_access do |access| unless Gitlab::LDAP::Access.allowed?(current_user)
if access.allowed?(current_user) sign_out current_user
access.update_permissions(current_user) flash[:alert] = "Access denied for your LDAP account."
current_user.last_credential_check_at = Time.now redirect_to new_user_session_path
current_user.save
else
sign_out current_user
flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path
end
end end
end end
end end
......
...@@ -46,11 +46,11 @@ class DashboardController < ApplicationController ...@@ -46,11 +46,11 @@ class DashboardController < ApplicationController
@projects = @projects.where(namespace_id: Group.find_by(name: params[:group])) if params[:group].present? @projects = @projects.where(namespace_id: Group.find_by(name: params[:group])) if params[:group].present?
@projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present?
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace)
@projects = @projects.tagged_with(params[:label]) if params[:label].present? @projects = @projects.tagged_with(params[:tag]) if params[:tag].present?
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]).per(30) @projects = @projects.page(params[:page]).per(30)
@labels = current_user.authorized_projects.tags_on(:labels) @tags = current_user.authorized_projects.tags_on(:tags)
@groups = current_user.authorized_groups @groups = current_user.authorized_groups
end end
......
class Public::ProjectsController < ApplicationController class Explore::GroupsController < ApplicationController
skip_before_filter :authenticate_user!, skip_before_filter :authenticate_user!,
:reject_blocked, :set_current_user_for_observers, :reject_blocked, :set_current_user_for_observers,
:add_abilities :add_abilities
layout 'public' layout "explore"
def index def index
@projects = Project.publicish(current_user) @groups = GroupsFinder.new.execute(current_user)
@projects = @projects.search(params[:search]) if params[:search].present? @groups = @groups.search(params[:search]) if params[:search].present?
@projects = @projects.sort(@sort = params[:sort]) @groups = @groups.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page]).per(20) @groups = @groups.page(params[:page]).per(20)
end end
end end
class Explore::ProjectsController < ApplicationController
skip_before_filter :authenticate_user!,
:reject_blocked,
:add_abilities
layout 'explore'
def index
@projects = ProjectsFinder.new.execute(current_user)
@projects = @projects.search(params[:search]) if params[:search].present?
@projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page]).per(20)
end
def trending
@trending_projects = TrendingProjectsFinder.new.execute(current_user)
@trending_projects = @trending_projects.page(params[:page]).per(10)
end
def starred
@starred_projects = ProjectsFinder.new.execute(current_user)
@starred_projects = @starred_projects.order('star_count DESC')
@starred_projects = @starred_projects.page(params[:page]).per(10)
end
end
...@@ -21,15 +21,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController ...@@ -21,15 +21,12 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
@user = Gitlab::LDAP::User.find_or_create(oauth) @user = Gitlab::LDAP::User.find_or_create(oauth)
@user.remember_me = true if @user.persisted? @user.remember_me = true if @user.persisted?
gitlab_ldap_access do |access| # Do additional LDAP checks for the user filter and EE features
if access.allowed?(@user) if Gitlab::LDAP::Access.allowed?(@user)
access.update_permissions(@user) sign_in_and_redirect(@user)
access.update_email(@user) else
sign_in_and_redirect(@user) flash[:alert] = "Access denied for your LDAP account."
else redirect_to new_user_session_path
flash[:alert] = "Access denied for your LDAP account."
redirect_to new_user_session_path
end
end end
end end
......
...@@ -11,6 +11,11 @@ class Profiles::PasswordsController < ApplicationController ...@@ -11,6 +11,11 @@ class Profiles::PasswordsController < ApplicationController
end end
def create def create
unless @user.valid_password?(user_params[:current_password])
redirect_to new_profile_password_path, alert: 'You must provide a valid current password'
return
end
new_password = user_params[:password] new_password = user_params[:password]
new_password_confirmation = user_params[:password_confirmation] new_password_confirmation = user_params[:password_confirmation]
......
...@@ -25,6 +25,21 @@ class Projects::BlobController < Projects::ApplicationController ...@@ -25,6 +25,21 @@ class Projects::BlobController < Projects::ApplicationController
end end
end end
def diff
@form = UnfoldForm.new(params)
@lines = @blob.data.lines[@form.since - 1..@form.to - 1]
if @form.bottom?
@match_line = ''
else
lines_length = @lines.length - 1
line = [@form.since, lines_length].join(',')
@match_line = "@@ -#{line}+#{line} @@"
end
render layout: false
end
private private
def blob def blob
......
...@@ -8,14 +8,21 @@ class Projects::CompareController < Projects::ApplicationController ...@@ -8,14 +8,21 @@ class Projects::CompareController < Projects::ApplicationController
end end
def show def show
compare = Gitlab::Git::Compare.new(@repository.raw_repository, params[:from], params[:to], MergeRequestDiff::COMMITS_SAFE_SIZE) base_ref = params[:from]
head_ref = params[:to]
@commits = compare.commits compare_result = CompareService.new.execute(
@commit = compare.commit current_user,
@diffs = compare.diffs @project,
@refs_are_same = compare.same head_ref,
@line_notes = [] @project,
@diff_timeout = compare.timeout base_ref
)
@commits = compare_result.commits
@diffs = compare_result.diffs
@commit = @commits.last
@line_notes = []
end end
def create def create
......
...@@ -24,7 +24,17 @@ class Projects::HooksController < Projects::ApplicationController ...@@ -24,7 +24,17 @@ class Projects::HooksController < Projects::ApplicationController
end end
def test def test
TestHookService.new.execute(hook, current_user) if !@project.empty_repo?
status = TestHookService.new.execute(hook, current_user)
if status
flash[:notice] = 'Hook successfully executed.'
else
flash[:alert] = 'Hook execution failed. '\
'Ensure hook URL is correct and service is up.'
end
else
flash[:alert] = 'Hook execution failed. Ensure the project has commits.'
end
redirect_to :back redirect_to :back
end end
......
...@@ -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, :label_list, :state_event :milestone_id, :state_event, label_ids: []
) )
end end
end end
class Projects::LabelsController < Projects::ApplicationController class Projects::LabelsController < Projects::ApplicationController
before_filter :module_enabled before_filter :module_enabled
before_filter :label, only: [:edit, :update, :destroy]
before_filter :authorize_labels! before_filter :authorize_labels!
before_filter :authorize_admin_labels!, except: [:index]
respond_to :js, :html respond_to :js, :html
def index def index
@labels = @project.issues_labels @labels = @project.labels.order_by_name.page(params[:page]).per(20)
end
def new
@label = @project.labels.new
end
def create
@label = @project.labels.create(label_params)
if @label.valid?
redirect_to project_labels_path(@project)
else
render 'new'
end
end
def edit
end
def update
if @label.update_attributes(label_params)
redirect_to project_labels_path(@project)
else
render 'edit'
end
end end
def generate def generate
...@@ -21,6 +47,15 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -21,6 +47,15 @@ class Projects::LabelsController < Projects::ApplicationController
end end
end end
def destroy
@label.destroy
respond_to do |format|
format.html { redirect_to project_labels_path(@project), notice: 'Label was removed' }
format.js { render nothing: true }
end
end
protected protected
def module_enabled def module_enabled
...@@ -28,4 +63,16 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -28,4 +63,16 @@ class Projects::LabelsController < Projects::ApplicationController
return render_404 return render_404
end end
end end
def label_params
params.require(:label).permit(:title, :color)
end
def label
@label = @project.labels.find(params[:id])
end
def authorize_admin_labels!
return render_404 unless can?(current_user, :admin_label, @project)
end
end end
...@@ -70,7 +70,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -70,7 +70,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@target_project = merge_request.target_project @target_project = merge_request.target_project
@source_project = merge_request.source_project @source_project = merge_request.source_project
@commits = @merge_request.compare_commits @commits = @merge_request.compare_commits
@commit = @merge_request.compare_base_commit @commit = @merge_request.compare_commits.last
@diffs = @merge_request.compare_diffs @diffs = @merge_request.compare_diffs
@note_counts = Note.where(commit_id: @commits.map(&:id)). @note_counts = Note.where(commit_id: @commits.map(&:id)).
group(:commit_id).count group(:commit_id).count
...@@ -242,7 +242,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -242,7 +242,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_list :state_event, :description, label_ids: []
) )
end end
end end
...@@ -14,11 +14,7 @@ class Projects::RepositoriesController < Projects::ApplicationController ...@@ -14,11 +14,7 @@ class Projects::RepositoriesController < Projects::ApplicationController
render_404 and return render_404 and return
end end
storage_path = Gitlab.config.gitlab.repository_downloads_path file_path = ArchiveRepositoryService.new.execute(@project, params[:ref], params[:format])
@repository.clean_old_archives
file_path = @repository.archive_repo(params[:ref], storage_path, params[:format].downcase)
if file_path if file_path
# Send file to user # Send file to user
......
...@@ -7,6 +7,7 @@ class Projects::TeamMembersController < Projects::ApplicationController ...@@ -7,6 +7,7 @@ class Projects::TeamMembersController < Projects::ApplicationController
def index def index
@group = @project.group @group = @project.group
@users_projects = @project.users_projects.order('project_access DESC') @users_projects = @project.users_projects.order('project_access DESC')
@project_group_links = @project.project_group_links
end end
def new def new
......
...@@ -12,12 +12,10 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -12,12 +12,10 @@ class Projects::WikisController < Projects::ApplicationController
def show def show
@page = @project_wiki.find_page(params[:id], params[:version_id]) @page = @project_wiki.find_page(params[:id], params[:version_id])
gollum_wiki = @project_wiki.wiki
file = gollum_wiki.file(params[:id], gollum_wiki.ref, true)
if @page if @page
render 'show' render 'show'
elsif file elsif file = @project_wiki.find_file(params[:id], params[:version_id])
if file.on_disk? if file.on_disk?
send_file file.on_disk_path, disposition: 'inline' send_file file.on_disk_path, disposition: 'inline'
else else
......
...@@ -60,6 +60,8 @@ class ProjectsController < ApplicationController ...@@ -60,6 +60,8 @@ class ProjectsController < ApplicationController
@events = event_filter.apply_filter(@events) @events = event_filter.apply_filter(@events)
@events = @events.limit(limit).offset(params[:offset] || 0) @events = @events.limit(limit).offset(params[:offset] || 0)
@show_star = !(current_user && current_user.starred?(@project))
respond_to do |format| respond_to do |format|
format.html do format.html do
if @project.empty_repo? if @project.empty_repo?
...@@ -167,6 +169,12 @@ class ProjectsController < ApplicationController ...@@ -167,6 +169,12 @@ class ProjectsController < ApplicationController
end end
end end
def toggle_star
current_user.toggle_star(@project)
@project.reload
render json: { star_count: @project.star_count }
end
private private
def upload_path def upload_path
...@@ -188,7 +196,7 @@ class ProjectsController < ApplicationController ...@@ -188,7 +196,7 @@ class ProjectsController < ApplicationController
def project_params def project_params
params.require(:project).permit( params.require(:project).permit(
:name, :path, :description, :issues_tracker, :label_list, :name, :path, :description, :issues_tracker, :tag_list,
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch, :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :merge_requests_template :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :merge_requests_template
) )
......
class SessionsController < Devise::SessionsController class SessionsController < Devise::SessionsController
def new def new
redirect_url = if request.referer.present? redirect_path = if request.referer.present? && (params['redirect_to_referer'] == 'yes')
referer_uri = URI(request.referer) referer_uri = URI(request.referer)
if referer_uri.host == Gitlab.config.gitlab.host if referer_uri.host == Gitlab.config.gitlab.host
referer_uri.path referer_uri.path
...@@ -12,7 +12,11 @@ class SessionsController < Devise::SessionsController ...@@ -12,7 +12,11 @@ class SessionsController < Devise::SessionsController
request.fullpath request.fullpath
end end
store_location_for(:redirect, redirect_url) # Prevent a 'you are already signed in' message directly after signing:
# we should never redirect to '/users/sign_in' after signing in successfully.
unless redirect_path == '/users/sign_in'
store_location_for(:redirect, redirect_path)
end
super super
end end
......
class UnsubscribesController < ApplicationController
skip_before_filter :authenticate_user!,
:reject_blocked, :set_current_user_for_observers,
:add_abilities
layout 'public_users'
def show
@user = get_user
end
def create
@user = get_user
if @user
@user.admin_unsubscribe!
Notify.send_unsubscribed_notification(@user).deliver
end
redirect_to new_user_session_path, notice: 'You have been unsubscribed'
end
protected
def get_user
@email = Base64.urlsafe_decode64(params[:email])
User.where(email: @email).first
end
end
...@@ -125,7 +125,13 @@ class BaseFinder ...@@ -125,7 +125,13 @@ class BaseFinder
def by_label(items) def by_label(items)
if params[:label_name].present? if params[:label_name].present?
items = items.tagged_with(params[:label_name]) label_names = params[:label_name].split(",")
item_ids = LabelLink.joins(:label).
where('labels.title in (?)', label_names).
where(target_type: klass.name).pluck(:target_id)
items = items.where(id: item_ids)
end end
items items
......
...@@ -32,20 +32,29 @@ class ProjectsFinder ...@@ -32,20 +32,29 @@ class ProjectsFinder
# internal projects # internal projects
# joined projects # joined projects
# #
group.projects.where( projects_ids = projects_members.pluck(:project_id)
"projects.id IN (?) OR projects.visibility_level IN (?)",
projects_members.pluck(:project_id),
Project.public_and_internal_levels
)
else else
# User has no access to group or group projects # User has no access to group or group projects
# or has access through shared project
# #
# Return only: # Return only:
# public projects # public projects
# internal projects # internal projects
# # shared projects
group.projects.public_and_internal_only
projects_ids = []
ProjectGroupLink.where(project_id: group.projects).each do |shared_project|
if shared_project.group.users.include?(current_user) || shared_project.project.users.include?(current_user)
projects_ids << shared_project.project.id
end
end
end end
group.projects.where(
"projects.id IN (?) OR projects.visibility_level IN (?)",
projects_ids,
Project.public_and_internal_levels
)
end end
else else
# Not authenticated # Not authenticated
......
class TrendingProjectsFinder
def execute(current_user, start_date = nil)
start_date ||= Date.today - 1.month
projects = projects_for(current_user)
# Determine trending projects based on comments count
# for period of time - ex. month
projects.joins(:notes).where('notes.created_at > ?', start_date).
select("projects.*, count(notes.id) as ncount").
group("projects.id").order("ncount DESC")
end
private
def projects_for(current_user)
ProjectsFinder.new.execute(current_user)
end
end
module AdminEmailHelper
def admin_email_grouped_recipient_options
options_for_select([['All GitLab users', 'all']]) +
grouped_options_for_select(
'Groups' => Group.pluck(:name, :id).map{ |name, id| [name, "group-#{id}"] },
'Projects' => grouped_project_list
)
end
protected
def grouped_project_list
Group.includes(:projects).flat_map do |group|
group.human_name
group.projects.map do |project|
["#{group.human_name} / #{project.name}", "project-#{project.id}"]
end
end
end
end
\ No newline at end of file
...@@ -221,7 +221,18 @@ module ApplicationHelper ...@@ -221,7 +221,18 @@ module ApplicationHelper
end end
def render_markup(file_name, file_content) def render_markup(file_name, file_content)
GitHub::Markup.render(file_name, file_content).html_safe GitHub::Markup.render(file_name, file_content).
force_encoding(file_content.encoding).html_safe
rescue RuntimeError
simple_format(file_content)
end
def markup?(filename)
Gitlab::MarkdownHelper.markup?(filename)
end
def gitlab_markdown?(filename)
Gitlab::MarkdownHelper.gitlab_markdown?(filename)
end end
def spinner(text = nil, visible = false) def spinner(text = nil, visible = false)
......
...@@ -232,4 +232,16 @@ module CommitsHelper ...@@ -232,4 +232,16 @@ module CommitsHelper
def diff_file_mode_changed?(diff) def diff_file_mode_changed?(diff)
diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode
end end
def unfold_bottom_class(bottom)
(bottom) ? 'js-unfold-bottom' : ''
end
def view_file_btn(commit_sha, diff, project)
link_to project_blob_path(project, tree_join(commit_sha, diff.new_path)),
class: 'btn btn-small view-file js-view-file' do
raw('View file @') + content_tag(:span, commit_sha[0..6],
class: 'commit-short-id')
end
end
end end
...@@ -179,7 +179,11 @@ module GitlabMarkdownHelper ...@@ -179,7 +179,11 @@ module GitlabMarkdownHelper
if @commit if @commit
@commit.id @commit.id
elsif @repository && !@repository.empty? elsif @repository && !@repository.empty?
@repository.head_commit.sha if @ref
@repository.commit(@ref).try(:sha)
else
@repository.head_commit.sha
end
end end
end end
......
module LabelsHelper module LabelsHelper
def issue_label_names def project_label_names
@project.issues_labels.map(&:name) @project.labels.pluck(:title)
end end
def labels_autocomplete_source def render_colored_label(label)
labels = @project.issues_labels label_color = label.color || Label::DEFAULT_COLOR
labels = labels.map{ |l| { label: l.name, value: l.name } } text_color = text_color_for_bg(label_color)
labels.to_json
content_tag :span, class: 'label color-label', style: "background:#{label_color};color:#{text_color}" do
label.name
end
end
def suggested_colors
[
'#D9534F',
'#F0AD4E',
'#428BCA',
'#5CB85C',
'#34495E',
'#7F8C8D',
'#8E44AD',
'#FFECDB'
]
end end
def label_css_class(name) def text_color_for_bg(bg_color)
klass = Gitlab::IssuesLabels r, g, b = bg_color.slice(1,7).scan(/.{2}/).map(&:hex)
case name.downcase if (r + g + b) > 500
when *klass.warning_labels "#333"
'label-warning'
when *klass.neutral_labels
'label-primary'
when *klass.positive_labels
'label-success'
when *klass.important_labels
'label-danger'
else else
'label-info' "#FFF"
end end
end end
end end
...@@ -122,6 +122,40 @@ module ProjectsHelper ...@@ -122,6 +122,40 @@ module ProjectsHelper
options_for_select(values, current_tracker) options_for_select(values, current_tracker)
end end
def link_to_toggle_star(title, starred, signed_in)
cls = 'btn btn-block'
cls += ' disabled' unless signed_in
toggle_html = content_tag('span', class: 'toggle') do
toggle_text = if starred
'Unstar'
else
'Star'
end
content_tag('i', ' ', class: 'icon-star') + toggle_text
end
count_html = content_tag('span', class: 'count') do
@project.star_count.to_s
end
link_opts = {
title: title,
class: cls,
method: :post,
remote: true,
data: {type: 'json'}
}
content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do
link_to toggle_star_project_path(@project), link_opts do
toggle_html + count_html
end
end
end
private private
def get_project_nav_tabs(project, current_user) def get_project_nav_tabs(project, current_user)
......
...@@ -21,6 +21,16 @@ module TreeHelper ...@@ -21,6 +21,16 @@ module TreeHelper
tree.html_safe tree.html_safe
end end
def render_readme(readme)
if gitlab_markdown?(readme.name)
preserve(markdown(readme.data))
elsif markup?(readme.name)
render_markup(readme.name, readme.data)
else
simple_format(readme.data)
end
end
# Return an image icon depending on the file type # Return an image icon depending on the file type
# #
# type - String type of the tree item; either 'folder' or 'file' # type - String type of the tree item; either 'folder' or 'file'
...@@ -38,24 +48,6 @@ module TreeHelper ...@@ -38,24 +48,6 @@ module TreeHelper
"file_#{hexdigest(content.name)}" "file_#{hexdigest(content.name)}"
end end
# Public: Determines if a given filename is compatible with GitHub::Markup.
#
# filename - Filename string to check
#
# Returns boolean
def markup?(filename)
filename.downcase.end_with?(*%w(.textile .rdoc .org .creole
.mediawiki .rst .adoc .asciidoc .pod))
end
def gitlab_markdown?(filename)
filename.downcase.end_with?(*%w(.mdown .md .markdown))
end
def plain_text_readme? filename
filename =~ /^README(.txt)?$/i
end
# Simple shortcut to File.join # Simple shortcut to File.join
def tree_join(*args) def tree_join(*args)
File.join(*args) File.join(*args)
......
module Emails
module AdminNotification
def send_admin_notification(user_id, subject, body)
email = recipient(user_id)
@unsubscribe_url = unsubscribe_url(email: Base64.urlsafe_encode64(email))
@body = body
mail to: email, subject: subject
end
def send_unsubscribed_notification(user_id)
email = recipient(user_id)
mail to: email, subject: "Unsubscribed from GitLab administrator notifications"
end
end
end
...@@ -49,9 +49,10 @@ module Emails ...@@ -49,9 +49,10 @@ module Emails
@updated_by = User.find updated_by_user_id @updated_by = User.find updated_by_user_id
@target_url = project_merge_request_url(@project, @merge_request) @target_url = project_merge_request_url(@project, @merge_request)
set_reference("merge_request_#{merge_request_id}") set_reference("merge_request_#{merge_request_id}")
mail(from: sender(updated_by_user_id), mail_answer_thread(@merge_request,
to: recipient(recipient_id), from: sender(updated_by_user_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid}) #{@mr_status}")) to: recipient(recipient_id),
subject: subject("#{@merge_request.title} (##{@merge_request.iid}) #{@mr_status}"))
end end
end end
......
module Emails module Emails
module Profile module Profile
def new_user_email(user_id, password) def new_user_email(user_id, password, token = nil)
@user = User.find(user_id) @user = User.find(user_id)
@password = password @password = password
@target_url = user_url(@user) @target_url = user_url(@user)
@token = token
mail(to: @user.email, subject: subject("Account was created for you")) mail(to: @user.email, subject: subject("Account was created for you"))
end end
......
class Notify < ActionMailer::Base class Notify < ActionMailer::Base
include ActionDispatch::Routing::PolymorphicRoutes include ActionDispatch::Routing::PolymorphicRoutes
include Emails::AdminNotification
include Emails::Issues include Emails::Issues
include Emails::MergeRequests include Emails::MergeRequests
include Emails::Notes include Emails::Notes
......
...@@ -142,6 +142,7 @@ class Ability ...@@ -142,6 +142,7 @@ class Ability
:write_wiki, :write_wiki,
:modify_issue, :modify_issue,
:admin_issue, :admin_issue,
:admin_label,
:push_code :push_code
] ]
end end
......
...@@ -13,6 +13,8 @@ module Issuable ...@@ -13,6 +13,8 @@ module Issuable
belongs_to :assignee, class_name: "User" belongs_to :assignee, class_name: "User"
belongs_to :milestone belongs_to :milestone
has_many :notes, as: :noteable, dependent: :destroy has_many :notes, as: :noteable, dependent: :destroy
has_many :label_links, as: :target, dependent: :destroy
has_many :labels, through: :label_links
validates :author, presence: true validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 } validates :title, presence: true, length: { within: 0..255 }
...@@ -131,4 +133,20 @@ module Issuable ...@@ -131,4 +133,20 @@ module Issuable
object_attributes: self.attributes object_attributes: self.attributes
} }
end end
def label_names
labels.order('title ASC').pluck(:title)
end
def remove_labels
labels.delete_all
end
def add_labels_by_names(label_names)
label_names.each do |label_name|
label = project.labels.create_with(
color: Label::DEFAULT_COLOR).find_or_create_by(title: label_name.strip)
self.labels << label
end
end
end end
...@@ -70,6 +70,12 @@ class Event < ActiveRecord::Base ...@@ -70,6 +70,12 @@ class Event < ActiveRecord::Base
author_id: user.id author_id: user.id
) )
end end
def reset_event_cache_for(target)
Event.where(target_id: target.id, target_type: target.class.to_s).
order('id DESC').limit(100).
update_all(updated_at: Time.now)
end
end end
def proper? def proper?
......
...@@ -89,4 +89,20 @@ class Group < Namespace ...@@ -89,4 +89,20 @@ class Group < Namespace
def ldap_access def ldap_access
ldap_group_links.first.try(:group_access) ldap_group_links.first.try(:group_access)
end end
class << self
def search(query)
where("LOWER(namespaces.name) LIKE :query", query: "%#{query.downcase}%")
end
def sort(method)
case method.to_s
when "newest" then reorder("namespaces.created_at DESC")
when "oldest" then reorder("namespaces.created_at ASC")
when "recently_updated" then reorder("namespaces.updated_at DESC")
when "last_updated" then reorder("namespaces.updated_at ASC")
else reorder("namespaces.path, namespaces.name ASC")
end
end
end
end end
...@@ -32,9 +32,6 @@ class Issue < ActiveRecord::Base ...@@ -32,9 +32,6 @@ class Issue < ActiveRecord::Base
scope :of_group, ->(group) { where(project_id: group.project_ids) } scope :of_group, ->(group) { where(project_id: group.project_ids) }
scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) } scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) }
acts_as_taggable_on :labels
scope :cared, ->(user) { where(assignee_id: user) } scope :cared, ->(user) { where(assignee_id: user) }
scope :open_for, ->(user) { opened.assigned_to(user) } scope :open_for, ->(user) { opened.assigned_to(user) }
...@@ -67,8 +64,6 @@ class Issue < ActiveRecord::Base ...@@ -67,8 +64,6 @@ class Issue < ActiveRecord::Base
# Thus it will automatically generate a new fragment # Thus it will automatically generate a new fragment
# when the event is updated because the key changes. # when the event is updated because the key changes.
def reset_events_cache def reset_events_cache
Event.where(target_id: self.id, target_type: 'Issue'). Event.reset_event_cache_for(self)
order('id DESC').limit(100).
update_all(updated_at: Time.now)
end end
end end
class Label < ActiveRecord::Base
DEFAULT_COLOR = '#428BCA'
belongs_to :project
has_many :label_links, dependent: :destroy
has_many :issues, through: :label_links, source: :target, source_type: 'Issue'
validates :color,
format: { with: /\A#[0-9A-Fa-f]{6}\Z/ },
allow_blank: false
validates :project, presence: true
# Don't allow '?', '&', and ',' for label titles
validates :title,
presence: true,
format: { with: /\A[^&\?,&]+\z/ },
uniqueness: { scope: :project_id }
scope :order_by_name, -> { reorder("labels.title ASC") }
alias_attribute :name, :title
def open_issues_count
issues.opened.count
end
end
class LabelLink < ActiveRecord::Base
belongs_to :target, polymorphic: true
belongs_to :label
validates :target, presence: true
validates :label, presence: true
end
...@@ -44,12 +44,9 @@ class MergeRequest < ActiveRecord::Base ...@@ -44,12 +44,9 @@ class MergeRequest < ActiveRecord::Base
# Temporary fields to store compare vars # Temporary fields to store compare vars
# when creating new merge request # when creating new merge request
attr_accessor :can_be_created, :compare_failed, :compare_base_commit, attr_accessor :can_be_created, :compare_failed,
:compare_commits, :compare_diffs :compare_commits, :compare_diffs
ActsAsTaggableOn.strict_case_match = true
acts_as_taggable_on :labels
state_machine :state, initial: :opened do state_machine :state, initial: :opened do
event :close do event :close do
transition [:reopened, :opened] => :closed transition [:reopened, :opened] => :closed
...@@ -287,9 +284,7 @@ class MergeRequest < ActiveRecord::Base ...@@ -287,9 +284,7 @@ class MergeRequest < ActiveRecord::Base
# Thus it will automatically generate a new fragment # Thus it will automatically generate a new fragment
# when the event is updated because the key changes. # when the event is updated because the key changes.
def reset_events_cache def reset_events_cache
Event.where(target_id: self.id, target_type: 'MergeRequest'). Event.reset_event_cache_for(self)
order('id DESC').limit(100).
update_all(updated_at: Time.now)
end end
def merge_commit_message def merge_commit_message
......
...@@ -83,11 +83,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -83,11 +83,7 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect array of Git::Commit objects # Collect array of Git::Commit objects
# between target and source branches # between target and source branches
def unmerged_commits def unmerged_commits
commits = if merge_request.for_fork? commits = compare_result.commits
compare_action.commits
else
repository.commits_between(target_branch, source_branch)
end
if commits.present? if commits.present?
commits = Commit.decorate(commits). commits = Commit.decorate(commits).
...@@ -147,12 +143,7 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -147,12 +143,7 @@ class MergeRequestDiff < ActiveRecord::Base
# Collect array of Git::Diff objects # Collect array of Git::Diff objects
# between target and source branches # between target and source branches
def unmerged_diffs def unmerged_diffs
diffs = if merge_request.for_fork? diffs = compare_result.diffs
compare_action.diffs
else
Gitlab::Git::Diff.between(repository, source_branch, target_branch)
end
diffs ||= [] diffs ||= []
diffs diffs
rescue Gitlab::Git::Diff::TimeoutError => ex rescue Gitlab::Git::Diff::TimeoutError => ex
...@@ -166,13 +157,13 @@ class MergeRequestDiff < ActiveRecord::Base ...@@ -166,13 +157,13 @@ class MergeRequestDiff < ActiveRecord::Base
private private
def compare_action def compare_result
Gitlab::Satellite::CompareAction.new( @compare_result ||= CompareService.new.execute(
merge_request.author, merge_request.author,
merge_request.source_project,
merge_request.source_branch,
merge_request.target_project, merge_request.target_project,
merge_request.target_branch, merge_request.target_branch,
merge_request.source_project,
merge_request.source_branch
) )
end end
end end
...@@ -327,9 +327,7 @@ class Note < ActiveRecord::Base ...@@ -327,9 +327,7 @@ class Note < ActiveRecord::Base
# Thus it will automatically generate a new fragment # Thus it will automatically generate a new fragment
# when the event is updated because the key changes. # when the event is updated because the key changes.
def reset_events_cache def reset_events_cache
Event.where(target_id: self.id, target_type: 'Note'). Event.reset_event_cache_for(self)
order('id DESC').limit(100).
update_all(updated_at: Time.now)
end end
def set_references def set_references
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
# visibility_level :integer default(0), not null # visibility_level :integer default(0), not null
# archived :boolean default(FALSE), not null # archived :boolean default(FALSE), not null
# import_status :string(255) # import_status :string(255)
# star_count :integer
# #
class Project < ActiveRecord::Base class Project < ActiveRecord::Base
...@@ -40,8 +41,7 @@ class Project < ActiveRecord::Base ...@@ -40,8 +41,7 @@ class Project < ActiveRecord::Base
default_value_for :snippets_enabled, gitlab_config_features.snippets default_value_for :snippets_enabled, gitlab_config_features.snippets
ActsAsTaggableOn.strict_case_match = true ActsAsTaggableOn.strict_case_match = true
acts_as_taggable_on :tags
acts_as_taggable_on :labels, :issues_default_labels
attr_accessor :new_default_branch attr_accessor :new_default_branch
...@@ -74,6 +74,7 @@ class Project < ActiveRecord::Base ...@@ -74,6 +74,7 @@ class Project < ActiveRecord::Base
# Merge requests from source project should be kept when source project was removed # Merge requests from source project should be kept when source project was removed
has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest
has_many :issues, -> { order "state DESC, created_at DESC" }, dependent: :destroy has_many :issues, -> { order "state DESC, created_at DESC" }, dependent: :destroy
has_many :labels, dependent: :destroy
has_many :services, dependent: :destroy has_many :services, dependent: :destroy
has_many :events, dependent: :destroy has_many :events, dependent: :destroy
has_many :milestones, dependent: :destroy has_many :milestones, dependent: :destroy
...@@ -85,6 +86,8 @@ class Project < ActiveRecord::Base ...@@ -85,6 +86,8 @@ class Project < ActiveRecord::Base
has_many :users, through: :users_projects has_many :users, through: :users_projects
has_many :deploy_keys_projects, dependent: :destroy has_many :deploy_keys_projects, dependent: :destroy
has_many :deploy_keys, through: :deploy_keys_projects has_many :deploy_keys, through: :deploy_keys_projects
has_many :users_star_projects, dependent: :destroy
has_many :starrers, through: :users_star_projects, source: :user
has_many :project_group_links, dependent: :destroy has_many :project_group_links, dependent: :destroy
has_many :invited_groups, through: :project_group_links, source: :group has_many :invited_groups, through: :project_group_links, source: :group
...@@ -114,6 +117,7 @@ class Project < ActiveRecord::Base ...@@ -114,6 +117,7 @@ class Project < ActiveRecord::Base
validates :import_url, validates :import_url,
format: { with: URI::regexp(%w(git http https)), message: "should be a valid url" }, format: { with: URI::regexp(%w(git http https)), message: "should be a valid url" },
if: :import? if: :import?
validates :star_count, numericality: { greater_than_or_equal_to: 0 }
validate :check_limit, on: :create validate :check_limit, on: :create
# Scopes # Scopes
...@@ -285,13 +289,6 @@ class Project < ActiveRecord::Base ...@@ -285,13 +289,6 @@ class Project < ActiveRecord::Base
self.id self.id
end end
# Tags are shared by issues and merge requests
def issues_labels
@issues_labels ||= (issues_default_labels +
merge_requests.tags_on(:labels) +
issues.tags_on(:labels)).uniq.sort_by(&:name)
end
def issue_exists?(issue_id) def issue_exists?(issue_id)
if used_default_issues_tracker? if used_default_issues_tracker?
self.issues.where(iid: issue_id).first.present? self.issues.where(iid: issue_id).first.present?
...@@ -338,6 +335,10 @@ class Project < ActiveRecord::Base ...@@ -338,6 +335,10 @@ class Project < ActiveRecord::Base
self.issues_tracker == "jira" self.issues_tracker == "jira"
end end
def redmine_tracker?
self.issues_tracker == "redmine"
end
# For compatibility with old code # For compatibility with old code
def code def code
path path
...@@ -508,6 +509,7 @@ class Project < ActiveRecord::Base ...@@ -508,6 +509,7 @@ class Project < ActiveRecord::Base
end end
def rename_repo def rename_repo
path_was = previous_changes['path'].first
old_path_with_namespace = File.join(namespace_dir, path_was) old_path_with_namespace = File.join(namespace_dir, path_was)
new_path_with_namespace = File.join(namespace_dir, path) new_path_with_namespace = File.join(namespace_dir, path)
...@@ -586,4 +588,12 @@ class Project < ActiveRecord::Base ...@@ -586,4 +588,12 @@ class Project < ActiveRecord::Base
def update_repository_size def update_repository_size
update_attribute(:repository_size, repository.size) update_attribute(:repository_size, repository.size)
end end
def forks_count
ForkedProjectLink.where(forked_from_project_id: self.id).count
end
def find_label(name)
labels.find_by(name: name)
end
end end
...@@ -18,6 +18,8 @@ ...@@ -18,6 +18,8 @@
# #
class HipchatService < Service class HipchatService < Service
MAX_COMMITS = 3
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
def title def title
...@@ -57,15 +59,24 @@ class HipchatService < Service ...@@ -57,15 +59,24 @@ class HipchatService < Service
message = "" message = ""
message << "#{push[:user_name]} " message << "#{push[:user_name]} "
if before =~ /000000/ if before =~ /000000/
message << "pushed new branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> to <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a>\n" message << "pushed new branch <a href=\""\
"#{project.web_url}/commits/#{URI.escape(ref)}\">#{ref}</a>"\
" to <a href=\"#{project.web_url}\">"\
"#{project.name_with_namespace.gsub!(/\s/, "")}</a>\n"
elsif after =~ /000000/ elsif after =~ /000000/
message << "removed branch #{ref} from <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> \n" message << "removed branch #{ref} from <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> \n"
else else
message << "pushed to branch <a href=\"#{project.web_url}/commits/#{ref}\">#{ref}</a> " message << "pushed to branch <a href=\""\
"#{project.web_url}/commits/#{URI.escape(ref)}\">#{ref}</a> "
message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> " message << "of <a href=\"#{project.web_url}\">#{project.name_with_namespace.gsub!(/\s/,'')}</a> "
message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)" message << "(<a href=\"#{project.web_url}/compare/#{before}...#{after}\">Compare changes</a>)"
for commit in push[:commits] do
message << "<br /> - #{commit[:message]} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)" push[:commits].take(MAX_COMMITS).each do |commit|
message << "<br /> - #{commit[:message].lines.first} (<a href=\"#{commit[:url]}\">#{commit[:id][0..5]}</a>)"
end
if push[:commits].count > MAX_COMMITS
message << "<br />... #{push[:commits].count - MAX_COMMITS} more commits"
end end
end end
......
...@@ -74,9 +74,12 @@ class JiraService < Service ...@@ -74,9 +74,12 @@ class JiraService < Service
} }
} }
json_body = message.to_json
Rails.logger.info("#{self.class.name}: sending POST with body #{json_body} to #{url}")
JiraService.post( JiraService.post(
url, url,
body: message.to_json, body: json_body,
headers: { headers: {
'Content-Type' => 'application/json', 'Content-Type' => 'application/json',
'Authorization' => "Basic #{auth}" 'Authorization' => "Basic #{auth}"
......
...@@ -72,6 +72,15 @@ class ProjectWiki ...@@ -72,6 +72,15 @@ class ProjectWiki
end end
end end
def find_file(name, version = nil, try_on_disk = true)
version = wiki.ref if version.nil? # Gollum::Wiki#file ?
if wiki_file = wiki.file(name, version, try_on_disk)
wiki_file
else
nil
end
end
def create_page(title, content, format = :markdown, message = nil) def create_page(title, content, format = :markdown, message = nil)
commit = commit_details(:created, message, title) commit = commit_details(:created, message, title)
......
...@@ -10,8 +10,11 @@ class Repository ...@@ -10,8 +10,11 @@ class Repository
nil nil
end end
# Return absolute path to repository
def path_to_repo def path_to_repo
@path_to_repo ||= File.join(Gitlab.config.gitlab_shell.repos_path, path_with_namespace + ".git") @path_to_repo ||= File.expand_path(
File.join(Gitlab.config.gitlab_shell.repos_path, path_with_namespace + ".git")
)
end end
def exists? def exists?
...@@ -134,7 +137,7 @@ class Repository ...@@ -134,7 +137,7 @@ class Repository
def graph_log def graph_log
Rails.cache.fetch(cache_key(:graph_log)) do Rails.cache.fetch(cache_key(:graph_log)) do
stats = Gitlab::Git::GitStats.new(raw, root_ref) stats = Gitlab::Git::GitStats.new(raw, root_ref, Gitlab.config.git.timeout)
stats.parsed_log stats.parsed_log
end end
end end
...@@ -263,4 +266,20 @@ class Repository ...@@ -263,4 +266,20 @@ class Repository
contributor contributor
end end
end end
def blob_for_diff(commit, diff)
file = blob_at(commit.id, diff.new_path)
unless file
file = prev_blob_for_diff(commit, diff)
end
file
end
def prev_blob_for_diff(commit, diff)
if commit.parent_id
blob_at(commit.parent_id, diff.old_path)
end
end
end end
class Tree class Tree
include Gitlab::MarkdownHelper
attr_accessor :entries, :readme, :contribution_guide attr_accessor :entries, :readme, :contribution_guide
def initialize(repository, sha, path = '/') def initialize(repository, sha, path = '/')
...@@ -6,7 +8,23 @@ class Tree ...@@ -6,7 +8,23 @@ class Tree
git_repo = repository.raw_repository git_repo = repository.raw_repository
@entries = Gitlab::Git::Tree.where(git_repo, sha, path) @entries = Gitlab::Git::Tree.where(git_repo, sha, path)
if readme_tree = @entries.find(&:readme?) available_readmes = @entries.select(&:readme?)
if available_readmes.count > 0
# If there is more than 1 readme in tree, find readme which is supported
# by markup renderer.
if available_readmes.length > 1
supported_readmes = available_readmes.select do |readme|
gitlab_markdown?(readme.name) || markup?(readme.name)
end
# Take the first supported readme, or the first available readme, if we
# don't support any of them
readme_tree = supported_readmes.first || available_readmes.first
else
readme_tree = available_readmes.first
end
readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name) readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name)
@readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path) @readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path)
end end
......
...@@ -91,6 +91,8 @@ class User < ActiveRecord::Base ...@@ -91,6 +91,8 @@ class User < ActiveRecord::Base
has_many :personal_projects, through: :namespace, source: :projects has_many :personal_projects, through: :namespace, source: :projects
has_many :projects, through: :users_projects has_many :projects, through: :users_projects
has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' has_many :created_projects, foreign_key: :creator_id, class_name: 'Project'
has_many :users_star_projects, dependent: :destroy
has_many :starred_projects, through: :users_star_projects, source: :project
has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet" has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet"
has_many :users_projects, dependent: :destroy has_many :users_projects, dependent: :destroy
...@@ -175,6 +177,7 @@ class User < ActiveRecord::Base ...@@ -175,6 +177,7 @@ class User < ActiveRecord::Base
scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all }
scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') } scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') }
scope :ldap, -> { where(provider: 'ldap') } scope :ldap, -> { where(provider: 'ldap') }
scope :subscribed_for_admin_email, -> { where(admin_email_unsubscribed_at: nil) }
scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active } scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active }
...@@ -240,6 +243,15 @@ class User < ActiveRecord::Base ...@@ -240,6 +243,15 @@ class User < ActiveRecord::Base
end end
end end
def generate_reset_token
@reset_token, enc = Devise.token_generator.generate(self.class, :reset_password_token)
self.reset_password_token = enc
self.reset_password_sent_at = Time.now.utc
@reset_token
end
def namespace_uniq def namespace_uniq
namespace_name = self.username namespace_name = self.username
if Namespace.find_by(path: namespace_name) if Namespace.find_by(path: namespace_name)
...@@ -404,8 +416,10 @@ class User < ActiveRecord::Base ...@@ -404,8 +416,10 @@ class User < ActiveRecord::Base
end end
def requires_ldap_check? def requires_ldap_check?
if ldap_user? if !Gitlab.config.ldap.enabled
!last_credential_check_at || (last_credential_check_at + 1.hour) < Time.now false
elsif ldap_user?
!last_credential_check_at || (last_credential_check_at + Gitlab.config.ldap['sync_time']) < Time.now
else else
false false
end end
...@@ -489,7 +503,7 @@ class User < ActiveRecord::Base ...@@ -489,7 +503,7 @@ class User < ActiveRecord::Base
def post_create_hook def post_create_hook
log_info("User \"#{self.name}\" (#{self.email}) was created") log_info("User \"#{self.name}\" (#{self.email}) was created")
notification_service.new_user(self) notification_service.new_user(self, @reset_token)
system_hook_service.execute_hooks_for(self, :create) system_hook_service.execute_hooks_for(self, :create)
end end
...@@ -509,4 +523,22 @@ class User < ActiveRecord::Base ...@@ -509,4 +523,22 @@ class User < ActiveRecord::Base
def system_hook_service def system_hook_service
SystemHooksService.new SystemHooksService.new
end end
def admin_unsubscribe!
update_column :admin_email_unsubscribed_at, Time.now
end
def starred?(project)
starred_projects.exists?(project)
end
def toggle_star(project)
user_star_project = users_star_projects.
where(project: project, user: self).take
if user_star_project
user_star_project.destroy
else
UsersStarProject.create!(project: project, user: self)
end
end
end end
# == Schema Information
#
# Table name: users_star_projects
#
# id :integer not null, primary key
# starrer_id :integer not null
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
#
class UsersStarProject < ActiveRecord::Base
belongs_to :project, counter_cache: :star_count
belongs_to :user
validates :user, presence: true
validates :user_id, uniqueness: { scope: [:project_id] }
validates :project, presence: true
end
class ArchiveRepositoryService
def execute(project, ref, format)
storage_path = Gitlab.config.gitlab.repository_downloads_path
unless File.directory?(storage_path)
FileUtils.mkdir_p(storage_path)
end
format ||= 'tar.gz'
repository = project.repository
repository.clean_old_archives
repository.archive_repo(ref, storage_path, format.downcase)
end
end
# Compare 2 branches for one repo or between repositories
# and return Gitlab::CompareResult object that responds to commits and diffs
class CompareService
def execute(current_user, source_project, source_branch, target_project, target_branch)
# Try to compare branches to get commits list and diffs
#
# Note: Use satellite only when need to compare between to repos
# because satellites are slower then operations on bare repo
if target_project == source_project
Gitlab::CompareResult.new(
Gitlab::Git::Compare.new(
target_project.repository.raw_repository,
target_branch,
source_branch,
)
)
else
Gitlab::Satellite::CompareAction.new(
current_user,
target_project,
target_branch,
source_project,
source_branch
).result
end
end
end
...@@ -37,7 +37,7 @@ module Files ...@@ -37,7 +37,7 @@ module Files
if created_successfully if created_successfully
success success
else else
error("Your changes could not be committed, because the file has been changed") error("Your changes could not be committed. Maybe the file was changed by another process or there was nothing to commit?")
end end
end end
end end
......
module Issues module Issues
class CreateService < Issues::BaseService class CreateService < Issues::BaseService
def execute def execute
issue = project.issues.new(params) label_params = params[:label_ids]
issue = project.issues.new(params.except(:label_ids))
issue.author = current_user issue.author = current_user
if issue.save if issue.save
issue.update_attributes(label_ids: label_params)
notification_service.new_issue(issue, current_user) notification_service.new_issue(issue, current_user)
event_service.open_issue(issue, current_user) event_service.open_issue(issue, current_user)
issue.create_cross_references!(issue.project, current_user) issue.create_cross_references!(issue.project, current_user)
......
...@@ -22,27 +22,25 @@ module MergeRequests ...@@ -22,27 +22,25 @@ module MergeRequests
# Set MR description based on project template # Set MR description based on project template
merge_request.description = merge_request.target_project.merge_requests_template merge_request.description = merge_request.target_project.merge_requests_template
# Try to compare branches to get commits list and diffs compare_result = CompareService.new.execute(
compare_action = Gitlab::Satellite::CompareAction.new(
current_user, current_user,
merge_request.source_project,
merge_request.source_branch,
merge_request.target_project, merge_request.target_project,
merge_request.target_branch, merge_request.target_branch,
merge_request.source_project,
merge_request.source_branch
) )
commits = compare_action.commits commits = compare_result.commits
# At this point we decide if merge request can be created # At this point we decide if merge request can be created
# If we have at least one commit to merge -> creation allowed # If we have at least one commit to merge -> creation allowed
if commits.present? if commits.present?
merge_request.compare_commits = Commit.decorate(commits) merge_request.compare_commits = Commit.decorate(commits)
merge_request.compare_base_commit = Commit.new(commits.first)
merge_request.can_be_created = true merge_request.can_be_created = true
merge_request.compare_failed = false merge_request.compare_failed = false
# Try to collect diff for merge request. # Try to collect diff for merge request.
diffs = compare_action.diffs diffs = compare_result.diffs
if diffs.present? if diffs.present?
merge_request.compare_diffs = diffs merge_request.compare_diffs = diffs
......
module MergeRequests module MergeRequests
class CreateService < MergeRequests::BaseService class CreateService < MergeRequests::BaseService
def execute def execute
merge_request = MergeRequest.new(params) label_params = params[:label_ids]
merge_request = MergeRequest.new(params.except(:label_ids))
merge_request.source_project = project merge_request.source_project = project
merge_request.target_project ||= project merge_request.target_project ||= project
merge_request.author = current_user merge_request.author = current_user
if merge_request.save if merge_request.save
merge_request.update_attributes(label_ids: label_params)
event_service.open_mr(merge_request, current_user) event_service.open_mr(merge_request, current_user)
notification_service.new_merge_request(merge_request, current_user) notification_service.new_merge_request(merge_request, current_user)
merge_request.create_cross_references!(merge_request.project, current_user) merge_request.create_cross_references!(merge_request.project, current_user)
......
...@@ -105,9 +105,9 @@ class NotificationService ...@@ -105,9 +105,9 @@ class NotificationService
end end
# Notify new user with email after creation # Notify new user with email after creation
def new_user(user) def new_user(user, token = nil)
# Don't email omniauth created users # Don't email omniauth created users
mailer.new_user_email(user.id, user.password) unless user.extern_uid? mailer.new_user_email(user.id, user.password, token) unless user.extern_uid?
end end
# Notify users on new note in system # Notify users on new note in system
......
...@@ -57,7 +57,6 @@ module Projects ...@@ -57,7 +57,6 @@ module Projects
:add_repository, :add_repository,
@project.path_with_namespace @project.path_with_namespace
) )
end end
if @project.wiki_enabled? if @project.wiki_enabled?
......
...@@ -12,7 +12,13 @@ module Search ...@@ -12,7 +12,13 @@ module Search
return result unless query.present? return result unless query.present?
if params[:search_code].present? if params[:search_code].present?
blobs = project.repository.search_files(query, params[:repository_ref]) unless project.empty_repo? if !@project.empty_repo?
blobs = project.repository.search_files(query,
params[:repository_ref])
else
blobs = Array.new
end
blobs = Kaminari.paginate_array(blobs).page(params[:page]).per(20) blobs = Kaminari.paginate_array(blobs).page(params[:page]).per(20)
result[:blobs] = blobs result[:blobs] = blobs
result[:total_results] = blobs.total_count result[:total_results] = blobs.total_count
......
...@@ -2,5 +2,8 @@ class TestHookService ...@@ -2,5 +2,8 @@ class TestHookService
def execute(hook, current_user) def execute(hook, current_user)
data = GitPushService.new.sample_data(hook.project, current_user) data = GitPushService.new.sample_data(hook.project, current_user)
hook.execute(data) hook.execute(data)
true
rescue SocketError
false
end end
end end
%h3.page-title
Send email notication
%p.light
You can notify the app / group or a project by sending them an email notification
= form_tag admin_email_path, class: 'form-horizontal', id: 'new-admin-email' do
.form-group
%label.control-label{for: :subject} Subject
.col-sm-10
= text_field_tag :subject, '', class: 'form-control', required: true
.form-group
%label.control-label{for: :body} Body
.col-sm-10
= text_area_tag :body, '', class: 'form-control', rows: 15, required: true
.form-group
%label.control-label{for: :recipients} Recipient group
.col-sm-10
= select_tag :recipients, admin_email_grouped_recipient_options, class: :select2, required: true
.form-actions
= submit_tag 'Send message', class: 'btn btn-create'
...@@ -31,9 +31,9 @@ ...@@ -31,9 +31,9 @@
= f.label :password, class: 'control-label' = f.label :password, class: 'control-label'
.col-sm-10 .col-sm-10
%strong %strong
A temporary password will be generated and sent to user. Reset link will be generated and sent to the user.
%br %br
User will be forced to change it after first sign in User will be forced to set the password on first sign in.
- else - else
%fieldset %fieldset
%legend Password %legend Password
......
...@@ -32,6 +32,7 @@ ...@@ -32,6 +32,7 @@
.panel-heading .panel-heading
Users (#{@users.total_count}) Users (#{@users.total_count})
.panel-head-actions .panel-head-actions
= link_to 'Send email to users', admin_email_path, class: 'btn btn-info'
= link_to 'New User', new_admin_user_path, class: "btn btn-new" = link_to 'New User', new_admin_user_path, class: "btn btn-new"
%ul.well-list %ul.well-list
- @users.each do |user| - @users.each do |user|
......
...@@ -44,12 +44,12 @@ ...@@ -44,12 +44,12 @@
- if @labels.present? - if @tags.present?
%fieldset %fieldset
%legend Labels %legend Tags
%ul.nav.nav-pills.nav-stacked.nav-small %ul.nav.nav-pills.nav-stacked.nav-small
- @labels.each do |label| - @tags.each do |tag|
%li{ class: (label.name == params[:label]) ? 'active' : 'light' } %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' }
= link_to projects_dashboard_filter_path(scope: params[:scope], label: label.name) do = link_to projects_dashboard_filter_path(scope: params[:scope], tag: tag.name) do
%i.icon-tag %i.icon-tag
= label.name = tag.name
...@@ -46,5 +46,5 @@ ...@@ -46,5 +46,5 @@
%br %br
Public projects are an easy way to allow everyone to have read-only access. Public projects are an easy way to allow everyone to have read-only access.
.link_holder .link_holder
= link_to public_projects_path, class: "btn btn-new" do = link_to explore_projects_path, class: "btn btn-new" do
Browse public projects » Browse public projects »
...@@ -54,10 +54,10 @@ ...@@ -54,10 +54,10 @@
%span.label %span.label
%i.icon-archive %i.icon-archive
Archived Archived
- project.labels.each do |label| - project.tags.each do |tag|
%span.label.label-info %span.label.label-info
%i.icon-tag %i.icon-tag
= label.name = tag.name
- if project.description.present? - if project.description.present?
%p= truncate project.description, length: 100 %p= truncate project.description, length: 100
.last-activity .last-activity
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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