Commit b48b0704 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'master' into drop-satellites

Signed-off-by: default avatarDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
parents c17f5d06 326b827c
Please view this file on the master branch, on stable branches it's out of date.
v 7.13.0 (unreleased)
v 7.14.0 (unreleased)
- Fix multi-line syntax highlighting (Stan Hu)
- Fix network graph when branch name has single quotes (Stan Hu)
- Upgrade gitlab_git to version 7.2.6 to fix Error 500 when creating network graphs (Stan Hu)
- Add support for Unicode filenames in relative links (Hiroyuki Sato)
- Fix URL used for refreshing notes if relative_url is present (Bartłomiej Święcki)
- Fix commit data retrieval when branch name has single quotes (Stan Hu)
- Check that project was actually created rather than just validated in import:repos task (Stan Hu)
- Fix full screen mode for snippet comments (Daniel Gerhardt)
- Fix 404 error in files view after deleting the last file in a repository (Stan Hu)
- Fix the "Reload with full diff" URL button (Stan Hu)
- Fix label read access for unauthenticated users (Daniel Gerhardt)
- Fix access to disabled features for unauthenticated users (Daniel Gerhardt)
- Fix OAuth provider bug where GitLab would not go return to the redirect_uri after sign-in (Stan Hu)
- Fix file upload dialog for comment editing (Daniel Gerhardt)
- Set OmniAuth full_host parameter to ensure redirect URIs are correct (Stan Hu)
- Expire Rails cache entries after two weeks to prevent endless Redis growth
- Add support for destroying project milestones (Stan Hu)
- Add fetch command to the MR page.
- Allow custom backup archive permissions
- Add fetch command to the MR page
- Add project star and fork count, group avatar URL and user/group web URL attributes to API
- Fix bug causing Bitbucket importer to crash when OAuth application had been removed.
- Add fetch command to the MR page.
- Add ability to manage user email addresses via the API.
- Show buttons to add license, changelog and contribution guide if they're missing.
- Disabled autocapitalize and autocorrect on login field (Daryl Chan)
- Mention group and project name in creation, update and deletion notices (Achilleas Pipinellis)
- Remove redis-store TTL monkey patch
v 7.13.2
- Fix randomly failed spec
- Create project services on Project creation
- Add admin_merge_request ability to Developer level and up
- Fix Error 500 when browsing projects with no HEAD (Stan Hu)
- Fix labels / assignee / milestone for the merge requests when issues are disabled
- Show the first tab automatically on MergeRequests#new
- Add rake task 'gitlab:update_commit_count' (Daniel Gerhardt)
- Fix Gmail Actions
v 7.13.1
- Fix: Label modifications are not reflected in existing notes and in the issue list
- Fix: Label not shown in the Issue list, although it's set through web interface
- Fix: Group/project references are linked incorrectly
- Improve documentation
- Fix of migration: Check if session_expire_delay column exists before adding the column
- Fix: ActionView::Template::Error
- Fix: "Create Merge Request" isn't always shown in event for newly pushed branch
- Fix bug causing "Remove source-branch" option not to work for merge requests from the same project.
- Render Note field hints consistently for "new" and "edit" forms
v 7.13.0
- Remove repository graph log to fix slow cache updates after push event (Stan Hu)
- Return comments in created order in merge request API (Stan Hu)
- Only enable HSTS header for HTTPS and port 443 (Stan Hu)
- Fix user autocomplete for unauthenticated users accessing public projects (Stan Hu)
- Fix redirection to home page URL for unauthorized users (Daniel Gerhardt)
......@@ -46,12 +99,15 @@ v 7.13.0 (unreleased)
- Faster code search in repository and wiki. Fixes search page timeout for big repositories
- Allow administrators to disable 2FA for a specific user
- Add error message for SSH key linebreaks
- Store commits count in database (will populate with valid values only after first push)
- Rebuild cache after push to repository in background job
v 7.12.2
- Correctly show anonymous authorized applications under Profile > Applications.
- Faster automerge check and merge itself when source and target branches are in same repository
- Audit log for user authentication
- Fix transferring of project to another group using the API.
- Allow custom label to be set for authentication providers.
v 7.12.1
- Fix error when deleting a user who has projects (Stan Hu)
......@@ -121,6 +177,7 @@ v 7.12.0
- Improve group removing logic
- Trigger create-hooks on backup restore task
- Add option to automatically link omniauth and LDAP identities
- Allow special character in users bio. I.e.: I <3 GitLab
v 7.11.4
- Fix missing bullets when creating lists
......@@ -139,9 +196,6 @@ v 7.11.1
v 7.11.0
- Fall back to Plaintext when Syntaxhighlighting doesn't work. Fixes some buggy lexers (Hannes Rosenögger)
- Get editing comments to work in Chrome 43 again.
- Allow special character in users bio. I.e.: I <3 GitLab
v 7.11.0
- Fix broken view when viewing history of a file that includes a path that used to be another file (Stan Hu)
- Don't show duplicate deploy keys
- Fix commit time being displayed in the wrong timezone in some cases (Hannes Rosenögger)
......
......@@ -38,7 +38,7 @@ gem "browser", '~> 0.8.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
gem "gitlab_git", '~> 7.2.5'
gem "gitlab_git", '~> 7.2.6'
# Ruby/Rack Git Smart-HTTP Server Handler
# GitLab fork with a lot of changes (improved thread-safety, better memory usage etc)
......@@ -46,7 +46,7 @@ gem "gitlab_git", '~> 7.2.5'
gem 'gitlab-grack', '~> 2.0.2', require: 'grack'
# 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
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
......@@ -54,9 +54,9 @@ gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
gem 'gollum-lib', '~> 4.0.2'
# Language detection
# GitLab fork of linguist does not require pygments/python dependency.
# New version of original gem also dropped pygments support but it has strict
# dependency to unstable rugged version. We have internal issue for replacing
# GitLab fork of linguist does not require pygments/python dependency.
# New version of original gem also dropped pygments support but it has strict
# dependency to unstable rugged version. We have internal issue for replacing
# fork with original gem when we meet on same rugged version - https://dev.gitlab.org/gitlab/gitlabhq/issues/2052.
gem "gitlab-linguist", "~> 3.0.1", require: "linguist"
......@@ -227,7 +227,7 @@ end
group :development, :test do
gem 'awesome_print'
gem 'byebug'
gem 'byebug', platform: :mri
gem 'fuubar', '~> 2.0.0'
gem 'pry-rails'
......@@ -272,4 +272,3 @@ end
gem "newrelic_rpm"
gem 'octokit', '3.7.0'
gem "rugments", "~> 1.0.0.beta8"
......@@ -271,7 +271,7 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.1.0)
gemojione (~> 2.0)
gitlab_git (7.2.5)
gitlab_git (7.2.6)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
......@@ -288,7 +288,7 @@ GEM
github-markup (~> 1.3.1)
gollum-grit_adapter (~> 0.1, >= 0.1.1)
nokogiri (~> 1.6.4)
rouge (~> 1.7.4)
rouge (~> 1.9)
sanitize (~> 2.1.0)
stringex (~> 2.5.1)
gon (5.0.1)
......@@ -508,7 +508,7 @@ GEM
rdoc (3.12.2)
json (~> 1.4)
redcarpet (3.3.2)
redis (3.1.0)
redis (3.2.1)
redis-actionpack (4.0.0)
actionpack (~> 4)
redis-rack (~> 1.5.0)
......@@ -525,7 +525,7 @@ GEM
redis-actionpack (~> 4)
redis-activesupport (~> 4)
redis-store (~> 1.1.0)
redis-store (1.1.4)
redis-store (1.1.6)
redis (>= 2.2)
request_store (1.0.5)
rerun (0.10.0)
......@@ -536,7 +536,7 @@ GEM
netrc (~> 0.7)
rinku (1.7.3)
rotp (1.6.1)
rouge (1.7.7)
rouge (1.9.1)
rqrcode (0.4.2)
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
......@@ -579,7 +579,6 @@ GEM
rubyntlm (0.5.0)
rubypants (0.2.0)
rugged (0.22.2)
rugments (1.0.0.beta8)
safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
......@@ -784,7 +783,7 @@ DEPENDENCIES
gitlab-grack (~> 2.0.2)
gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.1)
gitlab_git (~> 7.2.5)
gitlab_git (~> 7.2.6)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.2.1)
gollum-lib (~> 4.0.2)
......@@ -836,7 +835,6 @@ DEPENDENCIES
rqrcode-rails3
rspec-rails (~> 3.3.0)
rubocop (= 0.28.0)
rugments (~> 1.0.0.beta8)
sanitize (~> 2.0)
sass-rails (~> 4.0.5)
sdoc
......
# GitLab
[![build status](https://ci.gitlab.com/projects/1/status.png?ref=master)](https://ci.gitlab.com/projects/1?ref=master)
[![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq)
[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
[![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
## Canonical source
The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible.
# ![logo](https://about.gitlab.com/images/gitlab_logo.png) GitLab
## Open source software to collaborate on code
To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
......@@ -17,21 +22,12 @@ To see how GitLab looks please see the [features page on our website](https://ab
## Editions
There are two editions of GitLab.
*GitLab [Community Edition](https://about.gitlab.com/features/) (CE)* is available without any costs under an MIT license.
*GitLab Enterprise Edition (EE)* includes [extra features](https://about.gitlab.com/features/#compare) that are most useful for organizations with more than 100 users.
To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/).
## Code status
- [![build status](https://ci.gitlab.com/projects/1/status.png?ref=master)](https://ci.gitlab.com/projects/1?ref=master) on ci.gitlab.com (master branch)
There are two editions of GitLab:
- [![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq)
- GitLab Community Edition (CE) is available freely under the MIT Expat license.
- GitLab Enterprise Edition (EE) includes [extra features](https://about.gitlab.com/features/#compare) that are more useful for organizations with more than 100 users. To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/).
- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
Included with the GitLab Omnibus Packages is [GitLab CI](https://about.gitlab.com/gitlab-ci/) that can easily build, test and deploy code.
## Website
......@@ -46,24 +42,40 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a
## Requirements
GitLab requires the following software:
- Ubuntu/Debian/CentOS/RHEL
- Ruby (MRI) 2.0 or 2.1
- Git 1.7.10+
- Redis 2.0+
- MySQL or PostgreSQL
Please see the [requirements documentation](doc/install/requirements.md) for system requirements and more information about the supported operating systems.
## Installation
The recommended way to install GitLab is using the provided [Omnibus packages](https://about.gitlab.com/downloads/). Compared to an installation from source, this is faster and less error prone. Just select your operating system, download the respective package (Debian or RPM) and install it using the system's package manager.
The recommended way to install GitLab is with the [Omnibus packages](https://about.gitlab.com/downloads/) on our package server.
Compared to an installation from source, this is faster and less error prone.
Just select your operating system, download the respective package (Debian or RPM) and install it using the system's package manager.
There are various other options to install GitLab, please refer to the [installation page on the GitLab website](https://about.gitlab.com/installation/) for more information.
You can access a new installation with the login **`root`** and password **`5iveL!fe`**, after login you are required to set a unique password.
## Install a development environment
To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
If you do not use the GitLab Development Kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone.
One small thing you also have to do when installing it yourself is to copy the example development unicorn configuration file:
cp config/unicorn.rb.example.development config/unicorn.rb
Instructions on how to start GitLab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development).
## Software stack
GitLab is a Ruby on Rails application that runs on the following software:
- Ubuntu/Debian/CentOS/RHEL
- Ruby (MRI) 2.0 or 2.1
- Git 1.7.10+
- Redis 2.0+
- MySQL or PostgreSQL
For more information please see the [architecture documentation](http://doc.gitlab.com/ce/development/architecture.html).
## Third-party applications
There are a lot of [third-party applications integrating with GitLab](https://about.gitlab.com/applications/). These include GUI Git clients, mobile applications and API wrappers for various languages.
......@@ -76,16 +88,6 @@ Since 2011 a minor or major version of GitLab is released on the 22nd of every m
For upgrading information please see our [update page](https://about.gitlab.com/update/).
## Install a development environment
To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit).
If you do not use the GitLab Development Kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone.
One small thing you also have to do when installing it yourself is to copy the example development unicorn configuration file:
cp config/unicorn.rb.example.development config/unicorn.rb
Instructions on how to start GitLab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development).
## Documentation
All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/).
......
......@@ -164,9 +164,10 @@ $ ->
$('.account-box').hover -> $(@).toggleClass('hover')
# Commit show suppressed diff
$(".diff-content").on "click", ".supp_diff_link", ->
$(@).next('table').show()
$(@).remove()
$(document).on 'click', '.diff-content .js-show-suppressed-diff', ->
$container = $(@).parent()
$container.next('table').show()
$container.remove()
$('.navbar-toggle').on 'click', ->
$('.header-content .title').toggle()
......
......@@ -31,6 +31,10 @@ class @Diff
bottom: unfoldBottom
offset: offset
unfold: unfold
# indent is used to compensate for single space indent to fit
# '+' and '-' prepended to diff lines,
# see https://gitlab.com/gitlab-org/gitlab-ce/issues/707
indent: 1
$.get(link, params, (response) =>
target.parent().replaceWith(response)
......
......@@ -128,7 +128,10 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
new ZenMode()
new DropzoneInput($('.wiki-form'))
when 'snippets', 'labels', 'graphs'
when 'snippets'
shortcut_handler = new ShortcutsNavigation()
new ZenMode() if path[2] == 'show'
when 'labels', 'graphs'
shortcut_handler = new ShortcutsNavigation()
when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches'
shortcut_handler = new ShortcutsNavigation()
......
......@@ -70,7 +70,7 @@ class @LineHighlighter
@clearHighlight()
lineNumber = $(event.target).data('line-number')
lineNumber = $(event.target).closest('a').data('line-number')
current = @hashToRange(@_hash)
unless current[0] && event.shiftKey
......
......@@ -15,9 +15,7 @@ class @MergeRequest
this.$('.show-all-commits').on 'click', =>
this.showAllCommits()
# `MergeRequests#new` has no tab-persisting or lazy-loading behavior
unless @opts.action == 'new'
new MergeRequestTabs(@opts)
@initTabs()
# Prevent duplicate event bindings
@disableTaskList()
......@@ -29,6 +27,14 @@ class @MergeRequest
$: (selector) ->
this.$el.find(selector)
initTabs: ->
if @opts.action != 'new'
# `MergeRequests#new` has no tab-persisting or lazy-loading behavior
new MergeRequestTabs(@opts)
else
# Show the first tab (Commits)
$('.merge-request-tabs a[data-toggle="tab"]:first').tab('show')
showAllCommits: ->
this.$('.first-commits').remove()
this.$('.all-commits').removeClass 'hide'
......
......@@ -49,12 +49,6 @@ class @MergeRequestTabs
# Store the `location` object, allowing for easier stubbing in tests
@_location = location
switch @opts.action
when 'commits'
@commitsLoaded = true
when 'diffs'
@diffsLoaded = true
@bindEvents()
@activateTab(@opts.action)
......@@ -102,7 +96,7 @@ class @MergeRequestTabs
action = 'notes' if action == 'show'
# Remove a trailing '/commits' or '/diffs'
new_state = @_location.pathname.replace(/\/(commits|diffs)\/?$/, '')
new_state = @_location.pathname.replace(/\/(commits|diffs)(\.html)?\/?$/, '')
# Append the new action if we're on a tab other than 'notes'
unless action == 'notes'
......@@ -133,7 +127,7 @@ class @MergeRequestTabs
return if @diffsLoaded
@_get
url: "#{source}.json"
url: "#{source}.json" + @_location.search
success: (data) =>
document.getElementById('diffs').innerHTML = data.html
@diffsLoaded = true
......
......@@ -10,7 +10,6 @@ class @Notes
constructor: (notes_url, note_ids, last_fetched_at, view) ->
@notes_url = notes_url
@notes_url = gon.relative_url_root + @notes_url if gon.relative_url_root?
@note_ids = note_ids
@last_fetched_at = last_fetched_at
@view = view
......@@ -298,7 +297,7 @@ class @Notes
note.find(".note-header").hide()
base_form = note.find(".note-edit-form")
form = base_form.clone().insertAfter(base_form)
form.addClass('current-note-edit-form')
form.addClass('current-note-edit-form gfm-form')
form.find('.div-dropzone').remove()
# Show the attachment delete link
......
......@@ -70,7 +70,7 @@
font-family: $monospace_font;
white-space: pre;
word-wrap: normal;
padding: 0;
padding: 1px 2px;
}
kbd {
......
......@@ -184,7 +184,7 @@ li.note {
}
}
.supp_diff_link,
.show-suppressed-diff,
.show-all-commits {
cursor: pointer;
}
......
......@@ -38,6 +38,10 @@ code {
}
}
a > code {
color: $link-color;
}
/**
* Wiki typography
*
......
......@@ -65,6 +65,17 @@
color: #777;
}
.suppressed-container {
padding: ($padding-base-vertical + 5px) $padding-base-horizontal;
text-align: center;
// "Changes suppressed. Click to show." link
.show-suppressed-diff {
font-size: 110%;
font-weight: bold;
}
}
table {
width: 100%;
font-family: $monospace_font;
......
......@@ -37,7 +37,7 @@ ul.notes {
font-size: 13px;
a {
@extend .cgray;
@extend .cgray;
&:hover {
text-decoration: underline;
......@@ -105,6 +105,8 @@ ul.notes {
}
hr {
// Darken 'whitesmoke' a bit to make it more visible in note bodies
border-color: darken(#F5F5F5, 8%);
margin: 10px 0;
}
}
......
......@@ -297,6 +297,15 @@ table.table.protected-branches-list tr.no-border {
ul.nav-pills { display:inline-block; }
li { display:inline; }
a { float:left; }
li.missing a {
color: #bbb;
border: 1px dashed #ccc;
&:hover {
background-color: #FAFAFA;
}
}
}
pre.light-well {
......
......@@ -299,14 +299,14 @@ class ApplicationController < ActionController::Base
end
def github_import_enabled?
OauthHelper.enabled_oauth_providers.include?(:github)
Gitlab::OAuth::Provider.enabled?(:github)
end
def gitlab_import_enabled?
OauthHelper.enabled_oauth_providers.include?(:gitlab)
Gitlab::OAuth::Provider.enabled?(:gitlab)
end
def bitbucket_import_enabled?
OauthHelper.enabled_oauth_providers.include?(:bitbucket) && Gitlab::BitbucketImport.public_key.present?
Gitlab::OAuth::Provider.enabled?(:bitbucket) && Gitlab::BitbucketImport.public_key.present?
end
end
......@@ -18,4 +18,10 @@ class Groups::ApplicationController < ApplicationController
return render_404
end
end
def authorize_admin_group_member!
unless can?(current_user, :admin_group_member, group)
return render_403
end
end
end
......@@ -5,6 +5,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
# Authorize
before_action :authorize_read_group!
before_action :authorize_admin_group!, except: [:index, :leave]
before_action :authorize_admin_group_member!, only: [:create, :resend_invite]
def index
@project = @group.projects.find(params[:project_id]) if params[:project_id]
......@@ -28,6 +29,9 @@ class Groups::GroupMembersController < Groups::ApplicationController
def update
@member = @group.group_members.find(params[:id])
return render_403 unless can?(current_user, :update_group_member, @member)
@member.update_attributes(member_params)
end
......
......@@ -24,7 +24,7 @@ class GroupsController < Groups::ApplicationController
if @group.save
@group.add_owner(current_user)
redirect_to @group, notice: 'Group was successfully created.'
redirect_to @group, notice: "Group '#{@group.name}' was successfully created."
else
render action: "new"
end
......@@ -75,7 +75,7 @@ class GroupsController < Groups::ApplicationController
def update
if @group.update_attributes(group_params)
redirect_to edit_group_path(@group), notice: 'Group was successfully updated.'
redirect_to edit_group_path(@group), notice: "Group '#{@group.name}' was successfully updated."
else
render action: "edit"
end
......@@ -84,7 +84,7 @@ class GroupsController < Groups::ApplicationController
def destroy
DestroyGroupService.new(@group, current_user).execute
redirect_to root_path, notice: 'Group was removed.'
redirect_to root_path, alert: "Group '#{@group.name} was deleted."
end
protected
......
......@@ -3,6 +3,7 @@ class Import::BitbucketController < Import::BaseController
before_action :bitbucket_auth, except: :callback
rescue_from OAuth::Error, with: :bitbucket_unauthorized
rescue_from Gitlab::BitbucketImport::Client::Unauthorized, with: :bitbucket_unauthorized
def callback
request_token = session.delete(:oauth_request_token)
......
......@@ -72,10 +72,11 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
end
end
rescue Gitlab::OAuth::SignupDisabledError => e
message = "Signing in using your #{oauth['provider']} account without a pre-existing GitLab account is not allowed."
label = Gitlab::OAuth::Provider.label_for(oauth['provider'])
message = "Signing in using your #{label} account without a pre-existing GitLab account is not allowed."
if current_application_settings.signup_enabled?
message << " Create a GitLab account first, and then connect it to your #{oauth['provider']} account."
message << " Create a GitLab account first, and then connect it to your #{label} account."
end
flash[:notice] = message
......
......@@ -64,7 +64,12 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def destroy
return access_denied! unless can?(current_user, :admin_milestone, @milestone)
return access_denied! unless can?(current_user, :admin_milestone, @project)
update_params = { milestone: nil }
@milestone.issues.each do |issue|
Issues::UpdateService.new(@project, current_user, update_params).execute(issue)
end
@milestone.destroy
......
......@@ -7,6 +7,10 @@ class Projects::NetworkController < Projects::ApplicationController
before_action :authorize_download_code!
def show
@url = namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))
@commit_url = namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")
respond_to do |format|
format.html
......
class Projects::RefsController < Projects::ApplicationController
include ExtractsPath
include TreeHelper
before_action :require_non_empty_project
before_action :assign_ref_vars
......@@ -60,6 +61,11 @@ class Projects::RefsController < Projects::ApplicationController
}
end
if @logs.present?
@log_url = namespace_project_tree_url(@project.namespace, @project, tree_join(@ref, @path || '/'))
@more_log_url = logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: (@offset + @limit))
end
respond_to do |format|
format.html { render_404 }
format.js
......
......@@ -7,13 +7,15 @@ class Projects::TreeController < Projects::ApplicationController
before_action :authorize_download_code!
def show
return not_found! unless @repository.commit(@ref)
if tree.entries.empty?
if @repository.blob_at(@commit.id, @path)
redirect_to(
namespace_project_blob_path(@project.namespace, @project,
File.join(@ref, @path))
) and return
else
elsif @path.present?
return not_found!
end
end
......
class ProjectsController < ApplicationController
prepend_before_filter :render_go_import, only: [:show]
skip_before_action :authenticate_user!, only: [:show]
skip_before_action :authenticate_user!, only: [:show, :activity]
before_action :project, except: [:new, :create]
before_action :repository, except: [:new, :create]
......@@ -24,7 +24,7 @@ class ProjectsController < ApplicationController
if @project.saved?
redirect_to(
project_path(@project),
notice: 'Project was successfully created.'
notice: "Project '#{@project.name}' was successfully created."
)
else
render 'new'
......@@ -36,11 +36,11 @@ class ProjectsController < ApplicationController
respond_to do |format|
if status
flash[:notice] = 'Project was successfully updated.'
flash[:notice] = "Project '#{@project.name}' was successfully updated."
format.html do
redirect_to(
edit_project_path(@project),
notice: 'Project was successfully updated.'
notice: "Project '#{@project.name}' was successfully updated."
)
end
format.js
......@@ -100,7 +100,7 @@ class ProjectsController < ApplicationController
return access_denied! unless can?(current_user, :remove_project, @project)
::Projects::DestroyService.new(@project, current_user, {}).execute
flash[:alert] = 'Project deleted.'
flash[:alert] = "Project '#{@project.name}' was deleted."
if request.referer.include?('/admin')
redirect_to admin_namespaces_projects_path
......
......@@ -90,7 +90,7 @@ class SessionsController < Devise::SessionsController
# Prevent alert from popping up on the first page shown after authentication.
flash[:alert] = nil
redirect_to omniauth_authorize_path(:user, provider.to_sym)
redirect_to user_omniauth_authorize_path(provider.to_sym)
end
def valid_otp_attempt?(user)
......
module AuthHelper
PROVIDERS_WITH_ICONS = %w(twitter github gitlab bitbucket google_oauth2).freeze
FORM_BASED_PROVIDERS = [/\Aldap/, 'kerberos'].freeze
def ldap_enabled?
Gitlab.config.ldap.enabled
end
def provider_has_icon?(name)
PROVIDERS_WITH_ICONS.include?(name.to_s)
end
def auth_providers
Gitlab::OAuth::Provider.providers
end
def label_for_provider(name)
Gitlab::OAuth::Provider.label_for(name)
end
def form_based_provider?(name)
FORM_BASED_PROVIDERS.any? { |pattern| pattern === name.to_s }
end
def form_based_providers
auth_providers.select { |provider| form_based_provider?(provider) }
end
def button_based_providers
auth_providers.reject { |provider| form_based_provider?(provider) }
end
def provider_image_tag(provider, size = 64)
label = label_for_provider(provider)
if provider_has_icon?(provider)
file_name = "#{provider.to_s.split('_').first}_#{size}.png"
image_tag(image_path("auth_buttons/#{file_name}"), alt: label, title: "Sign in with #{label}")
else
label
end
end
def auth_active?(provider)
current_user.identities.exists?(provider: provider.to_s)
end
extend self
end
module BlobHelper
def highlight(blob_name, blob_content, nowrap: false, continue: false)
@formatter ||= Rugments::Formatters::HTML.new(
@formatter ||= Rouge::Formatters::HTMLGitlab.new(
nowrap: nowrap,
cssclass: 'code highlight',
lineanchors: true,
......@@ -8,11 +8,11 @@ module BlobHelper
)
begin
@lexer ||= Rugments::Lexer.guess(filename: blob_name, source: blob_content).new
@lexer ||= Rouge::Lexer.guess(filename: blob_name, source: blob_content).new
result = @formatter.format(@lexer.lex(blob_content, continue: continue)).html_safe
rescue
lexer = Rugments::Lexers::PlainText
result = @formatter.format(lexer.lex(blob_content)).html_safe
@lexer = Rouge::Lexers::PlainText
result = @formatter.format(@lexer.lex(blob_content)).html_safe
end
result
......
......@@ -31,8 +31,8 @@ module EmailsHelper
end
def color_email_diff(diffcontent)
formatter = Rugments::Formatters::HTML.new(cssclass: "highlight", inline_theme: :github)
lexer = Rugments::Lexers::Diff.new
formatter = Rouge::Formatters::HTML.new(css_class: 'highlight', inline_theme: 'github')
lexer = Rouge::Lexers::Diff
raw formatter.format(lexer.lex(diffcontent))
end
......
module OauthHelper
def ldap_enabled?
Gitlab.config.ldap.enabled
end
def default_providers
[:twitter, :github, :gitlab, :bitbucket, :google_oauth2, :ldap]
end
def enabled_oauth_providers
Devise.omniauth_providers
end
def enabled_social_providers
enabled_oauth_providers.select do |name|
[:saml, :twitter, :gitlab, :github, :bitbucket, :google_oauth2].include?(name.to_sym)
end
end
def additional_providers
enabled_oauth_providers.reject{|provider| provider.to_s.starts_with?('ldap')}
end
def oauth_image_tag(provider, size = 64)
file_name = "#{provider.to_s.split('_').first}_#{size}.png"
image_tag(image_path("authbuttons/#{file_name}"), alt: "Sign in with #{provider.to_s.titleize}")
end
def oauth_active?(provider)
current_user.identities.exists?(provider: provider.to_s)
end
extend self
end
module ProfileHelper
def show_profile_username_tab?
current_user.can_change_username?
end
def show_profile_social_tab?
enabled_social_providers.any?
end
def show_profile_remove_tab?
signup_enabled?
end
end
......@@ -131,8 +131,12 @@ module ProjectsHelper
nav_tabs << :snippets
end
if can?(current_user, :read_label, project)
nav_tabs << :labels
end
if can?(current_user, :read_milestone, project)
nav_tabs << [:milestones, :labels]
nav_tabs << :milestones
end
nav_tabs.flatten
......@@ -180,7 +184,43 @@ module ProjectsHelper
end
end
def contribution_guide_url(project)
def add_contribution_guide_path(project)
if project && !project.repository.contribution_guide
namespace_project_new_blob_path(
project.namespace,
project,
project.default_branch,
file_name: "CONTRIBUTING.md",
commit_message: "Add contribution guide"
)
end
end
def add_changelog_path(project)
if project && !project.repository.changelog
namespace_project_new_blob_path(
project.namespace,
project,
project.default_branch,
file_name: "CHANGELOG",
commit_message: "Add changelog"
)
end
end
def add_license_path(project)
if project && !project.repository.license
namespace_project_new_blob_path(
project.namespace,
project,
project.default_branch,
file_name: "LICENSE",
commit_message: "Add license"
)
end
end
def contribution_guide_path(project)
if project && contribution_guide = project.repository.contribution_guide
namespace_project_blob_path(
project.namespace,
......@@ -191,7 +231,7 @@ module ProjectsHelper
end
end
def changelog_url(project)
def changelog_path(project)
if project && changelog = project.repository.changelog
namespace_project_blob_path(
project.namespace,
......@@ -202,7 +242,7 @@ module ProjectsHelper
end
end
def license_url(project)
def license_path(project)
if project && license = project.repository.license
namespace_project_blob_path(
project.namespace,
......@@ -213,7 +253,7 @@ module ProjectsHelper
end
end
def version_url(project)
def version_path(project)
if project && version = project.repository.version
namespace_project_blob_path(
project.namespace,
......@@ -274,6 +314,21 @@ module ProjectsHelper
end
def readme_cache_key
[@project.id, @project.commit.sha, "readme"].join('-')
sha = @project.commit.try(:sha) || 'nil'
[@project.id, sha, "readme"].join('-')
end
def round_commit_count(project)
count = project.commit_count
if count > 10000
'10000+'
elsif count > 5000
'5000+'
elsif count > 1000
'1000+'
else
count
end
end
end
......@@ -31,10 +31,11 @@ class Ability
end
if project && project.public?
[
rules = [
:read_project,
:read_wiki,
:read_issue,
:read_label,
:read_milestone,
:read_project_snippet,
:read_project_member,
......@@ -42,6 +43,8 @@ class Ability
:read_note,
:download_code
]
rules - project_disabled_features_rules(project)
else
group = if subject.kind_of?(Group)
subject
......@@ -102,28 +105,7 @@ class Ability
rules -= project_archived_rules
end
unless project.issues_enabled
rules -= named_abilities('issue')
end
unless project.merge_requests_enabled
rules -= named_abilities('merge_request')
end
unless project.issues_enabled or project.merge_requests_enabled
rules -= named_abilities('label')
rules -= named_abilities('milestone')
end
unless project.snippets_enabled
rules -= named_abilities('project_snippet')
end
unless project.wiki_enabled
rules -= named_abilities('wiki')
end
rules
rules - project_disabled_features_rules(project)
end
end
......@@ -158,12 +140,13 @@ class Ability
:create_project_snippet,
:update_issue,
:admin_issue,
:admin_label,
:admin_label
]
end
def project_dev_rules
project_report_rules + [
:admin_merge_request,
:create_merge_request,
:create_wiki,
:push_code
......@@ -205,6 +188,33 @@ class Ability
]
end
def project_disabled_features_rules(project)
rules = []
unless project.issues_enabled
rules += named_abilities('issue')
end
unless project.merge_requests_enabled
rules += named_abilities('merge_request')
end
unless project.issues_enabled or project.merge_requests_enabled
rules += named_abilities('label')
rules += named_abilities('milestone')
end
unless project.snippets_enabled
rules += named_abilities('project_snippet')
end
unless project.wiki_enabled
rules += named_abilities('wiki')
end
rules
end
def group_abilities(user, group)
rules = []
......@@ -223,7 +233,8 @@ class Ability
if group.has_owner?(user) || user.admin?
rules.push(*[
:admin_group,
:admin_namespace
:admin_namespace,
:admin_group_member
])
end
......@@ -285,7 +296,7 @@ class Ability
rules = []
target_user = subject.user
group = subject.group
can_manage = group_abilities(user, group).include?(:admin_group)
can_manage = group_abilities(user, group).include?(:admin_group_member)
if can_manage && (user != target_user)
rules << :update_group_member
......
......@@ -14,13 +14,14 @@
# default_branch_protection :integer default(2)
# twitter_sharing_enabled :boolean default(TRUE)
# restricted_visibility_levels :text
# version_check_enabled :boolean default(TRUE)
# max_attachment_size :integer default(10), not null
# session_expire_delay :integer default(10080), not null
# default_project_visibility :integer
# default_snippet_visibility :integer
# restricted_signup_domains :text
# user_oauth_applications :bool default(TRUE)
# user_oauth_applications :boolean default(TRUE)
# after_sign_out_path :string(255)
# session_expire_delay :integer default(10080), not null
#
class ApplicationSetting < ActiveRecord::Base
......
# == Schema Information
#
# Table name: audit_events
#
# id :integer not null, primary key
# author_id :integer not null
# type :string(255) not null
# entity_id :integer not null
# entity_type :string(255) not null
# details :text
# created_at :datetime
# updated_at :datetime
#
class AuditEvent < ActiveRecord::Base
serialize :details, Hash
......
......@@ -159,6 +159,16 @@ module Issuable
end
end
# Convert this Issuable class name to a format usable by Ability definitions
#
# Examples:
#
# issuable.class # => MergeRequest
# issuable.to_ability_name # => "merge_request"
def to_ability_name
self.class.to_s.underscore
end
private
def filter_superceded_votes(votes, notes)
......
......@@ -56,6 +56,12 @@ class Group < Namespace
name
end
def avatar_url(size = nil)
if avatar.present?
[gitlab_config.url, avatar.url].join
end
end
def owners
@owners ||= group_members.owners.map(&:user)
end
......
......@@ -21,12 +21,13 @@
# import_url :string(255)
# visibility_level :integer default(0), not null
# archived :boolean default(FALSE), not null
# avatar :string(255)
# import_status :string(255)
# repository_size :float default(0.0)
# star_count :integer default(0), not null
# import_type :string(255)
# import_source :string(255)
# avatar :string(255)
# commit_count :integer default(0)
#
require 'carrierwave/orm/activerecord'
......@@ -36,7 +37,6 @@ class Project < ActiveRecord::Base
include Gitlab::ConfigHelper
include Gitlab::ShellAdapter
include Gitlab::VisibilityLevel
include Rails.application.routes.url_helpers
include Referable
include Sortable
......@@ -316,7 +316,7 @@ class Project < ActiveRecord::Base
end
def web_url
[gitlab_config.url, path_with_namespace].join('/')
Rails.application.routes.url_helpers.namespace_project_url(self.namespace, self)
end
def web_url_without_protocol
......@@ -433,7 +433,7 @@ class Project < ActiveRecord::Base
if avatar.present?
[gitlab_config.url, avatar.url].join
elsif avatar_in_git
[gitlab_config.url, namespace_project_avatar_path(namespace, self)].join
Rails.application.routes.url_helpers.namespace_project_avatar_url(namespace, self)
end
end
......@@ -563,7 +563,7 @@ class Project < ActiveRecord::Base
end
def http_url_to_repo
[gitlab_config.url, '/', path_with_namespace, '.git'].join('')
"#{web_url}.git"
end
# Check if current branch name is marked as protected in the system
......@@ -672,6 +672,10 @@ class Project < ActiveRecord::Base
update_attribute(:repository_size, repository.size)
end
def update_commit_count
update_attribute(:commit_count, repository.commit_count)
end
def forks_count
ForkedProjectLink.where(forked_from_project_id: self.id).count
end
......@@ -689,14 +693,14 @@ class Project < ActiveRecord::Base
if gitlab_shell.fork_repository(forked_from_project.path_with_namespace, self.namespace.path)
true
else
errors.add(:base, 'Failed to fork repository')
errors.add(:base, 'Failed to fork repository via gitlab-shell')
false
end
else
if gitlab_shell.add_repository(path_with_namespace)
true
else
errors.add(:base, 'Failed to create repository')
errors.add(:base, 'Failed to create repository via gitlab-shell')
false
end
end
......
......@@ -22,8 +22,12 @@ class GitlabCiService < CiService
API_PREFIX = "api/v1"
prop_accessor :project_url, :token
validates :project_url, presence: true, if: :activated?
validates :token, presence: true, if: :activated?
validates :project_url,
presence: true,
format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
validates :token,
presence: true,
format: { with: /\A([A-Za-z0-9]+)\z/ }, if: :activated?
after_save :compose_service_hook, if: :activated?
......
......@@ -94,18 +94,6 @@ class Repository
gitlab_shell.rm_tag(path_with_namespace, tag_name)
end
def round_commit_count
if commit_count > 10000
'10000+'
elsif commit_count > 5000
'5000+'
elsif commit_count > 1000
'1000+'
else
commit_count
end
end
def branch_names
cache.fetch(:branch_names) { raw_repository.branch_names }
end
......@@ -130,28 +118,29 @@ class Repository
cache.fetch(:size) { raw_repository.size }
end
def cache_keys
%i(size branch_names tag_names commit_count
readme version contribution_guide changelog license)
end
def build_cache
cache_keys.each do |key|
unless cache.exist?(key)
send(key)
end
end
end
def expire_cache
%i(size branch_names tag_names commit_count graph_log
readme version contribution_guide changelog license).each do |key|
cache_keys.each do |key|
cache.expire(key)
end
end
def graph_log
cache.fetch(:graph_log) do
commits = raw_repository.log(limit: 6000, skip_merges: true,
ref: root_ref)
commits.map do |rugged_commit|
commit = Gitlab::Git::Commit.new(rugged_commit)
{
author_name: commit.author_name,
author_email: commit.author_email,
additions: commit.stats.additions,
deletions: commit.stats.deletions,
}
end
def rebuild_cache
cache_keys.each do |key|
cache.expire(key)
send(key)
end
end
......@@ -463,8 +452,7 @@ class Repository
filename = nil
startline = 0
lines = result.lines
lines.each_with_index do |line, index|
result.each_line.each_with_index do |line, index|
if line =~ /^.*:.*:\d+:/
ref, filename, startline = line.split(':')
startline = startline.to_i - index
......@@ -472,11 +460,11 @@ class Repository
end
end
data = lines.map do |line|
line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
end
data = ""
data = data.join("")
result.each_line do |line|
data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
end
OpenStruct.new(
filename: filename,
......
# == Schema Information
#
# Table name: audit_events
#
# id :integer not null, primary key
# author_id :integer not null
# type :string(255) not null
# entity_id :integer not null
# entity_type :string(255) not null
# details :text
# created_at :datetime
# updated_at :datetime
#
class SecurityEvent < AuditEvent
end
......@@ -57,6 +57,7 @@
# otp_backup_codes :text
# public_email :string(255) default(""), not null
# dashboard :integer default(0)
# project_view :integer default(0)
#
require 'carrierwave/orm/activerecord'
......@@ -274,6 +275,10 @@ class User < ActiveRecord::Base
value: login.to_s.downcase).first
end
def find_by_username!(username)
find_by!('lower(username) = ?', username.downcase)
end
def by_username_or_id(name_or_id)
where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first
end
......
......@@ -19,7 +19,6 @@ class GitPushService
@project, @user = project, user
project.repository.expire_cache
project.update_repository_size
if push_remove_branch?(ref, newrev)
@push_commits = []
......@@ -59,6 +58,7 @@ class GitPushService
EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup, :push_hooks)
ProjectCacheWorker.perform_async(project.id)
end
protected
......
......@@ -2,15 +2,15 @@ class GitTagPushService
attr_accessor :project, :user, :push_data
def execute(project, user, oldrev, newrev, ref)
@project, @user = project, user
project.repository.expire_cache
@project, @user = project, user
@push_data = build_push_data(oldrev, newrev, ref)
EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :tag_push_hooks)
project.execute_services(@push_data.dup, :tag_push_hooks)
project.repository.expire_cache
ProjectCacheWorker.perform_async(project.id)
true
end
......
......@@ -27,8 +27,10 @@ class IssuableBaseService < BaseService
old_branch, new_branch)
end
def filter_params
unless can?(current_user, :admin_issue, project)
def filter_params(issuable_ability_name = :issue)
ability = :"admin_#{issuable_ability_name}"
unless can?(current_user, ability, project)
params.delete(:milestone_id)
params.delete(:label_ids)
params.delete(:assignee_id)
......
......@@ -10,6 +10,10 @@ module Issues
private
def filter_params
super(:issue)
end
def execute_hooks(issue, action = 'open')
issue_data = hook_data(issue, action)
issue.project.execute_hooks(issue_data, :issue_hooks)
......
......@@ -20,5 +20,11 @@ module MergeRequests
merge_request.project.execute_services(merge_data, :merge_request_hooks)
end
end
private
def filter_params
super(:merge_request)
end
end
end
......@@ -85,6 +85,8 @@ module Projects
@project.create_wiki if @project.wiki_enabled?
@project.build_missing_services
event_service.create_project(@project, current_user)
system_hook_service.execute_hooks_for(@project, :create)
......
......@@ -51,21 +51,22 @@
= paginate @projects, param_name: 'projects_page', theme: 'gitlab'
.col-md-6
.panel.panel-default
.panel-heading
Add user(s) to the group:
.panel-body.form-holder
%p.light
Read more about project permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
- if can?(current_user, :admin_group_member, @group)
.panel.panel-default
.panel-heading
Add user(s) to the group:
.panel-body.form-holder
%p.light
Read more about project permissions
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
= form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do
%div
= users_select_tag(:user_ids, multiple: true, email_user: true, scope: :all)
%div.prepend-top-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr
= button_tag 'Add users to group', class: "btn btn-create"
= form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do
%div
= users_select_tag(:user_ids, multiple: true, email_user: true, scope: :all)
%div.prepend-top-10
= select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2"
%hr
= button_tag 'Add users to group', class: "btn btn-create"
.panel.panel-default
.panel-heading
%h3.panel-title
......@@ -86,7 +87,8 @@
(invited)
%span.pull-right.light
= member.human_access
= link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-minus.fa-inverse
- if can?(current_user, :destroy_group_member, member)
= link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do
%i.fa.fa-minus.fa-inverse
.panel-footer
= paginate @members, param_name: 'members_page', theme: 'gitlab'
......@@ -8,7 +8,8 @@
.form-group
= f.label :provider, class: 'control-label'
.col-sm-10
= f.select :provider, Gitlab::OAuth::Provider.names, { allow_blank: false }, class: 'form-control'
- values = Gitlab::OAuth::Provider.providers.map { |name| ["#{Gitlab::OAuth::Provider.label_for(name)} (#{name})", name] }
= f.select :provider, values, { allow_blank: false }, class: 'form-control'
.form-group
= f.label :extern_uid, "Identifier", class: 'control-label'
.col-sm-10
......
%tr
%td
= identity.provider
= "#{Gitlab::OAuth::Provider.label_for(identity.provider)} (#{identity.provider})"
%td
= identity.extern_uid
%td
......
= form_for(resource, as: resource_name, url: session_path(resource_name)) do |f|
= f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus"
= f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off"
= f.password_field :password, class: "form-control bottom", placeholder: "Password"
- if devise_mapping.rememberable?
.remember-me.checkbox
......
......@@ -6,4 +6,4 @@
%label{for: "remember_me"}
= check_box_tag :remember_me, '1', false, id: 'remember_me'
%span Remember me
= button_tag "#{server['label']} Sign in", class: "btn-save btn"
= button_tag "Sign in", class: "btn-save btn"
%p
%span.light
Sign in with &nbsp;
- providers = additional_providers
- providers = button_based_providers
- providers.each do |provider|
%span.light
- if default_providers.include?(provider)
= link_to oauth_image_tag(provider), omniauth_authorize_path(resource_name, provider), method: :post, class: 'oauth-image-link'
- else
= link_to provider.to_s.titleize, omniauth_authorize_path(resource_name, provider), method: :post, class: "btn", "data-no-turbolink" => "true"
- has_icon = provider_has_icon?(provider)
= link_to provider_image_tag(provider), user_omniauth_authorize_path(provider), method: :post, class: (has_icon ? 'oauth-image-link' : 'btn'), "data-no-turbolink" => "true"
......@@ -6,7 +6,7 @@
.login-heading
%h3 Sign in
.login-body
- if ldap_enabled?
- if form_based_providers.any?
%ul.nav.nav-tabs
- @ldap_servers.each_with_index do |server, i|
%li{class: (:active if i.zero?)}
......
......@@ -8,11 +8,10 @@
= image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:''
= render "events/event/created_project", event: event
- else
= cache event do
= image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:''
- if event.push?
= render "events/event/push", event: event
- elsif event.commented?
= render "events/event/note", event: event
- else
= render "events/event/common", event: event
= image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:''
- if event.push?
= render "events/event/push", event: event
- elsif event.commented?
= render "events/event/note", event: event
- else
= render "events/event/common", event: event
......@@ -14,7 +14,7 @@
.repo-info
- unless project.empty_repo?
= link_to pluralize(project.repository.round_commit_count, 'commit'), namespace_project_commits_path(project.namespace, project, project.default_branch)
= link_to pluralize(round_commit_count(project), 'commit'), namespace_project_commits_path(project.namespace, project, project.default_branch)
&middot;
= link_to pluralize(project.repository.branch_names.count, 'branch'), namespace_project_branches_path(project.namespace, project)
&middot;
......
......@@ -24,7 +24,7 @@
= link_to member.created_by.name, user_path(member.created_by)
= time_ago_with_tooltip(member.created_at)
- if show_controls && can?(current_user, :admin_group, @group)
- if show_controls && can?(current_user, :admin_group_member, member)
= link_to resend_invite_group_group_member_path(@group, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do
Resend invite
......
......@@ -17,7 +17,7 @@
= search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input' }
= button_tag 'Search', class: 'btn'
- if current_user && current_user.can?(:admin_group, @group)
- if current_user && current_user.can?(:admin_group_member, @group)
.pull-right
= button_tag class: 'btn btn-new js-toggle-button', type: 'button' do
Add members
......
......@@ -33,7 +33,7 @@
= yield
%div.footer{style: "margin-top: 10px;"}
%p
\—
&mdash;
%br
- if @target_url
#{link_to "View it on GitLab", @target_url}
......
......@@ -59,22 +59,22 @@
%div
= link_to 'Enable Two-factor Authentication', new_profile_two_factor_auth_path, class: 'btn btn-success'
- if show_profile_social_tab?
- if button_based_providers.any?
.panel.panel-default
.panel-heading
Connected Accounts
.panel-body
.oauth-buttons.append-bottom-10
%p Click on icon to activate signin with one of the following services
- enabled_social_providers.each do |provider|
- button_based_providers.each do |provider|
.btn-group
= link_to oauth_image_tag(provider), omniauth_authorize_path(User, provider),
method: :post, class: "btn btn-lg #{'active' if oauth_active?(provider)}"
- if oauth_active?(provider)
= link_to provider_image_tag(provider), user_omniauth_authorize_path(provider), method: :post, class: "btn btn-lg #{'active' if auth_active?(provider)}", "data-no-turbolink" => "true"
- if auth_active?(provider)
= link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'btn btn-lg' do
= icon('close')
- if show_profile_username_tab?
- if current_user.can_change_username?
.panel.panel-warning.update-username
.panel-heading
Change Username
......@@ -94,7 +94,7 @@
%div
= f.submit 'Save username', class: "btn btn-warning"
- if show_profile_remove_tab?
- if signup_enabled?
.panel.panel-danger.remove-account
.panel-heading
Remove account
......
......@@ -6,14 +6,13 @@
- @key.errors.full_messages.each do |msg|
%li= msg
.form-group
= f.label :title, class: 'control-label'
.col-sm-10= f.text_field :title, class: "form-control"
.form-group
= f.label :key, class: 'control-label'
.col-sm-10
= f.text_area :key, class: "form-control", rows: 8
.form-group
= f.label :title, class: 'control-label'
.col-sm-10= f.text_field :title, class: "form-control"
.form-actions
= f.submit 'Add key', class: "btn btn-create"
......
......@@ -5,7 +5,7 @@
Download the Google Authenticator application from App Store for iOS or Google
Play for Android and scan this code.
More information is available in the #{link_to('documentation', help_page_path('workflow', 'two_factor_authentication'))}.
More information is available in the #{link_to('documentation', help_page_path('profile', 'two_factor_authentication'))}.
%hr
......
......@@ -11,7 +11,7 @@
%td.old_line.diff-line-num{data: {linenumber: line_old}}
= link_to raw(line_old), "#"
%td.new_line= link_to raw(line_new) , "#"
%td.line_content.noteable_line= line
%td.line_content.noteable_line= ' ' * @form.indent + line
- if @form.unfold? && @form.bottom? && @form.to < @blob.loc
%tr.line_holder{ id: @form.to }
......
- too_big = diff_file.diff_lines.count > Commit::DIFF_SAFE_LINES
- if too_big
%a.supp_diff_link Changes suppressed. Click to show
.suppressed-container
%a.show-suppressed-diff.js-show-suppressed-diff Changes suppressed. Click to show.
%table.text-file{class: "#{'hide' if too_big}"}
- last_line = 0
......
......@@ -3,7 +3,7 @@
Too many changes to show.
.pull-right
- unless diff_hard_limit_enabled?
= link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: nil)), class: "btn btn-sm btn-warning"
= link_to "Reload with full diff", url_for(params.merge(force_show_diff: true, format: :html)), class: "btn btn-sm btn-warning"
- if current_controller?(:commit) or current_controller?(:merge_requests)
- if current_controller?(:commit)
......
......@@ -3,43 +3,42 @@
.issue-check
= check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
= cache issue do
.issue-title
%span.issue-title-text
= link_to_gfm issue.title, issue_path(issue), class: "row_title"
.issue-labels
- issue.labels.each do |label|
= link_to_label(label, project: issue.project)
.pull-right.light
- if issue.closed?
%span
CLOSED
- if issue.assignee
= link_to_member(@project, issue.assignee, name: false)
- note_count = issue.notes.user.count
- if note_count > 0
&nbsp;
%span
%i.fa.fa-comments
= note_count
- else
&nbsp;
%span.issue-no-comments
%i.fa.fa-comments
= 0
.issue-info
= "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe
- if issue.votes_count > 0
= render 'votes/votes_inline', votable: issue
- if issue.milestone
.issue-title
%span.issue-title-text
= link_to_gfm issue.title, issue_path(issue), class: "row_title"
.issue-labels
- issue.labels.each do |label|
= link_to_label(label, project: issue.project)
.pull-right.light
- if issue.closed?
%span
CLOSED
- if issue.assignee
= link_to_member(@project, issue.assignee, name: false)
- note_count = issue.notes.user.count
- if note_count > 0
&nbsp;
%span
%i.fa.fa-clock-o
= issue.milestone.title
- if issue.tasks?
%span.task-status
= issue.task_status
%i.fa.fa-comments
= note_count
- else
&nbsp;
%span.issue-no-comments
%i.fa.fa-comments
= 0
.issue-info
= "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe
- if issue.votes_count > 0
= render 'votes/votes_inline', votable: issue
- if issue.milestone
&nbsp;
%span
%i.fa.fa-clock-o
= issue.milestone.title
- if issue.tasks?
%span.task-status
= issue.task_status
.pull-right.issue-updated-at
%small updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
.pull-right.issue-updated-at
%small updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
......@@ -31,6 +31,16 @@
%li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch)
%li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff)
- if @merge_request.open? and @merge_request.source_branch_exists?
.append-bottom-20
.slead
%span
Fetch the branch with
%strong.label-branch<
git fetch
\ #{@merge_request.source_project.http_url_to_repo}
\ #{@merge_request.source_branch}
= render "projects/merge_requests/show/how_to_merge"
= render "projects/merge_requests/widget/show.html.haml"
......@@ -56,11 +66,9 @@
#notes.notes.tab-pane.voting_notes
= render "projects/merge_requests/discussion"
#commits.commits.tab-pane
- if current_page?(action: 'commits')
= render "projects/merge_requests/show/commits"
- # This tab is always loaded via AJAX
#diffs.diffs.tab-pane
- if current_page?(action: 'diffs')
= render "projects/merge_requests/show/diffs"
- # This tab is always loaded via AJAX
.mr-loading-status
= spinner
......
:plain
$(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}");
$('.js-timeago').timeago()
:plain
$(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}");
$('.js-timeago').timeago()
......@@ -6,9 +6,11 @@
%span.label.label-inverse= @merge_request.source_branch
does not exist in
%span.label.label-info= @merge_request.source_project_path
%br
%strong Please close this merge request and open a new merge request to change source branches.
- else
%span.label.label-inverse= @merge_request.target_branch
does not exist in
%span.label.label-info= @merge_request.target_project_path
%br
%strong Please close this merge request or change branches with existing one
%br
%strong Please close this merge request or change to another target branch.
......@@ -5,6 +5,10 @@
%i.fa.fa-pencil-square-o
Edit
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close"
= link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-sm btn-remove" do
%i.fa.fa-trash-o
Remove
%h4
= link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone)
- if milestone.expired? and not milestone.closed?
......
......@@ -19,6 +19,9 @@
= link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped"
- else
= link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
= link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do
%i.fa.fa-trash-o
Remove
%hr
- if @milestone.issues.any? && @milestone.can_be_closed?
......
......@@ -17,9 +17,9 @@
:javascript
network_graph = new Network({
url: '#{namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))}',
commit_url: '#{namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")}',
ref: '#{@ref}',
url: "#{escape_javascript(@url)}",
commit_url: "#{escape_javascript(@commit_url)}",
ref: "#{escape_javascript(@ref)}",
commit_id: '#{@commit.id}'
})
new ShortcutsNetwork(network_graph.branch_graph)
......@@ -3,10 +3,7 @@
= note_target_fields(note)
= render layout: 'projects/md_preview', locals: { preview_class: 'note-text' } do
= render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field'
.comment-hints.clearfix
.pull-left #{link_to 'Markdown ', help_page_path('markdown', 'markdown'),{ target: '_blank', tabindex: -1 }}
.pull-right #{link_to 'Attach a file', '#', class: 'markdown-selector', tabindex: -1 }
= render 'projects/notes/hints'
.note-form-actions
.buttons
......
......@@ -8,18 +8,8 @@
= f.hidden_field :noteable_type
= render layout: 'projects/md_preview', locals: { preview_class: "note-text", referenced_users: true } do
= render 'projects/zen', f: f, attr: :note,
classes: 'note_text js-note-text'
.comment-hints.clearfix
.pull-left
= link_to "Markdown ", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }
tip:
= random_markdown_tip
.pull-right
= link_to '#', class: 'markdown-selector', tabindex: -1 do
Attach a file
= icon('paperclip')
= render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text'
= render 'projects/notes/hints'
.error-alert
.note-form-actions
......
.comment-hints.clearfix
.pull-left
= link_to 'Markdown', help_page_path('markdown', 'markdown'), target: '_blank', tabindex: -1
tip:
= random_markdown_tip
.pull-right
= link_to '#', class: 'markdown-selector', tabindex: -1 do
= icon('paperclip')
Attach a file
......@@ -56,10 +56,9 @@
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
= cache [note, 'markdown'] do
.note-text
= preserve do
= markdown(note.note, {no_header_anchors: true})
.note-text
= preserve do
= markdown(note.note, {no_header_anchors: true})
= render 'projects/notes/edit_form', note: note
- if note.attachment.url
......
......@@ -11,9 +11,11 @@
- if @logs.present?
:plain
var current_url = location.href.replace(/\/?$/, '/');
var log_url = '#{namespace_project_tree_url(@project.namespace, @project, tree_join(@ref, @path || '/'))}'.replace(/\/?$/, '/');
var log_url = "#{escape_javascript(@log_url)}".replace(/\/?$/, '/');
if(current_url == log_url) {
// Load 10 more commit log for each file in tree
// Load more commit logs for each file in tree
// if we still on the same page
ajaxGet('#{logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: (@offset + @limit))}');
var url = "#{escape_javascript(@more_log_url)}";
ajaxGet(url);
}
......@@ -6,33 +6,50 @@
= render 'shared/no_ssh'
= render 'shared/no_password'
= render 'projects/last_push'
- if prefer_readme?
= render 'projects/last_push'
= render "home_panel"
.project-stats
%ul.nav.nav-pills
%li
= link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do
= pluralize(number_with_delimiter(@repository.commit_count), 'commit')
= pluralize(number_with_delimiter(@project.commit_count), 'commit')
%li
= link_to namespace_project_branches_path(@project.namespace, @project) do
= pluralize(number_with_delimiter(@repository.branch_names.count), 'branch')
%li
= link_to namespace_project_tags_path(@project.namespace, @project) do
= pluralize(number_with_delimiter(@repository.tag_names.count), 'tag')
- if @repository.changelog
%li
= link_to changelog_url(@project) do
= link_to changelog_path(@project) do
Changelog
- if @repository.license
%li
= link_to license_url(@project) do
= link_to license_path(@project) do
License
- if @repository.contribution_guide
%li
= link_to contribution_guide_url(@project) do
= link_to contribution_guide_path(@project) do
Contribution guide
- if current_user && can_push_branch?(@project, @project.default_branch)
- unless @repository.changelog
%li.missing
= link_to add_changelog_path(@project) do
Add Changelog
- unless @repository.license
%li.missing
= link_to add_license_path(@project) do
Add License
- unless @repository.contribution_guide
%li.missing
= link_to add_contribution_guide_path(@project) do
Add Contribution guide
- if @project.archived?
.text-warning.center.prepend-top-20
%p
......
......@@ -49,5 +49,5 @@
:javascript
// Load last commit log for each file in tree
$('#tree-slider').waitForImages(function() {
ajaxGet('#{@logs_path}');
ajaxGet("#{escape_javascript(@logs_path)}");
});
......@@ -3,6 +3,10 @@
%h3.page-title
= @page.title
= render 'main_links'
.wiki-last-edit-by
Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
- if @page.historical?
.warning_message
This is an old version of this page.
......@@ -16,6 +20,6 @@
= render_wiki_content(@page)
%hr
.wiki-last-edit-by
Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)}
......@@ -8,7 +8,7 @@
- else
none
.issuable-context-selectbox
- if can?(current_user, :admin_issue, @project)
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true)
%div.prepend-top-20.clearfix
......@@ -24,7 +24,7 @@
- else
none
.issuable-context-selectbox
- if can?(current_user, :admin_issue, @project)
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
= f.select(:milestone_id, milestone_options(issuable), { include_blank: 'Select milestone' }, {class: 'select2 select2-compact js-select2 js-milestone'})
= hidden_field_tag :issuable_context
= f.submit class: 'btn hide'
......
......@@ -38,7 +38,7 @@
.clearfix
.error-alert
%hr
- if can?(current_user, :admin_issue, @project)
- if can?(current_user, :"admin_#{issuable.to_ability_name}", @project)
.form-group
.issue-assignee
= f.label :assignee_id, class: 'control-label' do
......@@ -100,7 +100,7 @@
= link_to 'Change branches', mr_change_branches_path(@merge_request)
.form-actions
- if !issuable.project.empty_repo? && (guide_url = contribution_guide_url(issuable.project)) && !issuable.persisted?
- if !issuable.project.empty_repo? && (guide_url = contribution_guide_path(issuable.project)) && !issuable.persisted?
%p
Please review the
%strong #{link_to 'guidelines for contribution', guide_url}
......
class ProjectCacheWorker
include Sidekiq::Worker
sidekiq_options queue: :default
def perform(project_id)
project = Project.find(project_id)
project.update_repository_size
project.update_commit_count
if project.repository.root_ref
project.repository.build_cache
end
end
end
......@@ -27,7 +27,7 @@ class RepositoryImportWorker
project.import_finish
project.save
project.update_repository_size
ProjectCacheWorker.perform_async(project.id)
Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
end
end
......@@ -96,6 +96,7 @@ module Gitlab
end
redis_config_hash[:namespace] = 'cache:gitlab'
redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
config.cache_store = :redis_store, redis_config_hash
# This is needed for gitlab-shell
......
......@@ -209,20 +209,29 @@ production: &base
# arguments, followed by optional 'args' which can be either a hash or an array.
# Documentation for this is available at http://doc.gitlab.com/ce/integration/omniauth.html
providers:
# - { name: 'google_oauth2', app_id: 'YOUR_APP_ID',
# - { name: 'google_oauth2',
# label: 'Google',
# app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET',
# args: { access_type: 'offline', approval_prompt: '' } }
# - { name: 'twitter', app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET'}
# - { name: 'github', app_id: 'YOUR_APP_ID',
# - { name: 'twitter',
# app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET' }
# - { name: 'github',
# label: 'GitHub',
# app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET',
# args: { scope: 'user:email' } }
# - { name: 'gitlab', app_id: 'YOUR_APP_ID',
# - { name: 'gitlab',
# label: 'GitLab.com',
# app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET',
# args: { scope: 'api' } }
# - { name: 'bitbucket', app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET'}
# - { name: 'saml',
# - { name: 'bitbucket',
# app_id: 'YOUR_APP_ID',
# app_secret: 'YOUR_APP_SECRET' }
# - { name: 'saml',
# label: 'Our SAML Provider',
# args: {
# assertion_consumer_service_url: 'https://gitlab.example.com/users/auth/saml/callback',
# idp_cert_fingerprint: '43:51:43:a1:b5:fc:8b:b7:0a:3a:a9:b1:0f:66:73:a8',
......@@ -247,6 +256,7 @@ production: &base
## Backup settings
backup:
path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/)
# archive_permissions: 0640 # Permissions for the resulting backup.tar file (default: 0600)
# keep_time: 604800 # default: 0 (forever) (in seconds)
# upload:
# # Fog storage connection settings, see http://fog.io/storage/ .
......@@ -338,6 +348,8 @@ test:
# user: YOUR_USERNAME
satellites:
path: tmp/tests/gitlab-satellites/
backup:
path: tmp/tests/backups
gitlab_shell:
path: tmp/tests/gitlab-shell/
repos_path: tmp/tests/repositories/
......
......@@ -170,6 +170,7 @@ Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.send(:build_gitlab_shell_s
Settings['backup'] ||= Settingslogic.new({})
Settings.backup['keep_time'] ||= 0
Settings.backup['path'] = File.expand_path(Settings.backup['path'] || "tmp/backups/", Rails.root)
Settings.backup['archive_permissions'] ||= 0600
Settings.backup['upload'] ||= Settingslogic.new({ 'remote_directory' => nil, 'connection' => nil })
# Convert upload connection settings to use symbol keys, to make Fog happy
if Settings.backup['upload']['connection']
......
......@@ -11,6 +11,7 @@ if Gitlab::LDAP::Config.enabled?
end
end
OmniAuth.config.full_host = Settings.gitlab['url']
OmniAuth.config.allowed_request_methods = [:post]
#In case of auto sign-in, the GET method is used (users don't get to click on a button)
OmniAuth.config.allowed_request_methods << :get if Gitlab.config.omniauth.auto_sign_in_with_provider.present?
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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