Commit a42b2c38 authored by Alfredo Sumaran's avatar Alfredo Sumaran

Merge branch 'master' into issue_7959

parents 647f28bd 18c04988
This diff is collapsed.
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
v 8.6.0 (unreleased) v 8.7.0 (unreleased)
- Make HTTP(s) label consistent on clone bar (Stan Hu)
v 8.6.1 (unreleased)
v 8.6.0
- Add ability to move issue to another project
- Prevent tokens in the import URL to be showed by the UI
- Fix bug where wrong commit ID was being used in a merge request diff to show old image (Stan Hu)
- Add confidential issues
- Bump gitlab_git to 9.0.3 (Stan Hu) - Bump gitlab_git to 9.0.3 (Stan Hu)
- Fix diff image view modes (2-up, swipe, onion skin) not working (Stan Hu)
- Support Golang subpackage fetching (Stan Hu) - Support Golang subpackage fetching (Stan Hu)
- Bump Capybara gem to 2.6.2 (Stan Hu) - Bump Capybara gem to 2.6.2 (Stan Hu)
- New branch button appears on issues where applicable - New branch button appears on issues where applicable
- Contributions to forked projects are included in calendar - Contributions to forked projects are included in calendar
- Improve the formatting for the user page bio (Connor Shea) - Improve the formatting for the user page bio (Connor Shea)
- Easily (un)mark merge request as WIP using link
- Use specialized system notes when MR is (un)marked as WIP
- Removed the default password from the initial admin account created during - Removed the default password from the initial admin account created during
setup. A password can be provided during setup (see installation docs), or setup. A password can be provided during setup (see installation docs), or
GitLab will ask the user to create a new one upon first visit. GitLab will ask the user to create a new one upon first visit.
- Fix issue when pushing to projects ending in .wiki - Fix issue when pushing to projects ending in .wiki
- Properly display YAML front matter in Markdown
- Add support for wiki with UTF-8 page names (Hiroyuki Sato) - Add support for wiki with UTF-8 page names (Hiroyuki Sato)
- Fix wiki search results point to raw source (Hiroyuki Sato) - Fix wiki search results point to raw source (Hiroyuki Sato)
- Don't load all of GitLab in mail_room - Don't load all of GitLab in mail_room
- Add information about `image` and `services` field at `job` level in the `.gitlab-ci.yml` documentation (Pat Turner)
- HTTP error pages work independently from location and config (Artem Sidorenko)
- Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set - Update `omniauth-saml` to 1.5.0 to allow for custom response attributes to be set
- Memoize @group in Admin::GroupsController (Yatish Mehta) - Memoize @group in Admin::GroupsController (Yatish Mehta)
- Indicate how much an MR diverged from the target branch (Pierre de La Morinerie) - Indicate how much an MR diverged from the target branch (Pierre de La Morinerie)
- Added omniauth-auth0 Gem (Daniel Carraro) - Added omniauth-auth0 Gem (Daniel Carraro)
- Add label description in tooltip to labels in issue index and sidebar
- Strip leading and trailing spaces in URL validator (evuez) - Strip leading and trailing spaces in URL validator (evuez)
- Add "last_sign_in_at" and "confirmed_at" to GET /users/* API endpoints for admins (evuez) - Add "last_sign_in_at" and "confirmed_at" to GET /users/* API endpoints for admins (evuez)
- Return empty array instead of 404 when commit has no statuses in commit status API - Return empty array instead of 404 when commit has no statuses in commit status API
...@@ -25,6 +42,7 @@ v 8.6.0 (unreleased) ...@@ -25,6 +42,7 @@ v 8.6.0 (unreleased)
- Rewrite logo to simplify SVG code (Sean Lang) - Rewrite logo to simplify SVG code (Sean Lang)
- Allow to use YAML anchors when parsing the `.gitlab-ci.yml` (Pascal Bach) - Allow to use YAML anchors when parsing the `.gitlab-ci.yml` (Pascal Bach)
- Ignore jobs that start with `.` (hidden jobs) - Ignore jobs that start with `.` (hidden jobs)
- Hide builds from project's settings when the feature is disabled
- Allow to pass name of created artifacts archive in `.gitlab-ci.yml` - Allow to pass name of created artifacts archive in `.gitlab-ci.yml`
- Refactor and greatly improve search performance - Refactor and greatly improve search performance
- Add support for cross-project label references - Add support for cross-project label references
...@@ -36,15 +54,24 @@ v 8.6.0 (unreleased) ...@@ -36,15 +54,24 @@ v 8.6.0 (unreleased)
- Fix bug where Bitbucket `closed` issues were imported as `opened` (Iuri de Silvio) - Fix bug where Bitbucket `closed` issues were imported as `opened` (Iuri de Silvio)
- Don't show Issues/MRs from archived projects in Groups view - Don't show Issues/MRs from archived projects in Groups view
- Fix wrong "iid of max iid" in Issuable sidebar for some merged MRs - Fix wrong "iid of max iid" in Issuable sidebar for some merged MRs
- Fix empty source_sha on Merge Request when there is no diff (Pierre de La Morinerie)
- Increase the notes polling timeout over time (Roberto Dip) - Increase the notes polling timeout over time (Roberto Dip)
- Add shortcut to toggle markdown preview (Florent Baldino) - Add shortcut to toggle markdown preview (Florent Baldino)
- Show labels in dashboard and group milestone views - Show labels in dashboard and group milestone views
- Fix an issue when the target branch of a MR had been deleted
- Add main language of a project in the list of projects (Tiago Botelho) - Add main language of a project in the list of projects (Tiago Botelho)
- Add #upcoming filter to Milestone filter (Tiago Botelho)
- Add ability to show archived projects on dashboard, explore and group pages - Add ability to show archived projects on dashboard, explore and group pages
- Move group activity to separate page - Move group activity to separate page
- Create external users which are excluded of internal and private projects unless access was explicitly granted - Create external users which are excluded of internal and private projects unless access was explicitly granted
- Continue parameters are checked to ensure redirection goes to the same instance - Continue parameters are checked to ensure redirection goes to the same instance
- User deletion is now done in the background so the request can not time out - User deletion is now done in the background so the request can not time out
- Canceled builds are now ignored in compound build status if marked as `allowed to fail`
- Trigger a todo for mentions on commits page
- Let project owners and admins soft delete issues and merge requests
v 8.5.8
- Bump Git version requirement to 2.7.4
v 8.5.7 v 8.5.7
- Bump Git version requirement to 2.7.3 - Bump Git version requirement to 2.7.3
...@@ -57,7 +84,6 @@ v 8.5.5 ...@@ -57,7 +84,6 @@ v 8.5.5
- Prevent a 500 error in Todos when author was removed - Prevent a 500 error in Todos when author was removed
- Fix pagination for filtered dashboard and explore pages - Fix pagination for filtered dashboard and explore pages
- Fix "Show all" link behavior - Fix "Show all" link behavior
- Add #upcoming filter to Milestone filter (Tiago Botelho)
v 8.5.4 v 8.5.4
- Do not cache requests for badges (including builds badge) - Do not cache requests for badges (including builds badge)
......
...@@ -51,7 +51,7 @@ gem "browser", '~> 1.0.0' ...@@ -51,7 +51,7 @@ gem "browser", '~> 1.0.0'
# Extracting information from a git repository # Extracting information from a git repository
# Provide access to Gitlab::Git library # Provide access to Gitlab::Git library
gem "gitlab_git", '~> 9.0' gem "gitlab_git", '~> 10.0'
# LDAP Auth # LDAP Auth
# GitLab fork with several improvements to original library. For full list of changes # GitLab fork with several improvements to original library. For full list of changes
...@@ -222,6 +222,8 @@ gem 'net-ssh', '~> 3.0.1' ...@@ -222,6 +222,8 @@ gem 'net-ssh', '~> 3.0.1'
# Sentry integration # Sentry integration
gem 'sentry-raven', '~> 0.15' gem 'sentry-raven', '~> 0.15'
gem 'premailer-rails', '~> 1.9.0'
# Metrics # Metrics
group :metrics do group :metrics do
gem 'allocations', '~> 1.0', require: false, platform: :mri gem 'allocations', '~> 1.0', require: false, platform: :mri
...@@ -285,7 +287,7 @@ group :development, :test do ...@@ -285,7 +287,7 @@ group :development, :test do
gem 'spring-commands-spinach', '~> 1.0.0' gem 'spring-commands-spinach', '~> 1.0.0'
gem 'spring-commands-teaspoon', '~> 0.0.2' gem 'spring-commands-teaspoon', '~> 0.0.2'
gem 'rubocop', '~> 0.35.0', require: false gem 'rubocop', '~> 0.38.0', require: false
gem 'scss_lint', '~> 0.47.0', require: false gem 'scss_lint', '~> 0.47.0', require: false
gem 'coveralls', '~> 0.8.2', require: false gem 'coveralls', '~> 0.8.2', require: false
gem 'simplecov', '~> 0.10.0', require: false gem 'simplecov', '~> 0.10.0', require: false
......
...@@ -61,9 +61,7 @@ GEM ...@@ -61,9 +61,7 @@ GEM
faraday_middleware-multi_json (~> 0.0) faraday_middleware-multi_json (~> 0.0)
oauth2 (~> 1.0) oauth2 (~> 1.0)
asciidoctor (1.5.3) asciidoctor (1.5.3)
ast (2.1.0) ast (2.2.0)
astrolabe (1.3.1)
parser (~> 2.2)
attr_encrypted (1.3.4) attr_encrypted (1.3.4)
encryptor (>= 1.3.0) encryptor (>= 1.3.0)
attr_required (1.0.0) attr_required (1.0.0)
...@@ -150,6 +148,8 @@ GEM ...@@ -150,6 +148,8 @@ GEM
crack (0.4.3) crack (0.4.3)
safe_yaml (~> 1.0.0) safe_yaml (~> 1.0.0)
creole (0.5.0) creole (0.5.0)
css_parser (1.3.7)
addressable
d3_rails (3.5.11) d3_rails (3.5.11)
railties (>= 3.1.0) railties (>= 3.1.0)
daemons (1.2.3) daemons (1.2.3)
...@@ -359,11 +359,11 @@ GEM ...@@ -359,11 +359,11 @@ GEM
posix-spawn (~> 0.3) posix-spawn (~> 0.3)
gitlab_emoji (0.3.1) gitlab_emoji (0.3.1)
gemojione (~> 2.2, >= 2.2.1) gemojione (~> 2.2, >= 2.2.1)
gitlab_git (9.0.3) gitlab_git (10.0.0)
activesupport (~> 4.0) activesupport (~> 4.0)
charlock_holmes (~> 0.7.3) charlock_holmes (~> 0.7.3)
github-linguist (~> 4.7.0) github-linguist (~> 4.7.0)
rugged (~> 0.24.0b13) rugged (~> 0.24.0)
gitlab_meta (7.0) gitlab_meta (7.0)
gitlab_omniauth-ldap (1.2.1) gitlab_omniauth-ldap (1.2.1)
net-ldap (~> 0.9) net-ldap (~> 0.9)
...@@ -423,6 +423,7 @@ GEM ...@@ -423,6 +423,7 @@ GEM
haml (~> 4.0.0) haml (~> 4.0.0)
nokogiri (~> 1.6.0) nokogiri (~> 1.6.0)
ruby_parser (~> 3.5) ruby_parser (~> 3.5)
htmlentities (4.3.4)
http-cookie (1.0.2) http-cookie (1.0.2)
domain_name (~> 0.5) domain_name (~> 0.5)
http_parser.rb (0.5.3) http_parser.rb (0.5.3)
...@@ -554,8 +555,8 @@ GEM ...@@ -554,8 +555,8 @@ GEM
orm_adapter (0.5.0) orm_adapter (0.5.0)
paranoia (2.1.4) paranoia (2.1.4)
activerecord (~> 4.0) activerecord (~> 4.0)
parser (2.2.3.0) parser (2.3.0.6)
ast (>= 1.1, < 3.0) ast (~> 2.2)
pg (0.18.4) pg (0.18.4)
poltergeist (1.9.0) poltergeist (1.9.0)
capybara (~> 2.1) capybara (~> 2.1)
...@@ -564,6 +565,12 @@ GEM ...@@ -564,6 +565,12 @@ GEM
websocket-driver (>= 0.2.0) websocket-driver (>= 0.2.0)
posix-spawn (0.3.11) posix-spawn (0.3.11)
powerpack (0.1.1) powerpack (0.1.1)
premailer (1.8.6)
css_parser (>= 1.3.6)
htmlentities (>= 4.0.0)
premailer-rails (1.9.0)
actionmailer (>= 3, < 5)
premailer (~> 1.7, >= 1.7.9)
pry (0.10.3) pry (0.10.3)
coderay (~> 1.1.0) coderay (~> 1.1.0)
method_source (~> 0.8.1) method_source (~> 0.8.1)
...@@ -615,7 +622,7 @@ GEM ...@@ -615,7 +622,7 @@ GEM
activesupport (= 4.2.5.2) activesupport (= 4.2.5.2)
rake (>= 0.8.7) rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0) thor (>= 0.18.1, < 2.0)
rainbow (2.0.0) rainbow (2.1.0)
raindrops (0.15.0) raindrops (0.15.0)
rake (10.5.0) rake (10.5.0)
raphael-rails (2.1.2) raphael-rails (2.1.2)
...@@ -687,13 +694,12 @@ GEM ...@@ -687,13 +694,12 @@ GEM
rspec-retry (0.4.5) rspec-retry (0.4.5)
rspec-core rspec-core
rspec-support (3.3.0) rspec-support (3.3.0)
rubocop (0.35.1) rubocop (0.38.0)
astrolabe (~> 1.3) parser (>= 2.3.0.6, < 3.0)
parser (>= 2.2.3.0, < 3.0)
powerpack (~> 0.1) powerpack (~> 0.1)
rainbow (>= 1.99.1, < 3.0) rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.7) ruby-progressbar (~> 1.7)
tins (<= 1.6.0) unicode-display_width (~> 1.0, >= 1.0.1)
ruby-fogbugz (0.2.1) ruby-fogbugz (0.2.1)
crack (~> 0.4) crack (~> 0.4)
ruby-progressbar (1.7.5) ruby-progressbar (1.7.5)
...@@ -843,6 +849,7 @@ GEM ...@@ -843,6 +849,7 @@ GEM
unf (0.1.4) unf (0.1.4)
unf_ext unf_ext
unf_ext (0.0.7.1) unf_ext (0.0.7.1)
unicode-display_width (1.0.2)
unicorn (4.9.0) unicorn (4.9.0)
kgio (~> 2.6) kgio (~> 2.6)
rack rack
...@@ -942,7 +949,7 @@ DEPENDENCIES ...@@ -942,7 +949,7 @@ DEPENDENCIES
github-markup (~> 1.3.1) github-markup (~> 1.3.1)
gitlab-flowdock-git-hook (~> 1.0.1) gitlab-flowdock-git-hook (~> 1.0.1)
gitlab_emoji (~> 0.3.0) gitlab_emoji (~> 0.3.0)
gitlab_git (~> 9.0) gitlab_git (~> 10.0)
gitlab_meta (= 7.0) gitlab_meta (= 7.0)
gitlab_omniauth-ldap (~> 1.2.1) gitlab_omniauth-ldap (~> 1.2.1)
gollum-lib (~> 4.1.0) gollum-lib (~> 4.1.0)
...@@ -992,6 +999,7 @@ DEPENDENCIES ...@@ -992,6 +999,7 @@ DEPENDENCIES
paranoia (~> 2.0) paranoia (~> 2.0)
pg (~> 0.18.2) pg (~> 0.18.2)
poltergeist (~> 1.9.0) poltergeist (~> 1.9.0)
premailer-rails (~> 1.9.0)
pry-rails pry-rails
quiet_assets (~> 1.0.2) quiet_assets (~> 1.0.2)
rack-attack (~> 4.3.1) rack-attack (~> 4.3.1)
...@@ -1013,7 +1021,7 @@ DEPENDENCIES ...@@ -1013,7 +1021,7 @@ DEPENDENCIES
rqrcode-rails3 (~> 0.1.7) rqrcode-rails3 (~> 0.1.7)
rspec-rails (~> 3.3.0) rspec-rails (~> 3.3.0)
rspec-retry rspec-retry
rubocop (~> 0.35.0) rubocop (~> 0.38.0)
ruby-fogbugz (~> 0.2.1) ruby-fogbugz (~> 0.2.1)
sanitize (~> 2.0) sanitize (~> 2.0)
sass-rails (~> 5.0.0) sass-rails (~> 5.0.0)
...@@ -1056,6 +1064,3 @@ DEPENDENCIES ...@@ -1056,6 +1064,3 @@ DEPENDENCIES
web-console (~> 2.0) web-console (~> 2.0)
webmock (~> 1.21.0) webmock (~> 1.21.0)
wikicloth (= 0.8.1) wikicloth (= 0.8.1)
BUNDLED WITH
1.11.2
...@@ -68,7 +68,7 @@ GitLab is a Ruby on Rails application that runs on the following software: ...@@ -68,7 +68,7 @@ GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL - Ubuntu/Debian/CentOS/RHEL
- Ruby (MRI) 2.1 - Ruby (MRI) 2.1
- Git 2.7.3+ - Git 2.7.4+
- Redis 2.8+ - Redis 2.8+
- MySQL or PostgreSQL - MySQL or PostgreSQL
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#= require jquery #= require jquery
#= require jquery-ui/autocomplete #= require jquery-ui/autocomplete
#= require jquery-ui/datepicker #= require jquery-ui/datepicker
#= require jquery-ui/draggable
#= require jquery-ui/effect-highlight #= require jquery-ui/effect-highlight
#= require jquery-ui/sortable #= require jquery-ui/sortable
#= require jquery_ujs #= require jquery_ujs
...@@ -138,7 +139,7 @@ $ -> ...@@ -138,7 +139,7 @@ $ ->
# Initialize tooltips # Initialize tooltips
$('body').tooltip( $('body').tooltip(
selector: '.has_tooltip, [data-toggle="tooltip"]' selector: '.has-tooltip, [data-toggle="tooltip"]'
placement: (_, el) -> placement: (_, el) ->
$el = $(el) $el = $(el)
$el.data('placement') || 'bottom' $el.data('placement') || 'bottom'
......
...@@ -5,7 +5,6 @@ class @Aside ...@@ -5,7 +5,6 @@ class @Aside
e.preventDefault() e.preventDefault()
btn = $(e.currentTarget) btn = $(e.currentTarget)
icon = btn.find('i') icon = btn.find('i')
console.log('1')
if icon.hasClass('fa-angle-left') if icon.hasClass('fa-angle-left')
btn.parent().find('section').hide() btn.parent().find('section').hide()
......
...@@ -122,7 +122,7 @@ class @AwardsHandler ...@@ -122,7 +122,7 @@ class @AwardsHandler
nodes = [] nodes = []
nodes.push( nodes.push(
"<button class='btn award-control js-emoji-btn has_tooltip active' title='me'>", "<button class='btn award-control js-emoji-btn has-tooltip active' title='me'>",
"<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>", "<div class='icon emoji-icon #{emojiCssClass}' data-emoji='#{emoji}'></div>",
"<span class='award-control-text js-counter'>1</span>", "<span class='award-control-text js-counter'>1</span>",
"</button>" "</button>"
......
...@@ -14,7 +14,6 @@ class Dispatcher ...@@ -14,7 +14,6 @@ class Dispatcher
path = page.split(':') path = page.split(':')
shortcut_handler = null shortcut_handler = null
switch page switch page
when 'projects:issues:index' when 'projects:issues:index'
Issues.init() Issues.init()
...@@ -25,6 +24,8 @@ class Dispatcher ...@@ -25,6 +24,8 @@ class Dispatcher
new ZenMode() new ZenMode()
when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show' when 'projects:milestones:show', 'groups:milestones:show', 'dashboard:milestones:show'
new Milestone() new Milestone()
when 'dashboard:todos:index'
new Todos()
when 'projects:milestones:new', 'projects:milestones:edit' when 'projects:milestones:new', 'projects:milestones:edit'
new ZenMode() new ZenMode()
new DropzoneInput($('.milestone-form')) new DropzoneInput($('.milestone-form'))
......
...@@ -167,7 +167,11 @@ class GitLabDropdown ...@@ -167,7 +167,11 @@ class GitLabDropdown
hidden: => hidden: =>
if @options.filterable if @options.filterable
@dropdown.find(".dropdown-input-field").blur().val("") @dropdown
.find(".dropdown-input-field")
.blur()
.val("")
.trigger("keyup")
if @dropdown.find(".dropdown-toggle-page").length if @dropdown.find(".dropdown-toggle-page").length
$('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS $('.dropdown-menu', @dropdown).removeClass PAGE_TWO_CLASS
...@@ -246,11 +250,15 @@ class GitLabDropdown ...@@ -246,11 +250,15 @@ class GitLabDropdown
if oldValue if oldValue
value = "#{oldValue},#{value}" value = "#{oldValue},#{value}"
else else
@dropdown.find(ACTIVE_CLASS).removeClass ACTIVE_CLASS @dropdown.find(".#{ACTIVE_CLASS}").removeClass ACTIVE_CLASS
# Toggle active class for the tick mark # Toggle active class for the tick mark
el.toggleClass "is-active" el.toggleClass "is-active"
# Toggle the dropdown label
if @options.toggleLabel
$(@el).find(".dropdown-toggle-text").text @options.toggleLabel(selectedObject)
if value? if value?
if !field.length if !field.length
# Create hidden input for form # Create hidden input for form
......
class @IssuableForm class @IssuableForm
issueMoveConfirmMsg: 'Are you sure you want to move this issue to another project?'
wipRegex: /^\s*(\[WIP\]\s*|WIP:\s*|WIP\s+)+\s*/i
constructor: (@form) -> constructor: (@form) ->
GitLab.GfmAutoComplete.setup() GitLab.GfmAutoComplete.setup()
new UsersSelect() new UsersSelect()
...@@ -6,14 +9,17 @@ class @IssuableForm ...@@ -6,14 +9,17 @@ class @IssuableForm
@titleField = @form.find("input[name*='[title]']") @titleField = @form.find("input[name*='[title]']")
@descriptionField = @form.find("textarea[name*='[description]']") @descriptionField = @form.find("textarea[name*='[description]']")
@issueMoveField = @form.find("#move_to_project_id")
return unless @titleField.length && @descriptionField.length return unless @titleField.length && @descriptionField.length
@initAutosave() @initAutosave()
@form.on "submit", @resetAutosave @form.on "submit", @handleSubmit
@form.on "click", ".btn-cancel", @resetAutosave @form.on "click", ".btn-cancel", @resetAutosave
@initWip()
initAutosave: -> initAutosave: ->
new Autosave @titleField, [ new Autosave @titleField, [
document.location.pathname, document.location.pathname,
...@@ -27,6 +33,50 @@ class @IssuableForm ...@@ -27,6 +33,50 @@ class @IssuableForm
"description" "description"
] ]
handleSubmit: =>
if (parseInt(@issueMoveField?.val()) ? 0) > 0
return false unless confirm(@issueMoveConfirmMsg)
@resetAutosave()
resetAutosave: => resetAutosave: =>
@titleField.data("autosave").reset() @titleField.data("autosave").reset()
@descriptionField.data("autosave").reset() @descriptionField.data("autosave").reset()
initWip: ->
@$wipExplanation = @form.find(".js-wip-explanation")
@$noWipExplanation = @form.find(".js-no-wip-explanation")
return unless @$wipExplanation.length and @$noWipExplanation.length
@form.on "click", ".js-toggle-wip", @toggleWip
@titleField.on "keyup blur", @renderWipExplanation
@renderWipExplanation()
workInProgress: ->
@wipRegex.test @titleField.val()
renderWipExplanation: =>
if @workInProgress()
@$wipExplanation.show()
@$noWipExplanation.hide()
else
@$wipExplanation.hide()
@$noWipExplanation.show()
toggleWip: (event) =>
event.preventDefault()
if @workInProgress()
@removeWip()
else
@addWip()
@renderWipExplanation()
removeWip: ->
@titleField.val @titleField.val().replace(@wipRegex, "")
addWip: ->
@titleField.val "WIP: #{@titleField.val()}"
...@@ -7,6 +7,7 @@ class @Issue ...@@ -7,6 +7,7 @@ class @Issue
# Prevent duplicate event bindings # Prevent duplicate event bindings
@disableTaskList() @disableTaskList()
@fixAffixScroll() @fixAffixScroll()
@initParticipants()
if $('a.btn-close').length if $('a.btn-close').length
@initTaskList() @initTaskList()
@initIssueBtnEventListeners() @initIssueBtnEventListeners()
...@@ -84,3 +85,27 @@ class @Issue ...@@ -84,3 +85,27 @@ class @Issue
type: 'PATCH' type: 'PATCH'
url: $('form.js-issuable-update').attr('action') url: $('form.js-issuable-update').attr('action')
data: patchData data: patchData
initParticipants: ->
_this = @
$(document).on "click", ".js-participants-more", @toggleHiddenParticipants
$(".js-participants-author").each (i) ->
if i >= _this.PARTICIPANTS_ROW_COUNT
$(@)
.addClass "js-participants-hidden"
.hide()
toggleHiddenParticipants: (e) ->
e.preventDefault()
currentText = $(this).text().trim()
lessText = $(this).data("less-text")
originalText = $(this).data("original-text")
if currentText is originalText
$(this).text(lessText)
else
$(this).text(originalText)
$(".js-participants-hidden").toggle()
...@@ -41,24 +41,28 @@ ...@@ -41,24 +41,28 @@
@timer = null @timer = null
$("#issue_search").keyup -> $("#issue_search").keyup ->
clearTimeout(@timer) clearTimeout(@timer)
@timer = setTimeout(Issues.filterResults, 500) @timer = setTimeout( ->
Issues.filterResults $("#issue_search_form")
, 500)
filterResults: => filterResults: (form) =>
form = $("#issue_search_form") $('.issues-holder, .merge-requests-holder').css("opacity", '0.5')
search = $("#issue_search").val() formAction = form.attr('action')
$('.issues-holder').css("opacity", '0.5') formData = form.serialize()
issues_url = form.attr('action') + '?' + form.serialize() issuesUrl = formAction
issuesUrl += ("#{if formAction.indexOf("?") < 0 then '?' else '&'}")
issuesUrl += formData
$.ajax $.ajax
type: "GET" type: "GET"
url: form.attr('action') url: formAction
data: form.serialize() data: formData
complete: -> complete: ->
$('.issues-holder').css("opacity", '1.0') $('.issues-holder, .merge-requests-holder').css("opacity", '1.0')
success: (data) -> success: (data) ->
$('.issues-holder').html(data.html) $('.issues-holder, .merge-requests-holder').html(data.html)
# Change url so if user reload a page - search results are saved # Change url so if user reload a page - search results are saved
history.replaceState {page: issues_url}, document.title, issues_url history.replaceState {page: issuesUrl}, document.title, issuesUrl
Issues.reload() Issues.reload()
dataType: "json" dataType: "json"
......
class @LabelsSelect class @LabelsSelect
constructor: -> constructor: ->
$('.js-label-select').each (i, dropdown) -> $('.js-label-select').each (i, dropdown) ->
projectId = $(dropdown).data('project-id') $dropdown = $(dropdown)
labelUrl = $(dropdown).data("labels") projectId = $dropdown.data('project-id')
selectedLabel = $(dropdown).data('selected') labelUrl = $dropdown.data('labels')
selectedLabel = $dropdown.data('selected')
if selectedLabel if selectedLabel
selectedLabel = selectedLabel.split(",") selectedLabel = selectedLabel.split(',')
newLabelField = $('#new_label_name') newLabelField = $('#new_label_name')
newColorField = $('#new_label_color') newColorField = $('#new_label_color')
showNo = $(dropdown).data('show-no') showNo = $dropdown.data('show-no')
showAny = $(dropdown).data('show-any') showAny = $dropdown.data('show-any')
defaultLabel = $dropdown.data('default-label')
if newLabelField.length if newLabelField.length
$('.suggest-colors-dropdown a').on "click", (e) -> $('.suggest-colors-dropdown a').on 'click', (e) ->
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
newColorField.val $(this).data("color") newColorField.val $(this).data('color')
$('.js-dropdown-label-color-preview') $('.js-dropdown-label-color-preview')
.css 'background-color', $(this).data("color") .css 'background-color', $(this).data('color')
.addClass 'is-active' .addClass 'is-active'
$('.js-new-label-btn').on "click", (e) -> $('.js-new-label-btn').on 'click', (e) ->
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
if newLabelField.val() isnt "" && newColorField.val() isnt "" if newLabelField.val() isnt '' and newColorField.val() isnt ''
$('.js-new-label-btn').disable() $('.js-new-label-btn').disable()
# Create new label with API # Create new label with API
...@@ -33,46 +35,38 @@ class @LabelsSelect ...@@ -33,46 +35,38 @@ class @LabelsSelect
color: newColorField.val() color: newColorField.val()
}, (label) -> }, (label) ->
$('.js-new-label-btn').enable() $('.js-new-label-btn').enable()
$('.dropdown-menu-back', $(dropdown).parent()).trigger "click" $('.dropdown-menu-back', $dropdown.parent()).trigger 'click'
$(dropdown).glDropdown( $dropdown.glDropdown(
data: (term, callback) -> data: (term, callback) ->
# We have to fetch the JS version of the labels list because there is no
# public facing JSON url for labels
$.ajax( $.ajax(
url: labelUrl url: labelUrl
).done (data) -> ).done (data) ->
html = $(data)
data = []
html.find('.label-row a').each ->
data.push(
title: $(@).text().trim()
)
if showNo if showNo
data.unshift( data.unshift(
id: "0" id: 0
title: 'No label' title: 'No Label'
) )
if showAny if showAny
data.unshift( data.unshift(
title: 'Any label' isAny: true
title: 'Any Label'
) )
if data.length > 2 if data.length > 2
data.splice 2, 0, "divider" data.splice 2, 0, 'divider'
callback data callback data
renderRow: (label) -> renderRow: (label) ->
if $.isArray(selectedLabel) if $.isArray(selectedLabel)
selected = "" selected = ''
$.each selectedLabel, (i, selectedLbl) -> $.each selectedLabel, (i, selectedLbl) ->
selectedLbl = selectedLbl.trim() selectedLbl = selectedLbl.trim()
if selected is "" && label.title is selectedLbl if selected is '' and label.title is selectedLbl
selected = "is-active" selected = 'is-active'
else else
selected = if label.title is selectedLabel then "is-active" else "" selected = if label.title is selectedLabel then 'is-active' else ''
"<li> "<li>
<a href='#' class='#{selected}'> <a href='#' class='#{selected}'>
...@@ -83,10 +77,24 @@ class @LabelsSelect ...@@ -83,10 +77,24 @@ class @LabelsSelect
search: search:
fields: ['title'] fields: ['title']
selectable: true selectable: true
fieldName: $(dropdown).data('field-name') toggleLabel: (selected) ->
if selected and selected.title isnt 'Any Label'
selected.title
else
defaultLabel
fieldName: $dropdown.data('field-name')
id: (label) -> id: (label) ->
if label.isAny?
''
else
label.title label.title
clicked: -> clicked: ->
if $(dropdown).hasClass "js-filter-submit" page = $('body').data 'page'
$(dropdown).parents('form').submit() isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is page is 'projects:merge_requests:index'
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
Issues.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
) )
...@@ -3,6 +3,8 @@ ...@@ -3,6 +3,8 @@
# Handles persisting and restoring the current tab selection and lazily-loading # Handles persisting and restoring the current tab selection and lazily-loading
# content on the MergeRequests#show page. # content on the MergeRequests#show page.
# #
#= require jquery.cookie
#
# ### Example Markup # ### Example Markup
# #
# <ul class="nav-links merge-request-tabs"> # <ul class="nav-links merge-request-tabs">
...@@ -68,11 +70,15 @@ class @MergeRequestTabs ...@@ -68,11 +70,15 @@ class @MergeRequestTabs
if action == 'commits' if action == 'commits'
@loadCommits($target.attr('href')) @loadCommits($target.attr('href'))
@expandView()
else if action == 'diffs' else if action == 'diffs'
@loadDiff($target.attr('href')) @loadDiff($target.attr('href'))
@shrinkView() @shrinkView()
else if action == 'builds' else if action == 'builds'
@loadBuilds($target.attr('href')) @loadBuilds($target.attr('href'))
@expandView()
else
@expandView()
@setCurrentAction(action) @setCurrentAction(action)
...@@ -189,11 +195,24 @@ class @MergeRequestTabs ...@@ -189,11 +195,24 @@ class @MergeRequestTabs
$('.container-fluid').removeClass('container-limited') $('.container-fluid').removeClass('container-limited')
shrinkView: -> shrinkView: ->
$gutterIcon = $('.js-sidebar-toggle i') $gutterIcon = $('.js-sidebar-toggle i:visible')
# Wait until listeners are set # Wait until listeners are set
setTimeout( -> setTimeout( ->
# Only when sidebar is collapsed # Only when sidebar is expanded
if $gutterIcon.is('.fa-angle-double-right') if $gutterIcon.is('.fa-angle-double-right')
$gutterIcon.closest('a').trigger('click',[true]) $gutterIcon.closest('a').trigger('click', [true])
, 0)
# Expand the issuable sidebar unless the user explicitly collapsed it
expandView: ->
return if $.cookie('collapsed_gutter') == 'true'
$gutterIcon = $('.js-sidebar-toggle i:visible')
# Wait until listeners are set
setTimeout( ->
# Only when sidebar is collapsed
if $gutterIcon.is('.fa-angle-double-left')
$gutterIcon.closest('a').trigger('click', [true])
, 0) , 0)
class @MilestoneSelect class @MilestoneSelect
constructor: -> constructor: ->
$('.js-milestone-select').each (i, dropdown) -> $('.js-milestone-select').each (i, dropdown) ->
projectId = $(dropdown).data('project-id') $dropdown = $(dropdown)
milestonesUrl = $(dropdown).data('milestones') projectId = $dropdown.data('project-id')
selectedMilestone = $(dropdown).data('selected') milestonesUrl = $dropdown.data('milestones')
showNo = $(dropdown).data('show-no') selectedMilestone = $dropdown.data('selected')
showAny = $(dropdown).data('show-any') showNo = $dropdown.data('show-no')
useId = $(dropdown).data('use-id') showAny = $dropdown.data('show-any')
useId = $dropdown.data('use-id')
defaultLabel = $dropdown.data('default-label')
$(dropdown).glDropdown( $dropdown.glDropdown(
data: (term, callback) -> data: (term, callback) ->
$.ajax( $.ajax(
url: milestonesUrl url: milestonesUrl
).done (data) -> ).done (data) ->
html = $(data)
data = []
html.find('.milestone strong a').each ->
link = $(@).attr("href").split("/")
data.push(
id: link[link.length - 1]
title: $(@).text().trim()
)
if showNo if showNo
data.unshift( data.unshift(
id: "0" id: '0'
title: 'No Milestone' title: 'No Milestone'
) )
if showAny if showAny
data.unshift( data.unshift(
isAny: true
title: 'Any Milestone' title: 'Any Milestone'
) )
if data.length > 2 if data.length > 2
data.splice 2, 0, "divider" data.splice 2, 0, 'divider'
callback(data) callback(data)
filterable: true filterable: true
search: search:
fields: ['title'] fields: ['title']
selectable: true selectable: true
fieldName: $(dropdown).data('field-name') toggleLabel: (selected) ->
if selected && 'id' of selected
selected.title
else
defaultLabel
fieldName: $dropdown.data('field-name')
text: (milestone) -> text: (milestone) ->
milestone.title milestone.title
id: (milestone) -> id: (milestone) ->
if !useId if !useId
if milestone.title isnt "Any milestone" if !milestone.isAny?
milestone.title milestone.title
else else
"" ''
else else
milestone.id milestone.id
isSelected: (milestone) -> isSelected: (milestone) ->
milestone.title is selectedMilestone milestone.title is selectedMilestone
clicked: -> clicked: ->
if $(dropdown).hasClass "js-filter-submit" page = $('body').data 'page'
$(dropdown).parents('form').submit() isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is page is 'projects:merge_requests:index'
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
Issues.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
) )
...@@ -343,6 +343,7 @@ class @Notes ...@@ -343,6 +343,7 @@ class @Notes
updateNote: (_xhr, note, _status) => updateNote: (_xhr, note, _status) =>
# Convert returned HTML to a jQuery object so we can modify it further # Convert returned HTML to a jQuery object so we can modify it further
$html = $(note.html) $html = $(note.html)
$('.js-timeago', $html).timeago()
$html.syntaxHighlight() $html.syntaxHighlight()
$html.find('.js-task-list-container').taskList('enable') $html.find('.js-task-list-container').taskList('enable')
...@@ -360,14 +361,12 @@ class @Notes ...@@ -360,14 +361,12 @@ class @Notes
showEditForm: (e) -> showEditForm: (e) ->
e.preventDefault() e.preventDefault()
note = $(this).closest(".note") note = $(this).closest(".note")
note.find(".note-body > .note-text").hide() note.addClass "is-editting"
note.find(".note-header").hide()
form = note.find(".note-edit-form") form = note.find(".note-edit-form")
isNewForm = form.is(':not(.gfm-form)') isNewForm = form.is(':not(.gfm-form)')
if isNewForm if isNewForm
form.addClass('gfm-form') form.addClass('gfm-form')
form.addClass('current-note-edit-form') form.addClass('current-note-edit-form')
form.show()
# Show the attachment delete link # Show the attachment delete link
note.find(".js-note-attachment-delete").show() note.find(".js-note-attachment-delete").show()
...@@ -401,11 +400,9 @@ class @Notes ...@@ -401,11 +400,9 @@ class @Notes
cancelEdit: (e) -> cancelEdit: (e) ->
e.preventDefault() e.preventDefault()
note = $(this).closest(".note") note = $(this).closest(".note")
note.find(".note-body > .note-text").show() note.removeClass "is-editting"
note.find(".note-header").show()
note.find(".current-note-edit-form") note.find(".current-note-edit-form")
.removeClass("current-note-edit-form") .removeClass("current-note-edit-form")
.hide()
### ###
Called in response to deleting a note of any kind. Called in response to deleting a note of any kind.
...@@ -626,10 +623,10 @@ class @Notes ...@@ -626,10 +623,10 @@ class @Notes
if closebtn.text() isnt closetext if closebtn.text() isnt closetext
closebtn.text(closetext) closebtn.text(closetext)
if reopenbtn.is(':not(.btn-comment-and-reopen)') if reopenbtn.is('.btn-comment-and-reopen')
reopenbtn.removeClass('btn-comment-and-reopen') reopenbtn.removeClass('btn-comment-and-reopen')
if closebtn.is(':not(.btn-comment-and-close)') if closebtn.is('.btn-comment-and-close')
closebtn.removeClass('btn-comment-and-close') closebtn.removeClass('btn-comment-and-close')
if discardbtn.is(':visible') if discardbtn.is(':visible')
......
...@@ -11,7 +11,6 @@ class @Project ...@@ -11,7 +11,6 @@ class @Project
$(@).toggleClass('active') $(@).toggleClass('active')
url = $("#project_clone").val() url = $("#project_clone").val()
console.log("url",url)
# Update the input field # Update the input field
$('#project_clone').val(url) $('#project_clone').val(url)
......
...@@ -3,3 +3,16 @@ class @ProjectNew ...@@ -3,3 +3,16 @@ class @ProjectNew
$('.project-edit-container').on 'ajax:before', => $('.project-edit-container').on 'ajax:before', =>
$('.project-edit-container').hide() $('.project-edit-container').hide()
$('.save-project-loader').show() $('.save-project-loader').show()
@toggleSettings()
@toggleSettingsOnclick()
toggleSettings: ->
checked = $("#project_builds_enabled").prop("checked")
if checked
$('.builds-feature').show()
else
$('.builds-feature').hide()
toggleSettingsOnclick: ->
$("#project_builds_enabled").on 'click', @toggleSettings
class @Todos
constructor: (@name) ->
@clearListeners()
@initBtnListeners()
clearListeners: ->
$('.done-todo').off('click')
$('.js-todos-mark-all').off('click')
initBtnListeners: ->
$('.done-todo').on('click', @doneClicked)
$('.js-todos-mark-all').on('click', @allDoneClicked)
doneClicked: (e) =>
e.preventDefault()
e.stopImmediatePropagation()
$this = $(e.currentTarget)
$this.disable()
$.ajax
type: 'POST'
url: $this.attr('href')
dataType: 'json'
data: '_method': 'delete'
success: (data) =>
@clearDone $this.closest('li')
@updateBadges data
allDoneClicked: (e) =>
e.preventDefault()
e.stopImmediatePropagation()
$this = $(e.currentTarget)
$this.disable()
$.ajax
type: 'POST'
url: $this.attr('href')
dataType: 'json'
data: '_method': 'delete'
success: (data) =>
$this.remove()
$('.js-todos-list').remove()
@updateBadges data
clearDone: ($row) ->
$ul = $row.closest('ul')
$row.remove()
if not $ul.find('li').length
$ul.parents('.panel').remove()
updateBadges: (data) ->
$('.todos-pending .badge, .todos-pending-count').text data.count
$('.todos-done .badge').text data.done_count
...@@ -4,14 +4,16 @@ class @UsersSelect ...@@ -4,14 +4,16 @@ class @UsersSelect
@userPath = "/autocomplete/users/:id.json" @userPath = "/autocomplete/users/:id.json"
$('.js-user-search').each (i, dropdown) => $('.js-user-search').each (i, dropdown) =>
@projectId = $(dropdown).data('project-id') $dropdown = $(dropdown)
@showCurrentUser = $(dropdown).data('current-user') @projectId = $dropdown.data('project-id')
showNullUser = $(dropdown).data('null-user') @showCurrentUser = $dropdown.data('current-user')
showAnyUser = $(dropdown).data('any-user') showNullUser = $dropdown.data('null-user')
firstUser = $(dropdown).data('first-user') showAnyUser = $dropdown.data('any-user')
selectedId = $(dropdown).data('selected') firstUser = $dropdown.data('first-user')
selectedId = $dropdown.data('selected')
$(dropdown).glDropdown( defaultLabel = $dropdown.data('default-label')
$dropdown.glDropdown(
data: (term, callback) => data: (term, callback) =>
@users term, (users) => @users term, (users) =>
if term.length is 0 if term.length is 0
...@@ -52,10 +54,21 @@ class @UsersSelect ...@@ -52,10 +54,21 @@ class @UsersSelect
search: search:
fields: ['name', 'username'] fields: ['name', 'username']
selectable: true selectable: true
fieldName: $(dropdown).data('field-name') fieldName: $dropdown.data('field-name')
toggleLabel: (selected) ->
if selected && 'id' of selected
selected.name
else
defaultLabel
clicked: -> clicked: ->
if $(dropdown).hasClass "js-filter-submit" page = $('body').data 'page'
$(dropdown).parents('form').submit() isIssueIndex = page is 'projects:issues:index'
isMRIndex = page is page is 'projects:merge_requests:index'
if $dropdown.hasClass('js-filter-submit') and (isIssueIndex or isMRIndex)
Issues.filterResults $dropdown.closest('form')
else if $dropdown.hasClass 'js-filter-submit'
$dropdown.closest('form').submit()
renderRow: (user) -> renderRow: (user) ->
username = if user.username then "@#{user.username}" else "" username = if user.username then "@#{user.username}" else ""
avatar = if user.avatar_url then user.avatar_url else false avatar = if user.avatar_url then user.avatar_url else false
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
} }
&.group-avatar, &.project-avatar, &.avatar-tile { &.group-avatar, &.project-avatar, &.avatar-tile {
@include border-radius(0px); @include border-radius(0);
} }
&.s16 { width: 16px; height: 16px; margin-right: 6px; } &.s16 { width: 16px; height: 16px; margin-right: 6px; }
......
...@@ -28,10 +28,6 @@ ...@@ -28,10 +28,6 @@
border-bottom: 1px solid $border-color; border-bottom: 1px solid $border-color;
color: $gl-gray; color: $gl-gray;
a {
color: $md-link-color;
}
&.oneline-block { &.oneline-block {
line-height: 42px; line-height: 42px;
} }
...@@ -111,10 +107,28 @@ ...@@ -111,10 +107,28 @@
margin: 0; margin: 0;
font-size: 23px; font-size: 23px;
font-weight: normal; font-weight: normal;
margin: 16px 0 5px 0; margin: 16px 0 5px;
color: #4c4e54; color: #4c4e54;
font-size: 23px; font-size: 23px;
line-height: 1.1; line-height: 1.1;
h1 {
color: #313236;
margin-bottom: 6px;
font-size: 23px;
}
.visibility-icon {
display: inline-block;
margin-left: 5px;
font-size: 18px;
color: $gray;
}
p {
padding: 0 $gl-padding;
color: #5c5d5e;
}
} }
.cover-desc { .cover-desc {
......
...@@ -208,3 +208,13 @@ ...@@ -208,3 +208,13 @@
background-color: #e4e7ed !important; background-color: #e4e7ed !important;
} }
} }
.btn-loading {
&:not(.disabled) .fa {
display: none;
}
.fa {
margin-right: 5px;
}
}
...@@ -9,6 +9,12 @@ ...@@ -9,6 +9,12 @@
border-left: $caret-width-base solid transparent; border-left: $caret-width-base solid transparent;
} }
.btn-group {
.caret {
margin-left: 0;
}
}
.dropdown { .dropdown {
position: relative; position: relative;
} }
...@@ -69,7 +75,7 @@ ...@@ -69,7 +75,7 @@
width: 240px; width: 240px;
margin-top: 2px; margin-top: 2px;
margin-bottom: 0; margin-bottom: 0;
padding: 10px 10px; padding: 10px;
font-size: 14px; font-size: 14px;
font-weight: normal; font-weight: normal;
background-color: $dropdown-bg; background-color: $dropdown-bg;
......
...@@ -3,22 +3,11 @@ ...@@ -3,22 +3,11 @@
vertical-align: top; vertical-align: top;
} }
@media (min-width: 800px) { @media (min-width: $screen-sm-min) {
.issues-filters, .issues-filters,
.issues_bulk_update { .issues_bulk_update {
select, .select2-container { .dropdown-menu-toggle {
width: 120px !important; width: 132px;
display: inline-block;
}
}
}
@media (min-width: 1200px) {
.issues-filters,
.issues_bulk_update {
select, .select2-container {
width: 150px !important;
display: inline-block;
} }
} }
} }
...@@ -16,7 +16,7 @@ body { ...@@ -16,7 +16,7 @@ body {
} }
.container .content { .container .content {
margin: 0 0; margin: 0;
} }
.navless-container { .navless-container {
......
...@@ -111,14 +111,17 @@ ul.content-list { ...@@ -111,14 +111,17 @@ ul.content-list {
> li { > li {
border-color: $table-border-color; border-color: $table-border-color;
color: $list-text-color;
font-size: $list-font-size; font-size: $list-font-size;
color: $list-text-color;
.title { .title {
color: $list-title-color;
font-weight: 600; font-weight: 600;
} }
a {
color: $gl-dark-link-color;
}
.description { .description {
p { p {
@include str-truncated; @include str-truncated;
...@@ -141,6 +144,10 @@ ul.content-list { ...@@ -141,6 +144,10 @@ ul.content-list {
} }
} }
.panel > .content-list > li {
padding: $gl-padding-top $gl-padding;
}
ul.controls { ul.controls {
padding-top: 1px; padding-top: 1px;
float: right; float: right;
......
/** /**
* Generic mixins * Generic mixins
*/ */
@mixin box-shadow($shadow) { @mixin box-shadow($shadow) {
-webkit-box-shadow: $shadow; -webkit-box-shadow: $shadow;
-moz-box-shadow: $shadow; -moz-box-shadow: $shadow;
-ms-box-shadow: $shadow; -ms-box-shadow: $shadow;
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
} }
.select2-drop { .select2-drop {
@include box-shadow(rgba(76, 86, 103, 0.247059) 0px 0px 1px 0px, rgba(31, 37, 50, 0.317647) 0px 2px 18px 0px); @include box-shadow(rgba(76, 86, 103, 0.247059) 0 0 1px 0, rgba(31, 37, 50, 0.317647) 0 2px 18px 0);
@include border-radius ($border-radius-default); @include border-radius ($border-radius-default);
border: none; border: none;
} }
......
...@@ -39,8 +39,8 @@ ...@@ -39,8 +39,8 @@
h1 { h1 {
font-size: 1.3em; font-size: 1.3em;
font-weight: 600; font-weight: 600;
margin: 24px 0 12px 0; margin: 24px 0 12px;
padding: 0 0 10px 0; padding: 0 0 10px;
border-bottom: 1px solid #e7e9ed; border-bottom: 1px solid #e7e9ed;
color: #313236; color: #313236;
} }
...@@ -48,27 +48,27 @@ ...@@ -48,27 +48,27 @@
h2 { h2 {
font-size: 1.2em; font-size: 1.2em;
font-weight: 600; font-weight: 600;
margin: 24px 0 12px 0; margin: 24px 0 12px;
color: #313236; color: #313236;
} }
h3 { h3 {
margin: 24px 0 12px 0; margin: 24px 0 12px;
font-size: 1.1em; font-size: 1.1em;
} }
h4 { h4 {
margin: 24px 0 12px 0; margin: 24px 0 12px;
font-size: 0.98em; font-size: 0.98em;
} }
h5 { h5 {
margin: 24px 0 12px 0; margin: 24px 0 12px;
font-size: 0.95em; font-size: 0.95em;
} }
h6 { h6 {
margin: 24px 0 12px 0; margin: 24px 0 12px;
font-size: 0.90em; font-size: 0.90em;
} }
...@@ -76,7 +76,7 @@ ...@@ -76,7 +76,7 @@
color: #7f8fa4; color: #7f8fa4;
font-size: inherit; font-size: inherit;
padding: 8px 21px; padding: 8px 21px;
margin: 12px 0 12px; margin: 12px 0;
border-left: 3px solid #e7e9ed; border-left: 3px solid #e7e9ed;
} }
...@@ -88,13 +88,13 @@ ...@@ -88,13 +88,13 @@
p { p {
color: #5c5d5e; color: #5c5d5e;
margin: 6px 0 0 0; margin: 6px 0 0;
} }
table { table {
@extend .table; @extend .table;
@extend .table-bordered; @extend .table-bordered;
margin: 12px 0 12px 0; margin: 12px 0;
color: #5c5d5e; color: #5c5d5e;
th { th {
background: #f8fafc; background: #f8fafc;
...@@ -102,7 +102,7 @@ ...@@ -102,7 +102,7 @@
} }
pre { pre {
margin: 12px 0 12px 0; margin: 12px 0;
font-size: 13px; font-size: 13px;
line-height: 1.6em; line-height: 1.6em;
overflow-x: auto; overflow-x: auto;
...@@ -191,7 +191,7 @@ body { ...@@ -191,7 +191,7 @@ body {
line-height: 1.3; line-height: 1.3;
font-size: 1.25em; font-size: 1.25em;
font-weight: 600; font-weight: 600;
margin: 12px 7px 12px 7px; margin: 12px 7px;
} }
h1, h2, h3, h4, h5, h6 { h1, h2, h3, h4, h5, h6 {
......
$row-hover: #f4f8fe; /*
$gl-text-color: #54565b; * Layout
$gl-text-green: #4a2; */
$gl-text-red: #d12f19;
$gl-text-orange: #d90;
$gl-header-color: #323232;
$gl-link-color: #333c48;
$md-text-color: #444;
$md-link-color: #3084bb;
$progress-color: #c0392b;
$gl-font-size: 15px;
$list-font-size: 15px;
$sidebar_collapsed_width: 62px; $sidebar_collapsed_width: 62px;
$sidebar_width: 230px; $sidebar_width: 230px;
$gutter_collapsed_width: 62px; $gutter_collapsed_width: 62px;
$gutter_width: 290px; $gutter_width: 290px;
$gutter_inner_width: 258px; $gutter_inner_width: 258px;
$avatar_radius: 50%;
$code_font_size: 13px; /*
$code_line_height: 1.5; * UI elements
*/
$border-color: #efeff1; $border-color: #efeff1;
$table-border-color: #eef0f2; $table-border-color: #eef0f2;
$background-color: #faf9f9; $background-color: #faf9f9;
$header-height: 58px;
$fixed-layout-width: 1280px; /*
$gl-gray: #5a5a5a; * Text
*/
$gl-font-size: 15px;
$gl-title-color: #333;
$gl-text-color: #555;
$gl-text-green: #4a2;
$gl-text-red: #d12f19;
$gl-text-orange: #d90;
$gl-link-color: #3084bb;
$gl-dark-link-color: #333;
$gl-placeholder-color: #8f8f8f;
$gl-gray: $gl-text-color;
$gl-header-color: $gl-title-color;
/*
* Lists
*/
$list-font-size: $gl-font-size;
$list-title-color: $gl-title-color;
$list-text-color: $gl-text-color;
/*
* Markdown
*/
$md-text-color: $gl-text-color;
$md-link-color: $gl-link-color;
/*
* Code
*/
$code_font_size: 13px;
$code_line_height: 1.5;
/*
* Padding
*/
$gl-padding: 16px; $gl-padding: 16px;
$gl-btn-padding: 10px; $gl-btn-padding: 10px;
$gl-vert-padding: 6px; $gl-vert-padding: 6px;
$gl-padding-top: 10px; $gl-padding-top: 10px;
/*
* Misc
*/
$row-hover: #f4f8fe;
$progress-color: #c0392b;
$avatar_radius: 50%;
$header-height: 58px;
$fixed-layout-width: 1280px;
$gl-avatar-size: 40px; $gl-avatar-size: 40px;
$secondary-text: #7f8fa4;
$error-exclamation-point: #e62958; $error-exclamation-point: #e62958;
$border-radius-default: 3px; $border-radius-default: 3px;
$list-title-color: #333;
$list-text-color: #555;
$btn-transparent-color: #8f8f8f; $btn-transparent-color: #8f8f8f;
$ssh-key-icon-color: #8f8f8f; $ssh-key-icon-color: #8f8f8f;
$ssh-key-icon-size: 18px; $ssh-key-icon-size: 18px;
$provider-btn-group-border: #e5e5e5; $provider-btn-group-border: #e5e5e5;
$provider-btn-not-active-color: #4688f1; $provider-btn-not-active-color: #4688f1;
......
img {
max-width: 100%;
height: auto;
}
p.details {
font-style:italic;
color:#777
}
.footer p {
font-size:small;
color:#777
}
pre.commit-message {
white-space: pre-wrap;
}
.file-stats a {
text-decoration: none;
}
.file-stats .new-file {
color: #090;
}
.file-stats .deleted-file {
color: #B00;
}
...@@ -55,7 +55,7 @@ li.commit { ...@@ -55,7 +55,7 @@ li.commit {
} }
.commit-row-message { .commit-row-message {
color: $gl-link-color; color: $gl-dark-link-color;
&:hover { &:hover {
text-decoration: underline; text-decoration: underline;
......
// Common // Common
.diff-file { .diff-file {
border: 1px solid $border-color; border: 1px solid $border-color;
border-top: none; margin-bottom: $gl-padding;
.diff-header { .diff-header {
position: relative; position: relative;
...@@ -132,7 +132,7 @@ ...@@ -132,7 +132,7 @@
} }
.image-info { .image-info {
font-size: 12px; font-size: 12px;
margin: 5px 0 0 0; margin: 5px 0 0;
color: grey; color: grey;
} }
...@@ -361,3 +361,11 @@ ...@@ -361,3 +361,11 @@
border-color: $border; border-color: $border;
} }
} }
.files {
margin-top: -1px;
.diff-file:last-child {
margin-bottom: 0;
}
}
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
font-size: $gl-font-size; font-size: $gl-font-size;
padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top); padding: $gl-padding-top 0 $gl-padding-top ($gl-avatar-size + $gl-padding-top);
border-bottom: 1px solid $table-border-color; border-bottom: 1px solid $table-border-color;
color: #7f8fa4; color: $list-text-color;
&.event-inline { &.event-inline {
.avatar { .avatar {
...@@ -21,7 +21,7 @@ ...@@ -21,7 +21,7 @@
} }
a { a {
color: #4c4e54; color: $gl-dark-link-color;
} }
.avatar { .avatar {
...@@ -31,10 +31,7 @@ ...@@ -31,10 +31,7 @@
.event-title { .event-title {
@include str-truncated(calc(100% - 174px)); @include str-truncated(calc(100% - 174px));
font-weight: 600; font-weight: 600;
color: $list-text-color;
.author_name {
color: #333;
}
} }
.event-body { .event-body {
......
@media (max-width: $screen-sm-max) {
.issuable-affix {
margin-top: 20px;
}
}
@media (max-width: $screen-md-max) {
.issuable-affix {
position: static;
}
}
@media (min-width: $screen-md-max) {
.issuable-affix {
&.affix-top {
position: static;
}
&.affix {
position: fixed;
top: 70px;
margin-right: 35px;
&.no-affix {
position: relative;
top: 0;
}
}
}
}
.issuable-details { .issuable-details {
section { section {
.issuable-discussion { .issuable-discussion {
...@@ -54,6 +23,10 @@ ...@@ -54,6 +23,10 @@
padding: 6px 10px; padding: 6px 10px;
} }
} }
&.has-labels {
margin-bottom: -5px;
}
} }
.issuable-sidebar { .issuable-sidebar {
...@@ -66,8 +39,9 @@ ...@@ -66,8 +39,9 @@
width: $gutter_inner_width; width: $gutter_inner_width;
// -- // --
&:first-child { &.issuable-sidebar-header {
padding-top: 5px; padding-top: 0;
padding-bottom: 10px;
} }
&:last-child { &:last-child {
...@@ -75,7 +49,6 @@ ...@@ -75,7 +49,6 @@
} }
span { span {
margin-top: 7px;
display: inline-block; display: inline-block;
} }
...@@ -84,7 +57,7 @@ ...@@ -84,7 +57,7 @@
} }
.issuable-count { .issuable-count {
margin-top: 7px;
} }
.gutter-toggle { .gutter-toggle {
...@@ -99,19 +72,19 @@ ...@@ -99,19 +72,19 @@
.title { .title {
color: $gl-text-color; color: $gl-text-color;
margin-bottom: 8px; margin-bottom: 10px;
line-height: 1;
.avatar { .avatar {
margin-left: 0; margin-left: 0;
} }
label {
font-weight: normal;
margin-right: 4px;
}
.edit-link { .edit-link {
color: $gl-gray; color: $gl-gray;
&:hover {
color: $md-link-color;
}
} }
} }
...@@ -144,11 +117,6 @@ ...@@ -144,11 +117,6 @@
.btn-clipboard { .btn-clipboard {
color: $gl-gray; color: $gl-gray;
} }
.participants .avatar {
margin-top: 6px;
margin-right: 2px;
}
} }
.right-sidebar { .right-sidebar {
...@@ -163,8 +131,12 @@ ...@@ -163,8 +131,12 @@
&.right-sidebar-expanded { &.right-sidebar-expanded {
width: $gutter_width; width: $gutter_width;
hr { .value {
display: none; line-height: 1;
}
.bold {
font-weight: 600;
} }
.sidebar-collapsed-icon { .sidebar-collapsed-icon {
...@@ -172,8 +144,23 @@ ...@@ -172,8 +144,23 @@
} }
.gutter-toggle { .gutter-toggle {
margin-top: 7px;
border-left: 1px solid $border-gray-light; border-left: 1px solid $border-gray-light;
} }
.assignee .avatar {
float: left;
margin-right: 10px;
margin-bottom: 0;
margin-left: 0;
}
.username {
display: block;
margin-top: 4px;
font-size: 13px;
font-weight: normal;
}
} }
.subscribe-button { .subscribe-button {
...@@ -193,28 +180,26 @@ ...@@ -193,28 +180,26 @@
width: $sidebar_collapsed_width; width: $sidebar_collapsed_width;
padding-top: 0; padding-top: 0;
hr {
margin: 0;
color: $gray-normal;
border-color: $gray-normal;
width: 62px;
margin-left: -20px
}
.block { .block {
width: $sidebar_collapsed_width - 1px; width: $sidebar_collapsed_width - 1px;
margin-left: -19px; margin-left: -19px;
padding: 15px 0 0 0; padding: 15px 0 0;
border-bottom: none; border-bottom: none;
overflow: hidden; overflow: hidden;
} }
.participants {
border-bottom: 1px solid $border-gray-light;
}
.hide-collapsed { .hide-collapsed {
display: none; display: none;
} }
.gutter-toggle { .gutter-toggle {
margin-left: -36px; width: 100%;
margin-left: 0;
padding-left: 25px;
} }
.sidebar-collapsed-icon { .sidebar-collapsed-icon {
...@@ -229,6 +214,10 @@ ...@@ -229,6 +214,10 @@
margin-top: 0; margin-top: 0;
} }
.author {
display: none;
}
.btn-clipboard { .btn-clipboard {
border: none; border: none;
...@@ -241,6 +230,11 @@ ...@@ -241,6 +230,11 @@
} }
} }
} }
.sidebar-collapsed-user {
padding-bottom: 0;
margin-bottom: 10px;
}
} }
.btn { .btn {
...@@ -251,6 +245,13 @@ ...@@ -251,6 +245,13 @@
border: 1px solid $border-gray-dark; border: 1px solid $border-gray-dark;
} }
} }
a:not(.btn) {
&:hover {
color: $md-link-color;
text-decoration: none;
}
}
} }
.btn-default.gutter-toggle { .btn-default.gutter-toggle {
...@@ -262,3 +263,37 @@ ...@@ -262,3 +263,37 @@
color: $gray-darkest; color: $gray-darkest;
} }
} }
.edited-text {
color: $gray-darkest;
.author_link {
color: $gray-darkest;
}
}
.participants-list {
margin: -5px;
}
.participants-author {
display: inline-block;
padding: 5px;
.author_link {
display: block;
}
.avatar.avatar-inline {
margin: 0;
}
}
.participants-more {
margin-top: 5px;
margin-left: 5px;
a {
color: #8c8c8c;
}
}
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
padding: 10px $gl-padding; padding: 10px $gl-padding;
position: relative; position: relative;
.issue-title { .title {
margin-bottom: 2px; margin-bottom: 2px;
} }
...@@ -130,7 +130,7 @@ form.edit-issue { ...@@ -130,7 +130,7 @@ form.edit-issue {
} }
.issue-closed-by-widget { .issue-closed-by-widget {
color: $secondary-text; color: $gl-text-color;
margin-left: 52px; margin-left: 52px;
} }
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
.login-heading h3 { .login-heading h3 {
font-weight: 300; font-weight: 300;
line-height: 1.5; line-height: 1.5;
margin: 0 0 10px 0; margin: 0 0 10px;
} }
.login-footer { .login-footer {
......
...@@ -26,7 +26,7 @@ ...@@ -26,7 +26,7 @@
display: none; display: none;
} }
.new_note, .edit_note { .new_note, .note-edit-form {
.note-form-actions { .note-form-actions {
margin-top: $gl-padding; margin-top: $gl-padding;
} }
......
...@@ -100,6 +100,18 @@ ul.notes { ...@@ -100,6 +100,18 @@ ul.notes {
display: block; display: block;
position: relative; position: relative;
&.is-editting {
.note-header,
.note-text,
.edited-text {
display: none;
}
.note-edit-form {
display: block;
}
}
.note-body { .note-body {
overflow: auto; overflow: auto;
......
...@@ -54,7 +54,7 @@ ...@@ -54,7 +54,7 @@
} }
.account-well { .account-well {
padding: 10px 10px; padding: 10px;
background-color: $help-well-bg; background-color: $help-well-bg;
border: 1px solid $help-well-border; border: 1px solid $help-well-border;
border-radius: $border-radius-base; border-radius: $border-radius-base;
......
...@@ -33,6 +33,13 @@ ...@@ -33,6 +33,13 @@
.project-settings-dropdown { .project-settings-dropdown {
margin-left: 10px; margin-left: 10px;
display: inline-block; display: inline-block;
.dropdown-menu {
left: auto;
width: auto;
right: 0;
max-width: 240px;
}
} }
} }
...@@ -61,28 +68,6 @@ ...@@ -61,28 +68,6 @@
} }
} }
.project-home-desc {
h1 {
color: #313236;
margin: 0;
margin-bottom: 6px;
font-size: 23px;
font-weight: normal;
}
.visibility-icon {
display: inline-block;
margin-left: 5px;
font-size: 18px;
color: $gray;
}
p {
padding: 0 $gl-padding;
color: #5c5d5e;
}
}
.project-repo-buttons { .project-repo-buttons {
margin-top: 20px; margin-top: 20px;
margin-bottom: 0; margin-bottom: 0;
...@@ -326,7 +311,7 @@ pre.light-well { ...@@ -326,7 +311,7 @@ pre.light-well {
} }
.git-empty { .git-empty {
margin: 0 7px 0 7px; margin: 0 7px;
h5 { h5 {
color: #5c5d5e; color: #5c5d5e;
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
#contributors { #contributors {
.contributors-list { .contributors-list {
margin: 0 0 10px 0; margin: 0 0 10px;
list-style: none; list-style: none;
padding: 0; padding: 0;
} }
......
...@@ -14,25 +14,8 @@ ...@@ -14,25 +14,8 @@
} }
.todo-item { .todo-item {
font-size: $gl-font-size;
padding-left: $gl-avatar-size + $gl-padding-top;
color: $secondary-text;
a {
color: #4c4e54;
}
.avatar {
margin-left: -($gl-avatar-size + $gl-padding-top);
}
.todo-title { .todo-title {
@include str-truncated(calc(100% - 174px)); @include str-truncated(calc(100% - 174px));
font-weight: 600;
.author-name {
color: #333;
}
} }
.todo-body { .todo-body {
......
...@@ -41,7 +41,7 @@ ...@@ -41,7 +41,7 @@
vertical-align: middle; vertical-align: middle;
i, a { i, a {
color: $gl-link-color; color: $gl-dark-link-color;
} }
img { img {
......
...@@ -61,6 +61,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController ...@@ -61,6 +61,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:session_expire_delay, :session_expire_delay,
:default_project_visibility, :default_project_visibility,
:default_snippet_visibility, :default_snippet_visibility,
:default_group_visibility,
:restricted_signup_domains_raw, :restricted_signup_domains_raw,
:version_check_enabled, :version_check_enabled,
:admin_notification_email, :admin_notification_email,
......
...@@ -5,12 +5,12 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -5,12 +5,12 @@ class Admin::GroupsController < Admin::ApplicationController
@groups = Group.all @groups = Group.all
@groups = @groups.sort(@sort = params[:sort]) @groups = @groups.sort(@sort = params[:sort])
@groups = @groups.search(params[:name]) if params[:name].present? @groups = @groups.search(params[:name]) if params[:name].present?
@groups = @groups.page(params[:page]).per(PER_PAGE) @groups = @groups.page(params[:page])
end end
def show def show
@members = @group.members.order("access_level DESC").page(params[:members_page]).per(PER_PAGE) @members = @group.members.order("access_level DESC").page(params[:members_page])
@projects = @group.projects.page(params[:projects_page]).per(PER_PAGE) @projects = @group.projects.page(params[:projects_page])
end end
def new def new
...@@ -59,6 +59,6 @@ class Admin::GroupsController < Admin::ApplicationController ...@@ -59,6 +59,6 @@ class Admin::GroupsController < Admin::ApplicationController
end end
def group_params def group_params
params.require(:group).permit(:name, :description, :path, :avatar) params.require(:group).permit(:name, :description, :path, :avatar, :visibility_level)
end end
end end
...@@ -2,7 +2,7 @@ class Admin::LabelsController < Admin::ApplicationController ...@@ -2,7 +2,7 @@ class Admin::LabelsController < Admin::ApplicationController
before_action :set_label, only: [:show, :edit, :update, :destroy] before_action :set_label, only: [:show, :edit, :update, :destroy]
def index def index
@labels = Label.templates.page(params[:page]).per(PER_PAGE) @labels = Label.templates.page(params[:page])
end end
def show def show
......
class Admin::ProjectsController < Admin::ApplicationController class Admin::ProjectsController < Admin::ApplicationController
before_action :project, only: [:show, :transfer] before_action :project, only: [:show, :transfer]
before_action :group, only: [:show, :transfer] before_action :group, only: [:show, :transfer]
before_action :repository, only: [:show, :transfer]
def index def index
@projects = Project.all @projects = Project.all
...@@ -12,15 +11,15 @@ class Admin::ProjectsController < Admin::ApplicationController ...@@ -12,15 +11,15 @@ class Admin::ProjectsController < Admin::ApplicationController
@projects = @projects.non_archived unless params[:with_archived].present? @projects = @projects.non_archived unless params[:with_archived].present?
@projects = @projects.search(params[:name]) if params[:name].present? @projects = @projects.search(params[:name]) if params[:name].present?
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(PER_PAGE) @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page])
end end
def show def show
if @group if @group
@group_members = @group.members.order("access_level DESC").page(params[:group_members_page]).per(PER_PAGE) @group_members = @group.members.order("access_level DESC").page(params[:group_members_page])
end end
@project_members = @project.project_members.page(params[:project_members_page]).per(PER_PAGE) @project_members = @project.project_members.page(params[:project_members_page])
end end
def transfer def transfer
......
...@@ -6,8 +6,6 @@ class ApplicationController < ActionController::Base ...@@ -6,8 +6,6 @@ class ApplicationController < ActionController::Base
include GitlabRoutingHelper include GitlabRoutingHelper
include PageLayoutHelper include PageLayoutHelper
PER_PAGE = 20
before_action :authenticate_user_from_token! before_action :authenticate_user_from_token!
before_action :authenticate_user! before_action :authenticate_user!
before_action :validate_user_service_ticket! before_action :validate_user_service_ticket!
...@@ -25,7 +23,6 @@ class ApplicationController < ActionController::Base ...@@ -25,7 +23,6 @@ class ApplicationController < ActionController::Base
helper_method :abilities, :can?, :current_application_settings helper_method :abilities, :can?, :current_application_settings
helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled? helper_method :import_sources_enabled?, :github_import_enabled?, :github_import_configured?, :gitlab_import_enabled?, :gitlab_import_configured?, :bitbucket_import_enabled?, :bitbucket_import_configured?, :gitorious_import_enabled?, :google_code_import_enabled?, :fogbugz_import_enabled?, :git_import_enabled?
helper_method :repository, :can_collaborate_with_project?
rescue_from Encoding::CompatibilityError do |exception| rescue_from Encoding::CompatibilityError do |exception|
log_exception(exception) log_exception(exception)
...@@ -118,47 +115,6 @@ class ApplicationController < ActionController::Base ...@@ -118,47 +115,6 @@ class ApplicationController < ActionController::Base
abilities.allowed?(object, action, subject) abilities.allowed?(object, action, subject)
end end
def project
unless @project
namespace = params[:namespace_id]
id = params[:project_id] || params[:id]
# Redirect from
# localhost/group/project.git
# to
# localhost/group/project
#
if id =~ /\.git\Z/
redirect_to request.original_url.gsub(/\.git\/?\Z/, '') and return
end
project_path = "#{namespace}/#{id}"
@project = Project.find_with_namespace(project_path)
if @project and can?(current_user, :read_project, @project)
if @project.path_with_namespace != project_path
redirect_to request.original_url.gsub(project_path, @project.path_with_namespace) and return
end
@project
elsif current_user.nil?
@project = nil
authenticate_user!
else
@project = nil
render_404 and return
end
end
@project
end
def repository
@repository ||= project.repository
end
def authorize_project!(action)
return access_denied! unless can?(current_user, action, project)
end
def access_denied! def access_denied!
render "errors/access_denied", layout: "errors", status: 404 render "errors/access_denied", layout: "errors", status: 404
end end
...@@ -167,14 +123,6 @@ class ApplicationController < ActionController::Base ...@@ -167,14 +123,6 @@ class ApplicationController < ActionController::Base
render "errors/git_not_found.html", layout: "errors", status: 404 render "errors/git_not_found.html", layout: "errors", status: 404
end end
def method_missing(method_sym, *arguments, &block)
if method_sym.to_s =~ /\Aauthorize_(.*)!\z/
authorize_project!($1.to_sym)
else
super
end
end
def render_403 def render_403
head :forbidden head :forbidden
end end
...@@ -183,10 +131,6 @@ class ApplicationController < ActionController::Base ...@@ -183,10 +131,6 @@ class ApplicationController < ActionController::Base
render file: Rails.root.join("public", "404"), layout: false, status: "404" render file: Rails.root.join("public", "404"), layout: false, status: "404"
end end
def require_non_empty_project
redirect_to @project if @project.empty_repo?
end
def no_cache_headers def no_cache_headers
response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate" response.headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
response.headers["Pragma"] = "no-cache" response.headers["Pragma"] = "no-cache"
...@@ -412,13 +356,6 @@ class ApplicationController < ActionController::Base ...@@ -412,13 +356,6 @@ class ApplicationController < ActionController::Base
current_user.nil? && root_path == request.path current_user.nil? && root_path == request.path
end end
def can_collaborate_with_project?(project = nil)
project ||= @project
can?(current_user, :push_code, project) ||
(current_user && current_user.already_forked?(project))
end
private private
def set_default_sort def set_default_sort
......
...@@ -7,7 +7,7 @@ class AutocompleteController < ApplicationController ...@@ -7,7 +7,7 @@ class AutocompleteController < ApplicationController
@users = @users.search(params[:search]) if params[:search].present? @users = @users.search(params[:search]) if params[:search].present?
@users = @users.active @users = @users.active
@users = @users.reorder(:name) @users = @users.reorder(:name)
@users = @users.page(params[:page]).per(PER_PAGE) @users = @users.page(params[:page])
if params[:search].blank? if params[:search].blank?
# Include current user if available to filter by "Me" # Include current user if available to filter by "Me"
......
...@@ -6,7 +6,7 @@ module GlobalMilestones ...@@ -6,7 +6,7 @@ module GlobalMilestones
@milestones = MilestonesFinder.new.execute(@projects, params) @milestones = MilestonesFinder.new.execute(@projects, params)
@milestones = GlobalMilestone.build_collection(@milestones) @milestones = GlobalMilestone.build_collection(@milestones)
@milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date } @milestones = @milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
@milestones = Kaminari.paginate_array(@milestones).page(params[:page]).per(ApplicationController::PER_PAGE) @milestones = Kaminari.paginate_array(@milestones).page(params[:page])
end end
def milestone def milestone
......
module IssuableActions
extend ActiveSupport::Concern
included do
before_action :authorize_destroy_issuable!, only: :destroy
end
def destroy
issuable.destroy
name = issuable.class.name.titleize.downcase
flash[:notice] = "The #{name} was successfully deleted."
redirect_to polymorphic_path([@project.namespace.becomes(Namespace), @project, issuable.class])
end
private
def authorize_destroy_issuable!
unless current_user.can?(:"destroy_#{issuable.to_ability_name}", issuable)
return access_denied!
end
end
end
...@@ -3,7 +3,7 @@ module IssuesAction ...@@ -3,7 +3,7 @@ module IssuesAction
def issues def issues
@issues = get_issues_collection.non_archived @issues = get_issues_collection.non_archived
@issues = @issues.page(params[:page]).per(ApplicationController::PER_PAGE) @issues = @issues.page(params[:page])
@issues = @issues.preload(:author, :project) @issues = @issues.preload(:author, :project)
@label = @issuable_finder.labels.first @label = @issuable_finder.labels.first
......
...@@ -3,7 +3,7 @@ module MergeRequestsAction ...@@ -3,7 +3,7 @@ module MergeRequestsAction
def merge_requests def merge_requests
@merge_requests = get_merge_requests_collection.non_archived @merge_requests = get_merge_requests_collection.non_archived
@merge_requests = @merge_requests.page(params[:page]).per(ApplicationController::PER_PAGE) @merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(:author, :target_project) @merge_requests = @merge_requests.preload(:author, :target_project)
@label = @issuable_finder.labels.first @label = @issuable_finder.labels.first
......
class Dashboard::GroupsController < Dashboard::ApplicationController class Dashboard::GroupsController < Dashboard::ApplicationController
def index def index
@group_members = current_user.group_members.page(params[:page]).per(PER_PAGE) @group_members = current_user.group_members.page(params[:page])
end end
end end
...@@ -8,7 +8,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -8,7 +8,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = filter_projects(@projects) @projects = filter_projects(@projects)
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace)
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]).per(PER_PAGE) @projects = @projects.page(params[:page])
@last_push = current_user.recent_push @last_push = current_user.recent_push
...@@ -32,7 +32,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController ...@@ -32,7 +32,7 @@ class Dashboard::ProjectsController < Dashboard::ApplicationController
@projects = filter_projects(@projects) @projects = filter_projects(@projects)
@projects = @projects.includes(:namespace, :forked_from_project, :tags) @projects = @projects.includes(:namespace, :forked_from_project, :tags)
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]).per(PER_PAGE) @projects = @projects.page(params[:page])
@last_push = current_user.recent_push @last_push = current_user.recent_push
@groups = [] @groups = []
......
...@@ -6,6 +6,6 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController ...@@ -6,6 +6,6 @@ class Dashboard::SnippetsController < Dashboard::ApplicationController
user: current_user, user: current_user,
scope: params[:scope] scope: params[:scope]
) )
@snippets = @snippets.page(params[:page]).per(PER_PAGE) @snippets = @snippets.page(params[:page])
end end
end end
class Dashboard::TodosController < Dashboard::ApplicationController class Dashboard::TodosController < Dashboard::ApplicationController
before_action :find_todos, only: [:index, :destroy_all] before_action :find_todos, only: [:index, :destroy, :destroy_all]
def index def index
@todos = @todos.page(params[:page]).per(PER_PAGE) @todos = @todos.page(params[:page])
end end
def destroy def destroy
todo.done! todo.done
todo_notice = 'Todo was successfully marked as done.'
respond_to do |format| respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: 'Todo was successfully marked as done.' } format.html { redirect_to dashboard_todos_path, notice: todo_notice }
format.js { render nothing: true } format.js { render nothing: true }
format.json do
render json: { count: @todos.size, done_count: current_user.todos.done.count }
end
end end
end end
def destroy_all def destroy_all
@todos.each(&:done!) @todos.each(&:done)
respond_to do |format| respond_to do |format|
format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' } format.html { redirect_to dashboard_todos_path, notice: 'All todos were marked as done.' }
format.js { render nothing: true } format.js { render nothing: true }
format.json do
find_todos
render json: { count: @todos.size, done_count: current_user.todos.done.count }
end
end end
end end
......
...@@ -3,7 +3,7 @@ class DashboardController < Dashboard::ApplicationController ...@@ -3,7 +3,7 @@ class DashboardController < Dashboard::ApplicationController
include MergeRequestsAction include MergeRequestsAction
before_action :event_filter, only: :activity before_action :event_filter, only: :activity
before_action :projects, only: [:issues, :merge_requests] before_action :projects, only: [:issues, :merge_requests, :labels, :milestones]
respond_to :html respond_to :html
...@@ -20,6 +20,29 @@ class DashboardController < Dashboard::ApplicationController ...@@ -20,6 +20,29 @@ class DashboardController < Dashboard::ApplicationController
end end
end end
def labels
labels = Label.where(project_id: @projects).select(:title, :color).uniq(:title)
respond_to do |format|
format.json do
render json: labels
end
end
end
def milestones
milestones = Milestone.where(project_id: @projects).active
epoch = DateTime.parse('1970-01-01')
grouped_milestones = GlobalMilestone.build_collection(milestones)
grouped_milestones = grouped_milestones.sort_by { |x| x.due_date.nil? ? epoch : x.due_date }
respond_to do |format|
format.json do
render json: grouped_milestones
end
end
end
protected protected
def load_events def load_events
......
class Explore::GroupsController < Explore::ApplicationController class Explore::GroupsController < Explore::ApplicationController
def index def index
@groups = Group.order_id_desc @groups = GroupsFinder.new.execute(current_user)
@groups = @groups.search(params[:search]) if params[:search].present? @groups = @groups.search(params[:search]) if params[:search].present?
@groups = @groups.sort(@sort = params[:sort]) @groups = @groups.sort(@sort = params[:sort])
@groups = @groups.page(params[:page]).per(PER_PAGE) @groups = @groups.page(params[:page])
end end
end end
...@@ -8,7 +8,7 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -8,7 +8,7 @@ class Explore::ProjectsController < Explore::ApplicationController
@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 = filter_projects(@projects) @projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE) @projects = @projects.includes(:namespace).page(params[:page])
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -23,7 +23,7 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -23,7 +23,7 @@ class Explore::ProjectsController < Explore::ApplicationController
def trending def trending
@projects = TrendingProjectsFinder.new.execute(current_user) @projects = TrendingProjectsFinder.new.execute(current_user)
@projects = filter_projects(@projects) @projects = filter_projects(@projects)
@projects = @projects.page(params[:page]).per(PER_PAGE) @projects = @projects.page(params[:page])
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -39,7 +39,7 @@ class Explore::ProjectsController < Explore::ApplicationController ...@@ -39,7 +39,7 @@ class Explore::ProjectsController < Explore::ApplicationController
@projects = ProjectsFinder.new.execute(current_user) @projects = ProjectsFinder.new.execute(current_user)
@projects = filter_projects(@projects) @projects = filter_projects(@projects)
@projects = @projects.reorder('star_count DESC') @projects = @projects.reorder('star_count DESC')
@projects = @projects.page(params[:page]).per(PER_PAGE) @projects = @projects.page(params[:page])
respond_to do |format| respond_to do |format|
format.html format.html
......
class Explore::SnippetsController < Explore::ApplicationController class Explore::SnippetsController < Explore::ApplicationController
def index def index
@snippets = SnippetsFinder.new.execute(current_user, filter: :all) @snippets = SnippetsFinder.new.execute(current_user, filter: :all)
@snippets = @snippets.page(params[:page]).per(PER_PAGE) @snippets = @snippets.page(params[:page])
end end
end end
class Groups::ApplicationController < ApplicationController class Groups::ApplicationController < ApplicationController
layout 'group' layout 'group'
skip_before_action :authenticate_user!
before_action :group before_action :group
private private
def group def group
@group ||= Group.find_by(path: params[:group_id]) unless @group
end id = params[:group_id] || params[:id]
@group = Group.find_by(path: id)
unless @group && can?(current_user, :read_group, @group)
@group = nil
def authorize_read_group!
unless @group and can?(current_user, :read_group, @group)
if current_user.nil? if current_user.nil?
return authenticate_user! authenticate_user!
else else
return render_404 render_404
end end
end end
end end
@group
end
def group_projects
@projects ||= GroupProjectsFinder.new(group).execute(current_user)
end
def authorize_admin_group! def authorize_admin_group!
unless can?(current_user, :admin_group, group) unless can?(current_user, :admin_group, group)
return render_404 return render_404
......
class Groups::AvatarsController < Groups::ApplicationController class Groups::AvatarsController < Groups::ApplicationController
before_action :authorize_admin_group!
def destroy def destroy
@group.remove_avatar! @group.remove_avatar!
@group.save @group.save
......
class Groups::GroupMembersController < Groups::ApplicationController class Groups::GroupMembersController < Groups::ApplicationController
skip_before_action :authenticate_user!, only: [:index]
# Authorize # Authorize
before_action :authorize_read_group!
before_action :authorize_admin_group_member!, except: [:index, :leave] before_action :authorize_admin_group_member!, except: [:index, :leave]
def index def index
......
class Groups::MilestonesController < Groups::ApplicationController class Groups::MilestonesController < Groups::ApplicationController
include GlobalMilestones include GlobalMilestones
before_action :projects before_action :group_projects
before_action :milestones, only: [:index] before_action :milestones, only: [:index]
before_action :milestone, only: [:show, :update] before_action :milestone, only: [:show, :update]
before_action :authorize_group_milestone!, only: [:create, :update] before_action :authorize_admin_milestones!, only: [:new, :create, :update]
def index def index
end end
...@@ -17,7 +17,7 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -17,7 +17,7 @@ class Groups::MilestonesController < Groups::ApplicationController
project_ids = params[:milestone][:project_ids] project_ids = params[:milestone][:project_ids]
title = milestone_params[:title] title = milestone_params[:title]
@group.projects.where(id: project_ids).each do |project| @projects.where(id: project_ids).each do |project|
Milestones::CreateService.new(project, current_user, milestone_params).execute Milestones::CreateService.new(project, current_user, milestone_params).execute
end end
...@@ -37,7 +37,7 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -37,7 +37,7 @@ class Groups::MilestonesController < Groups::ApplicationController
private private
def authorize_group_milestone! def authorize_admin_milestones!
return render_404 unless can?(current_user, :admin_milestones, group) return render_404 unless can?(current_user, :admin_milestones, group)
end end
...@@ -48,8 +48,4 @@ class Groups::MilestonesController < Groups::ApplicationController ...@@ -48,8 +48,4 @@ class Groups::MilestonesController < Groups::ApplicationController
def milestone_path(title) def milestone_path(title)
group_milestone_path(@group, title.to_slug.to_s, title: title) group_milestone_path(@group, title.to_slug.to_s, title: title)
end end
def projects
@projects ||= @group.projects
end
end end
...@@ -5,16 +5,15 @@ class GroupsController < Groups::ApplicationController ...@@ -5,16 +5,15 @@ class GroupsController < Groups::ApplicationController
respond_to :html respond_to :html
skip_before_action :authenticate_user!, only: [:index, :show, :issues, :merge_requests] before_action :authenticate_user!, only: [:new, :create]
before_action :group, except: [:index, :new, :create] before_action :group, except: [:index, :new, :create]
# Authorize # Authorize
before_action :authorize_read_group!, except: [:index, :show, :new, :create, :autocomplete]
before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects] before_action :authorize_admin_group!, only: [:edit, :update, :destroy, :projects]
before_action :authorize_create_group!, only: [:new, :create] before_action :authorize_create_group!, only: [:new, :create]
# Load group projects # Load group projects
before_action :load_projects, except: [:index, :new, :create, :projects, :edit, :update, :autocomplete] before_action :group_projects, only: [:show, :projects, :activity, :issues, :merge_requests]
before_action :event_filter, only: [:activity] before_action :event_filter, only: [:activity]
layout :determine_layout layout :determine_layout
...@@ -28,11 +27,9 @@ class GroupsController < Groups::ApplicationController ...@@ -28,11 +27,9 @@ class GroupsController < Groups::ApplicationController
end end
def create def create
@group = Group.new(group_params) @group = Groups::CreateService.new(current_user, group_params).execute
@group.name = @group.path.dup unless @group.name
if @group.save if @group.persisted?
@group.add_owner(current_user)
redirect_to @group, notice: "Group '#{@group.name}' was successfully created." redirect_to @group, notice: "Group '#{@group.name}' was successfully created."
else else
render action: "new" render action: "new"
...@@ -41,12 +38,13 @@ class GroupsController < Groups::ApplicationController ...@@ -41,12 +38,13 @@ class GroupsController < Groups::ApplicationController
def show def show
@last_push = current_user.recent_push if current_user @last_push = current_user.recent_push if current_user
@projects = @projects.includes(:namespace) @projects = @projects.includes(:namespace)
@projects = filter_projects(@projects) @projects = filter_projects(@projects)
@projects = @projects.sort(@sort = params[:sort]) @projects = @projects.sort(@sort = params[:sort])
@projects = @projects.page(params[:page]).per(PER_PAGE) if params[:filter_projects].blank? @projects = @projects.page(params[:page]) if params[:filter_projects].blank?
@shared_projects = @group.shared_projects @shared_projects = GroupProjectsFinder.new(group, only_shared: true).execute(current_user)
respond_to do |format| respond_to do |format|
format.html format.html
...@@ -83,7 +81,7 @@ class GroupsController < Groups::ApplicationController ...@@ -83,7 +81,7 @@ class GroupsController < Groups::ApplicationController
end end
def update def update
if @group.update_attributes(group_params) if Groups::UpdateService.new(@group, current_user, group_params).execute
redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated." redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated."
else else
render action: "edit" render action: "edit"
...@@ -98,26 +96,6 @@ class GroupsController < Groups::ApplicationController ...@@ -98,26 +96,6 @@ class GroupsController < Groups::ApplicationController
protected protected
def group
@group ||= Group.find_by(path: params[:id])
@group || render_404
end
def load_projects
@projects ||= ProjectsFinder.new.execute(current_user, group: group).sorted_by_activity
end
# Dont allow unauthorized access to group
def authorize_read_group!
unless @group and (@projects.present? or can?(current_user, :read_group, @group))
if current_user.nil?
return authenticate_user!
else
return render_404
end
end
end
def authorize_create_group! def authorize_create_group!
unless can?(current_user, :create_group, nil) unless can?(current_user, :create_group, nil)
return render_404 return render_404
...@@ -135,7 +113,7 @@ class GroupsController < Groups::ApplicationController ...@@ -135,7 +113,7 @@ class GroupsController < Groups::ApplicationController
end end
def group_params def group_params
params.require(:group).permit(:name, :description, :path, :avatar, :public, :share_with_group_lock) params.require(:group).permit(:name, :description, :path, :avatar, :public, :visibility_level, :share_with_group_lock)
end end
def load_events def load_events
......
...@@ -14,7 +14,7 @@ class NamespacesController < ApplicationController ...@@ -14,7 +14,7 @@ class NamespacesController < ApplicationController
if user if user
redirect_to user_path(user) redirect_to user_path(user)
elsif group elsif group && can?(current_user, :read_group, namespace)
redirect_to group_path(group) redirect_to group_path(group)
elsif current_user.nil? elsif current_user.nil?
authenticate_user! authenticate_user!
......
...@@ -7,6 +7,7 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController ...@@ -7,6 +7,7 @@ class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController
if pre_auth.authorizable? if pre_auth.authorizable?
if skip_authorization? || matching_token? if skip_authorization? || matching_token?
auth = authorization.authorize auth = authorization.authorize
session.delete(:user_return_to)
redirect_to auth.redirect_uri redirect_to auth.redirect_uri
else else
render "doorkeeper/authorizations/new" render "doorkeeper/authorizations/new"
......
...@@ -35,8 +35,7 @@ class ProfilesController < Profiles::ApplicationController ...@@ -35,8 +35,7 @@ class ProfilesController < Profiles::ApplicationController
def audit_log def audit_log
@events = AuditEvent.where(entity_type: "User", entity_id: current_user.id). @events = AuditEvent.where(entity_type: "User", entity_id: current_user.id).
order("created_at DESC"). order("created_at DESC").
page(params[:page]). page(params[:page])
per(PER_PAGE)
end end
def update_username def update_username
......
class Projects::ApplicationController < ApplicationController class Projects::ApplicationController < ApplicationController
skip_before_action :authenticate_user!
before_action :project before_action :project
before_action :repository before_action :repository
layout 'project' layout 'project'
def authenticate_user! helper_method :repository, :can_collaborate_with_project?
# Restrict access to Projects area only
# for non-signed users private
if !current_user
def project
unless @project
namespace = params[:namespace_id]
id = params[:project_id] || params[:id] id = params[:project_id] || params[:id]
project_with_namespace = "#{params[:namespace_id]}/#{id}"
@project = Project.find_with_namespace(project_with_namespace)
return if @project && @project.public? # Redirect from
# localhost/group/project.git
# to
# localhost/group/project
#
if id =~ /\.git\Z/
redirect_to request.original_url.gsub(/\.git\/?\Z/, '')
return
end
project_path = "#{namespace}/#{id}"
@project = Project.find_with_namespace(project_path)
if @project && can?(current_user, :read_project, @project)
if @project.path_with_namespace != project_path
redirect_to request.original_url.gsub(project_path, @project.path_with_namespace)
end
else
@project = nil
if current_user.nil?
authenticate_user!
else
render_404
end
end
end
@project
end end
def repository
@repository ||= project.repository
end
def can_collaborate_with_project?(project = nil)
project ||= @project
can?(current_user, :push_code, project) ||
(current_user && current_user.already_forked?(project))
end
def authorize_project!(action)
return access_denied! unless can?(current_user, action, project)
end
def method_missing(method_sym, *arguments, &block)
if method_sym.to_s =~ /\Aauthorize_(.*)!\z/
authorize_project!($1.to_sym)
else
super super
end end
end
def require_non_empty_project
redirect_to namespace_project_path(@project.namespace, @project) if @project.empty_repo?
end
def require_branch_head def require_branch_head
unless @repository.branch_names.include?(@ref) unless @repository.branch_names.include?(@ref)
...@@ -26,8 +80,6 @@ class Projects::ApplicationController < ApplicationController ...@@ -26,8 +80,6 @@ class Projects::ApplicationController < ApplicationController
end end
end end
private
def apply_diff_view_cookie! def apply_diff_view_cookie!
view = params[:view] || cookies[:diff_view] view = params[:view] || cookies[:diff_view]
cookies.permanent[:diff_view] = params[:view] = view if view cookies.permanent[:diff_view] = params[:view] = view if view
......
class Projects::AvatarsController < Projects::ApplicationController class Projects::AvatarsController < Projects::ApplicationController
include BlobHelper include BlobHelper
before_action :project before_action :authorize_admin_project!, only: [:destroy]
def show def show
@blob = @repository.blob_at_branch('master', @project.avatar_in_git) @blob = @repository.blob_at_branch('master', @project.avatar_in_git)
......
...@@ -8,7 +8,7 @@ class Projects::BranchesController < Projects::ApplicationController ...@@ -8,7 +8,7 @@ class Projects::BranchesController < Projects::ApplicationController
def index def index
@sort = params[:sort] || 'name' @sort = params[:sort] || 'name'
@branches = @repository.branches_sorted_by(@sort) @branches = @repository.branches_sorted_by(@sort)
@branches = Kaminari.paginate_array(@branches).page(params[:page]).per(PER_PAGE) @branches = Kaminari.paginate_array(@branches).page(params[:page])
@max_commits = @branches.reduce(0) do |memo, branch| @max_commits = @branches.reduce(0) do |memo, branch|
diverging_commit_counts = repository.diverging_commit_counts(branch) diverging_commit_counts = repository.diverging_commit_counts(branch)
......
...@@ -15,7 +15,7 @@ class Projects::ForksController < Projects::ApplicationController ...@@ -15,7 +15,7 @@ class Projects::ForksController < Projects::ApplicationController
@sort = params[:sort] || 'id_desc' @sort = params[:sort] || 'id_desc'
@forks = @forks.search(params[:filter_projects]) if params[:filter_projects].present? @forks = @forks.search(params[:filter_projects]) if params[:filter_projects].present?
@forks = @forks.order_by(@sort).page(params[:page]).per(PER_PAGE) @forks = @forks.order_by(@sort).page(params[:page])
respond_to do |format| respond_to do |format|
format.html format.html
......
class Projects::IssuesController < Projects::ApplicationController class Projects::IssuesController < Projects::ApplicationController
include ToggleSubscriptionAction include ToggleSubscriptionAction
include IssuableActions
before_action :module_enabled before_action :module_enabled
before_action :issue, only: [:edit, :update, :show] before_action :issue, only: [:edit, :update, :show]
# Allow read any issue # Allow read any issue
before_action :authorize_read_issue! before_action :authorize_read_issue!, only: [:show]
# Allow write(create) issue # Allow write(create) issue
before_action :authorize_create_issue!, only: [:new, :create] before_action :authorize_create_issue!, only: [:new, :create]
...@@ -33,7 +34,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -33,7 +34,7 @@ class Projects::IssuesController < Projects::ApplicationController
end end
end end
@issues = @issues.page(params[:page]).per(PER_PAGE) @issues = @issues.page(params[:page])
@label = @project.labels.find_by(title: params[:label_name]) @label = @project.labels.find_by(title: params[:label_name])
respond_to do |format| respond_to do |format|
...@@ -90,6 +91,12 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -90,6 +91,12 @@ class Projects::IssuesController < Projects::ApplicationController
def update def update
@issue = Issues::UpdateService.new(project, current_user, issue_params).execute(issue) @issue = Issues::UpdateService.new(project, current_user, issue_params).execute(issue)
if params[:move_to_project_id].to_i > 0
new_project = Project.find(params[:move_to_project_id])
move_service = Issues::MoveService.new(project, current_user)
@issue = move_service.execute(@issue, new_project)
end
respond_to do |format| respond_to do |format|
format.js format.js
format.html do format.html do
...@@ -127,6 +134,11 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -127,6 +134,11 @@ class Projects::IssuesController < Projects::ApplicationController
end end
end end
alias_method :subscribable_resource, :issue alias_method :subscribable_resource, :issue
alias_method :issuable, :issue
def authorize_read_issue!
return render_404 unless can?(current_user, :read_issue, @issue)
end
def authorize_update_issue! def authorize_update_issue!
return render_404 unless can?(current_user, :update_issue, @issue) return render_404 unless can?(current_user, :update_issue, @issue)
...@@ -158,7 +170,7 @@ class Projects::IssuesController < Projects::ApplicationController ...@@ -158,7 +170,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, :confidential,
:milestone_id, :state_event, :task_num, label_ids: [] :milestone_id, :state_event, :task_num, label_ids: []
) )
end end
......
...@@ -11,7 +11,14 @@ class Projects::LabelsController < Projects::ApplicationController ...@@ -11,7 +11,14 @@ class Projects::LabelsController < Projects::ApplicationController
respond_to :js, :html respond_to :js, :html
def index def index
@labels = @project.labels.page(params[:page]).per(PER_PAGE) @labels = @project.labels.page(params[:page])
respond_to do |format|
format.html
format.json do
render json: @project.labels
end
end
end end
def new def new
......
class Projects::MergeRequestsController < Projects::ApplicationController class Projects::MergeRequestsController < Projects::ApplicationController
include ToggleSubscriptionAction include ToggleSubscriptionAction
include DiffHelper include DiffHelper
include IssuableActions
before_action :module_enabled before_action :module_enabled
before_action :merge_request, only: [ before_action :merge_request, only: [
:edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check, :edit, :update, :show, :diffs, :commits, :builds, :merge, :merge_check,
:ci_status, :cancel_merge_when_build_succeeds :ci_status, :toggle_subscription, :cancel_merge_when_build_succeeds, :remove_wip
] ]
before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits, :builds] before_action :closes_issues, only: [:edit, :update, :show, :diffs, :commits, :builds]
before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds] before_action :validates_merge_request, only: [:show, :diffs, :commits, :builds]
...@@ -20,7 +21,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -20,7 +21,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :authorize_create_merge_request!, only: [:new, :create] before_action :authorize_create_merge_request!, only: [:new, :create]
# Allow modify merge_request # Allow modify merge_request
before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :sort] before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :remove_wip, :sort]
def index def index
terms = params['issue_search'] terms = params['issue_search']
...@@ -34,7 +35,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -34,7 +35,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
end end
@merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE) @merge_requests = @merge_requests.page(params[:page])
@merge_requests = @merge_requests.preload(:target_project) @merge_requests = @merge_requests.preload(:target_project)
@label = @project.labels.find_by(title: params[:label_name]) @label = @project.labels.find_by(title: params[:label_name])
...@@ -164,6 +165,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -164,6 +165,13 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end end
end end
def remove_wip
MergeRequests::UpdateService.new(project, current_user, title: @merge_request.wipless_title).execute(@merge_request)
redirect_to namespace_project_merge_request_path(@project.namespace, @project, @merge_request),
notice: "The merge request can now be merged."
end
def merge_check def merge_check
@merge_request.check_if_can_be_merged @merge_request.check_if_can_be_merged
...@@ -248,6 +256,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController ...@@ -248,6 +256,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@merge_request ||= @project.merge_requests.find_by!(iid: params[:id]) @merge_request ||= @project.merge_requests.find_by!(iid: params[:id])
end end
alias_method :subscribable_resource, :merge_request alias_method :subscribable_resource, :merge_request
alias_method :issuable, :merge_request
def closes_issues def closes_issues
@closes_issues ||= @merge_request.closes_issues @closes_issues ||= @merge_request.closes_issues
......
...@@ -19,7 +19,15 @@ class Projects::MilestonesController < Projects::ApplicationController ...@@ -19,7 +19,15 @@ class Projects::MilestonesController < Projects::ApplicationController
end end
@milestones = @milestones.includes(:project) @milestones = @milestones.includes(:project)
@milestones = @milestones.page(params[:page]).per(PER_PAGE)
respond_to do |format|
format.html do
@milestones = @milestones.page(params[:page])
end
format.json do
render json: @milestones
end
end
end end
def new def new
......
...@@ -21,7 +21,7 @@ class Projects::SnippetsController < Projects::ApplicationController ...@@ -21,7 +21,7 @@ class Projects::SnippetsController < Projects::ApplicationController
filter: :by_project, filter: :by_project,
project: @project project: @project
}) })
@snippets = @snippets.page(params[:page]).per(PER_PAGE) @snippets = @snippets.page(params[:page])
end end
def new def new
......
...@@ -7,7 +7,7 @@ class Projects::TagsController < Projects::ApplicationController ...@@ -7,7 +7,7 @@ class Projects::TagsController < Projects::ApplicationController
def index def index
sorted = VersionSorter.rsort(@repository.tag_names) sorted = VersionSorter.rsort(@repository.tag_names)
@tags = Kaminari.paginate_array(sorted).page(params[:page]).per(PER_PAGE) @tags = Kaminari.paginate_array(sorted).page(params[:page])
@releases = project.releases.where(tag: @tags) @releases = project.releases.where(tag: @tags)
end end
......
class Projects::UploadsController < Projects::ApplicationController class Projects::UploadsController < Projects::ApplicationController
skip_before_action :authenticate_user!, :reject_blocked!, :project, skip_before_action :reject_blocked!, :project,
:repository, if: -> { action_name == 'show' && image? } :repository, if: -> { action_name == 'show' && image? }
before_action :authorize_upload_file!, only: [:create]
def create def create
link_to_file = ::Projects::UploadService.new(project, params[:file]). link_to_file = ::Projects::UploadService.new(project, params[:file]).
execute execute
...@@ -26,6 +28,8 @@ class Projects::UploadsController < Projects::ApplicationController ...@@ -26,6 +28,8 @@ class Projects::UploadsController < Projects::ApplicationController
send_file uploader.file.path, disposition: disposition send_file uploader.file.path, disposition: disposition
end end
private
def uploader def uploader
return @uploader if defined?(@uploader) return @uploader if defined?(@uploader)
......
...@@ -7,7 +7,7 @@ class Projects::WikisController < Projects::ApplicationController ...@@ -7,7 +7,7 @@ class Projects::WikisController < Projects::ApplicationController
before_action :load_project_wiki before_action :load_project_wiki
def pages def pages
@wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(PER_PAGE) @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page])
end end
def show def show
......
class ProjectsController < ApplicationController class ProjectsController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
skip_before_action :authenticate_user!, only: [:show, :activity] before_action :authenticate_user!, except: [:show, :activity]
before_action :project, except: [:new, :create] before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create] before_action :repository, except: [:new, :create]
before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists? before_action :assign_ref_vars, :tree, only: [:show], if: :repo_exists?
...@@ -134,7 +134,7 @@ class ProjectsController < ApplicationController ...@@ -134,7 +134,7 @@ class ProjectsController < ApplicationController
def autocomplete_sources def autocomplete_sources
note_type = params['type'] note_type = params['type']
note_id = params['type_id'] note_id = params['type_id']
autocomplete = ::Projects::AutocompleteService.new(@project) autocomplete = ::Projects::AutocompleteService.new(@project, current_user)
participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id) participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id)
@suggestions = { @suggestions = {
......
...@@ -25,7 +25,7 @@ class SnippetsController < ApplicationController ...@@ -25,7 +25,7 @@ class SnippetsController < ApplicationController
filter: :by_user, filter: :by_user,
user: @user, user: @user,
scope: params[:scope] }). scope: params[:scope] }).
page(params[:page]).per(PER_PAGE) page(params[:page])
render 'index' render 'index'
else else
......
...@@ -100,7 +100,7 @@ class UsersController < ApplicationController ...@@ -100,7 +100,7 @@ class UsersController < ApplicationController
def load_projects def load_projects
@projects = @projects =
PersonalProjectsFinder.new(@user).execute(current_user) PersonalProjectsFinder.new(@user).execute(current_user)
.page(params[:page]).per(PER_PAGE) .page(params[:page])
end end
def load_contributed_projects def load_contributed_projects
...@@ -108,7 +108,7 @@ class UsersController < ApplicationController ...@@ -108,7 +108,7 @@ class UsersController < ApplicationController
end end
def load_groups def load_groups
@groups = @user.groups.order_id_desc @groups = JoinedGroupsFinder.new(@user).execute(current_user)
end end
def projects_for_current_user def projects_for_current_user
......
class ContributedProjectsFinder class ContributedProjectsFinder < UnionFinder
def initialize(user) def initialize(user)
@user = user @user = user
end end
...@@ -11,27 +11,19 @@ class ContributedProjectsFinder ...@@ -11,27 +11,19 @@ class ContributedProjectsFinder
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
def execute(current_user = nil) def execute(current_user = nil)
if current_user segments = all_projects(current_user)
relation = projects_visible_to_user(current_user)
else
relation = public_projects
end
relation.includes(:namespace).order_id_desc find_union(segments, Project).includes(:namespace).order_id_desc
end end
private private
def projects_visible_to_user(current_user) def all_projects(current_user)
authorized = @user.contributed_projects.visible_to_user(current_user) projects = []
union = Gitlab::SQL::Union.
new([authorized.select(:id), public_projects.select(:id)])
Project.where("projects.id IN (#{union.to_sql})") projects << @user.contributed_projects.visible_to_user(current_user) if current_user
end projects << @user.contributed_projects.public_to_user(current_user)
def public_projects projects
@user.contributed_projects.public_only
end end
end end
class GroupProjectsFinder < UnionFinder
def initialize(group, options = {})
@group = group
@options = options
end
def execute(current_user = nil)
segments = group_projects(current_user)
find_union(segments, Project)
end
private
def group_projects(current_user)
only_owned = @options.fetch(:only_owned, false)
only_shared = @options.fetch(:only_shared, false)
projects = []
if current_user
if @group.users.include?(current_user)
projects << @group.projects unless only_shared
projects << @group.shared_projects unless only_owned
else
unless only_shared
projects << @group.projects.visible_to_user(current_user)
projects << @group.projects.public_to_user(current_user)
end
unless only_owned
projects << @group.shared_projects.visible_to_user(current_user)
projects << @group.shared_projects.public_to_user(current_user)
end
end
else
projects << @group.projects.public_only unless only_shared
projects << @group.shared_projects.public_only unless only_owned
end
projects
end
end
class GroupsFinder < UnionFinder
def execute(current_user = nil)
segments = all_groups(current_user)
find_union(segments, Group).order_id_desc
end
private
def all_groups(current_user)
groups = []
groups << current_user.authorized_groups if current_user
groups << Group.unscoped.public_to_user(current_user)
groups
end
end
...@@ -80,9 +80,10 @@ class IssuableFinder ...@@ -80,9 +80,10 @@ class IssuableFinder
@projects = project @projects = project
elsif current_user && params[:authorized_only].presence && !current_user_related? elsif current_user && params[:authorized_only].presence && !current_user_related?
@projects = current_user.authorized_projects.reorder(nil) @projects = current_user.authorized_projects.reorder(nil)
elsif group
@projects = GroupProjectsFinder.new(group).execute(current_user).reorder(nil)
else else
@projects = ProjectsFinder.new.execute(current_user, group: group). @projects = ProjectsFinder.new.execute(current_user).reorder(nil)
reorder(nil)
end end
end end
...@@ -171,14 +172,12 @@ class IssuableFinder ...@@ -171,14 +172,12 @@ class IssuableFinder
def by_scope(items) def by_scope(items)
case params[:scope] case params[:scope]
when 'created-by-me', 'authored' then when 'created-by-me', 'authored'
items.where(author_id: current_user.id) items.where(author_id: current_user.id)
when 'all' then when 'assigned-to-me'
items
when 'assigned-to-me' then
items.where(assignee_id: current_user.id) items.where(assignee_id: current_user.id)
else else
raise 'You must specify default scope' items
end end
end end
...@@ -198,8 +197,7 @@ class IssuableFinder ...@@ -198,8 +197,7 @@ class IssuableFinder
end end
def by_group(items) def by_group(items)
items = items.of_group(group) if group # Selection by group is already covered by `by_project` and `projects`
items items
end end
......
...@@ -19,4 +19,10 @@ class IssuesFinder < IssuableFinder ...@@ -19,4 +19,10 @@ class IssuesFinder < IssuableFinder
def klass def klass
Issue Issue
end end
private
def init_collection
Issue.visible_to_user(current_user)
end
end end
class JoinedGroupsFinder < UnionFinder
def initialize(user)
@user = user
end
# Finds the groups of the source user, optionally limited to those visible to
# the current user.
def execute(current_user = nil)
segments = all_groups(current_user)
find_union(segments, Group).order_id_desc
end
private
def all_groups(current_user)
groups = []
groups << @user.authorized_groups.visible_to_user(current_user) if current_user
groups << @user.authorized_groups.public_to_user(current_user)
groups
end
end
class PersonalProjectsFinder class PersonalProjectsFinder < UnionFinder
def initialize(user) def initialize(user)
@user = user @user = user
end end
...@@ -11,31 +11,19 @@ class PersonalProjectsFinder ...@@ -11,31 +11,19 @@ class PersonalProjectsFinder
# #
# Returns an ActiveRecord::Relation. # Returns an ActiveRecord::Relation.
def execute(current_user = nil) def execute(current_user = nil)
if current_user segments = all_projects(current_user)
relation = projects_visible_to_user(current_user)
else
relation = public_projects
end
relation.includes(:namespace).order_id_desc find_union(segments, Project).includes(:namespace).order_id_desc
end end
private private
def projects_visible_to_user(current_user) def all_projects(current_user)
authorized = @user.personal_projects.visible_to_user(current_user) projects = []
union = Gitlab::SQL::Union.
new([authorized.select(:id), public_and_internal_projects.select(:id)])
Project.where("projects.id IN (#{union.to_sql})") projects << @user.personal_projects.visible_to_user(current_user) if current_user
end projects << @user.personal_projects.public_to_user(current_user)
def public_projects
@user.personal_projects.public_only
end
def public_and_internal_projects projects
@user.personal_projects.public_and_internal_only
end end
end end
class ProjectsFinder class ProjectsFinder < UnionFinder
# Returns all projects, optionally including group projects a user has access
# to.
#
# ## Examples
#
# Retrieving all public projects:
#
# ProjectsFinder.new.execute
#
# Retrieving all public/internal projects and those the given user has access
# to:
#
# ProjectsFinder.new.execute(some_user)
#
# Retrieving all public/internal projects as well as the group's projects the
# user has access to:
#
# ProjectsFinder.new.execute(some_user, group: some_group)
#
# Returns an ActiveRecord::Relation.
def execute(current_user = nil, options = {}) def execute(current_user = nil, options = {})
group = options[:group]
if group
segments = group_projects(current_user, group)
else
segments = all_projects(current_user) segments = all_projects(current_user)
end
if segments.length > 1 find_union(segments, Project)
union = Gitlab::SQL::Union.new(segments.map { |s| s.select(:id) })
Project.where("projects.id IN (#{union.to_sql})")
else
segments.first
end
end end
private private
def group_projects(current_user, group)
return [group.projects.public_only] unless current_user
user_group_projects = [
group_projects_for_user(current_user, group),
group.shared_projects.visible_to_user(current_user)
]
if current_user.external?
user_group_projects << group.projects.public_only
else
user_group_projects << group.projects.public_and_internal_only
end
end
def all_projects(current_user) def all_projects(current_user)
return [public_projects] unless current_user projects = []
if current_user.external? projects << current_user.authorized_projects if current_user
[current_user.authorized_projects, public_projects] projects << Project.unscoped.public_to_user(current_user)
else
[current_user.authorized_projects, public_and_internal_projects]
end
end
def group_projects_for_user(current_user, group)
if group.users.include?(current_user)
group.projects
else
group.projects.visible_to_user(current_user)
end
end
def public_projects
Project.unscoped.public_only
end
def public_and_internal_projects projects
Project.unscoped.public_and_internal_only
end end
end end
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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