Commit cb8b5c32 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Merge branch 'ce-into-ee' into 'master'

CE into EE

See merge request !303
parents 45349abd 96a3f0e0
This diff is collapsed.
......@@ -8,7 +8,7 @@ v 7.8.0
- Better UI for project services page
- Cleaner UI for web editor
- Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger)
-
- Add API endpoint to fetch all changes on a MergeRequest (Jeroen van Baarsen)
-
-
- Allow more variations for commit messages closing issues (Julien Bianchi and Hannes Rosenögger)
......@@ -25,10 +25,12 @@ v 7.8.0
- Upgrade Sidekiq gem to version 3.3.0
- Stop git zombie creation during force push check
- Show success/error messages for test setting button in services
-
- Added Rubocop for code style checks
- Fix commits pagination
-
-
- Async load a branch information at the commit page
- Disable blacklist validation for project names
- Allow configuring protection of the default branch upon first push (Marco Wessel)
-
-
- Add a commit calendar to the user profile (Hannes Rosenögger)
......@@ -42,7 +44,7 @@ v 7.8.0
- Password reset token validity increased from 2 hours to 2 days since it is also send on account creation.
-
-
-
- Enable raw image paste from clipboard, currently Chrome only (Marco Cyriacks)
-
-
- Add action property to merge request hook (Julien Bianchi)
......@@ -53,15 +55,20 @@ v 7.8.0
- Add a new API function that retrieves all issues assigned to a single milestone (Justin Whear and Hannes Rosenögger)
-
-
-
-
- API: Access groups with their path (Julien Bianchi)
- Added link to milestone and keeping resource context on smaller viewports for issues and merge requests (Jason Blanchard)
-
-
- API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger)
-
-
-
- When test web hook - show error message instead of 500 error page if connection to hook url was reset
- Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov)
- Added persistent collapse button for left side nav bar (Jason Blanchard)
v 7.7.2
- Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch
- Fix issue when LDAP user can't login with existing GitLab account
v 7.7.1
- Improve mention autocomplete performance
......
......@@ -208,8 +208,6 @@ group :development do
gem 'better_errors'
gem 'binding_of_caller'
gem 'rails_best_practices'
# Docs generator
gem "sdoc"
......@@ -219,11 +217,12 @@ end
group :development, :test do
gem 'coveralls', require: false
gem 'rubocop', '0.28.0', require: false
# gem 'rails-dev-tweaks'
gem 'spinach-rails'
gem "rspec-rails"
gem "capybara", '~> 2.2.1'
gem "pry"
gem "pry-rails"
gem "awesome_print"
gem "database_cleaner"
gem "launchy"
......@@ -256,7 +255,7 @@ end
group :test do
gem "simplecov", require: false
gem "shoulda-matchers", "~> 2.1.0"
gem "shoulda-matchers", "~> 2.7.0"
gem 'email_spec'
gem "webmock"
gem 'test_after_commit'
......
......@@ -37,6 +37,9 @@ GEM
rake (>= 0.8.7)
arel (5.0.1.20140414130214)
asciidoctor (0.1.4)
ast (2.0.0)
astrolabe (1.3.0)
parser (>= 2.2.0.pre.3, < 3.0)
attr_required (1.0.0)
awesome_print (1.2.0)
axiom-types (0.0.5)
......@@ -67,8 +70,6 @@ GEM
timers (~> 4.0.0)
charlock_holmes (0.6.9.4)
cliver (0.3.2)
code_analyzer (0.4.3)
sexp_processor
coderay (1.1.0)
coercible (1.0.0)
descendants_tracker (~> 0.0.1)
......@@ -354,6 +355,8 @@ GEM
org-ruby (0.9.12)
rubypants (~> 0.2)
orm_adapter (0.5.0)
parser (2.2.0.2)
ast (>= 1.1, < 3.0)
pg (0.15.1)
phantomjs (1.9.2.0)
poltergeist (1.5.1)
......@@ -363,10 +366,13 @@ GEM
websocket-driver (>= 0.2.0)
polyglot (0.3.4)
posix-spawn (0.3.9)
powerpack (0.0.9)
pry (0.9.12.4)
coderay (~> 1.0)
method_source (~> 0.8)
slop (~> 3.4)
pry-rails (0.3.2)
pry (>= 0.9.10)
pyu-ruby-sasl (0.0.3.3)
quiet_assets (1.0.2)
railties (>= 3.1, < 5.0)
......@@ -403,20 +409,12 @@ GEM
sprockets-rails (~> 2.0)
rails_autolink (1.1.6)
rails (> 3.1)
rails_best_practices (1.14.4)
activesupport
awesome_print
code_analyzer (>= 0.4.3)
colored
erubis
i18n
require_all
ruby-progressbar
railties (4.1.1)
actionpack (= 4.1.1)
activesupport (= 4.1.1)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rainbow (2.0.0)
raindrops (0.13.0)
rake (10.3.2)
raphael-rails (2.1.2)
......@@ -447,7 +445,6 @@ GEM
redis (>= 2.2)
ref (1.0.5)
request_store (1.0.5)
require_all (1.3.2)
rest-client (1.6.7)
mime-types (>= 1.16)
rinku (1.7.3)
......@@ -467,7 +464,13 @@ GEM
rspec-core (~> 2.14.0)
rspec-expectations (~> 2.14.0)
rspec-mocks (~> 2.14.0)
ruby-progressbar (1.2.0)
rubocop (0.28.0)
astrolabe (~> 1.3)
parser (>= 2.2.0.pre.7, < 3.0)
powerpack (~> 0.0.6)
rainbow (>= 1.99.1, < 3.0)
ruby-progressbar (~> 1.4)
ruby-progressbar (1.7.1)
rubyntlm (0.4.0)
rubypants (0.2.0)
rugged (0.21.2)
......@@ -495,8 +498,7 @@ GEM
semantic-ui-sass (1.8.0.0)
sass (~> 3.2)
settingslogic (2.0.9)
sexp_processor (4.4.0)
shoulda-matchers (2.1.0)
shoulda-matchers (2.7.0)
activesupport (>= 3.0.0)
sidekiq (3.3.0)
celluloid (>= 0.16.0)
......@@ -700,7 +702,7 @@ DEPENDENCIES
org-ruby (= 0.9.12)
pg
poltergeist (~> 1.5.1)
pry
pry-rails
quiet_assets (~> 1.0.1)
rack-attack
rack-cors
......@@ -708,7 +710,6 @@ DEPENDENCIES
rack-oauth2 (~> 1.0.5)
rails (~> 4.1.0)
rails_autolink (~> 1.1)
rails_best_practices
raphael-rails (~> 2.1.2)
rb-fsevent
rb-inotify
......@@ -717,6 +718,7 @@ DEPENDENCIES
redis-rails
request_store
rspec-rails
rubocop (= 0.28.0)
rugments
sanitize (~> 2.0)
sass-rails (~> 4.0.2)
......@@ -725,7 +727,7 @@ DEPENDENCIES
select2-rails
semantic-ui-sass (~> 1.8.0)
settingslogic
shoulda-matchers (~> 2.1.0)
shoulda-matchers (~> 2.7.0)
sidekiq (~> 3.3)
sidetiq (= 0.6.3)
simplecov
......
......@@ -9,8 +9,6 @@ class @calendar
cal.init
itemName: ["commit"]
data: timestamps
domain: "year"
subDomain: "month"
start: new Date(starting_year, starting_month)
domainLabelFormat: "%b"
id: "cal-heatmap"
......
......@@ -13,6 +13,8 @@ class @DropzoneInput
form_textarea = $(form).find("textarea.markdown-area")
form_textarea.wrap "<div class=\"div-dropzone\"></div>"
form_textarea.bind 'paste', (event) =>
handlePaste(event)
form_dropzone = $(form).find('.div-dropzone')
form_dropzone.parent().addClass "div-dropzone-wrapper"
......@@ -133,25 +135,18 @@ class @DropzoneInput
formatLink = (str) ->
"![" + str.alt + "](" + str.url + ")"
handlePaste = (e) ->
e.preventDefault()
my_event = e.originalEvent
if my_event.clipboardData and my_event.clipboardData.items
processItem(my_event)
processItem = (e) ->
image = isImage(e)
handlePaste = (event) ->
pasteEvent = event.originalEvent
if pasteEvent.clipboardData and pasteEvent.clipboardData.items
image = isImage(pasteEvent)
if image
filename = getFilename(e) or "image.png"
event.preventDefault()
filename = getFilename(pasteEvent) or "image.png"
text = "{{" + filename + "}}"
pasteText(text)
uploadFile image.getAsFile(), filename
else
text = e.clipboardData.getData("text/plain")
pasteText(text)
isImage = (data) ->
i = 0
while i < data.clipboardData.items.length
......
......@@ -24,3 +24,18 @@ $ ->
$(window).resize ->
responsive_resize()
return
$(document).on("click", '.toggle-nav-collapse', (e) ->
e.preventDefault()
collapsed = 'page-sidebar-collapsed'
expanded = 'page-sidebar-expanded'
if $('.page-with-sidebar').hasClass(collapsed)
$('.page-with-sidebar').removeClass(collapsed).addClass(expanded)
$('.toggle-nav-collapse i').removeClass('fa-angle-right').addClass('fa-angle-left')
$.cookie("collapsed_nav", "false", { path: '/' })
else
$('.page-with-sidebar').removeClass(expanded).addClass(collapsed)
$('.toggle-nav-collapse i').removeClass('fa-angle-left').addClass('fa-angle-right')
$.cookie("collapsed_nav", "true", { path: '/' })
)
......@@ -15,6 +15,10 @@
&.s24 { margin-right: 4px; }
}
&.avatar-tile {
@include border-radius(0px);
}
&.s16 { width: 16px; height: 16px; margin-right: 6px; }
&.s24 { width: 24px; height: 24px; margin-right: 8px; }
&.s26 { width: 26px; height: 26px; margin-right: 8px; }
......
......@@ -97,7 +97,17 @@
.dash-project-avatar {
float: left;
.avatar {
margin-top: -8px;
margin-left: -15px;
@include border-radius(0px);
}
.identicon {
line-height: 40px;
}
}
.dash-project-access-icon {
float: left;
margin-right: 5px;
......
......@@ -40,12 +40,12 @@
font-size: $code_font_size;
.old {
span.idiff {
background-color: #F99;
background-color: #f8cbcb;
}
}
.new {
span.idiff {
background-color: #8F8;
background-color: #a6f3a6;
}
}
.unfold {
......@@ -84,7 +84,7 @@
padding: 0px;
border: none;
background: #F5F5F5;
color: #666;
color: rgba(0,0,0,0.3);
padding: 0px 5px;
border-right: 1px solid #ccc;
text-align: right;
......@@ -96,7 +96,7 @@
float: left;
width: 35px;
font-weight: normal;
color: #666;
color: rgba(0,0,0,0.3);
&:hover {
text-decoration: underline;
}
......@@ -114,13 +114,13 @@
.line_holder {
&.old .old_line,
&.old .new_line {
background: #FCC;
border-color: #E7BABA;
background: #ffdddd;
border-color: #f1c0c0;
}
&.new .old_line,
&.new .new_line {
background: #CFC;
border-color: #B9ECB9;
background: #dbffdb;
border-color: #c1e9c1;
}
}
.line_content {
......@@ -129,10 +129,10 @@
padding: 0px 0.5em;
border: none;
&.new {
background: #CFD;
background: #eaffea;
}
&.old {
background: #FDD;
background: #ffecec;
}
&.matched {
color: #ccc;
......
.page-with-sidebar {
background: #F5F5F5;
......@@ -101,9 +99,7 @@
}
@mixin expanded-sidebar {
.page-with-sidebar {
padding-left: $sidebar_width;
}
.sidebar-wrapper {
width: $sidebar_width;
......@@ -122,9 +118,7 @@
}
@mixin folded-sidebar {
.page-with-sidebar {
padding-left: 50px;
}
.sidebar-wrapper {
width: 52px;
......@@ -150,10 +144,33 @@
}
}
.collapse-nav a {
position: fixed;
bottom: 15px;
padding: 10px;
background: #DDD;
}
@media (max-width: $screen-md-max) {
.page-sidebar-collapsed {
@include folded-sidebar;
}
.page-sidebar-expanded {
@include folded-sidebar;
}
.collapse-nav {
display: none;
}
}
@media(min-width: $screen-md-max) {
.page-sidebar-collapsed {
@include folded-sidebar;
}
.page-sidebar-expanded {
@include expanded-sidebar;
}
}
......@@ -81,7 +81,7 @@ ul.notes {
.diff-file .notes_holder {
font-size: 13px;
line-height: 18px;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-family: $regular_font;
td {
border: 1px solid #ddd;
......
......@@ -22,6 +22,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
def application_setting_params
params.require(:application_setting).permit(
:default_projects_limit,
:default_branch_protection,
:signup_enabled,
:signin_enabled,
:gravatar_enabled,
......
......@@ -23,7 +23,7 @@ class GithubImportsController < ApplicationController
end
def jobs
jobs = current_user.created_projects.where(import_type: "github").to_json(:only => [:id, :import_status])
jobs = current_user.created_projects.where(import_type: "github").to_json(only: [:id, :import_status])
render json: jobs
end
......@@ -58,7 +58,7 @@ class GithubImportsController < ApplicationController
def octo_client
Octokit.auto_paginate = true
@octo_client ||= Octokit::Client.new(:access_token => current_user.github_access_token)
@octo_client ||= Octokit::Client.new(access_token: current_user.github_access_token)
end
def github_auth
......
......@@ -15,4 +15,3 @@ class NamespacesController < ApplicationController
end
end
end
......@@ -59,8 +59,7 @@ class Projects::BlobController < Projects::ApplicationController
def preview
@content = params[:content]
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3',
include_diff_info: true)
diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true)
@diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/))
render layout: false
......
......@@ -11,8 +11,6 @@ class Projects::CommitController < Projects::ApplicationController
return git_not_found! unless @commit
@line_notes = @project.notes.for_commit_id(commit.id).inline
@branches = @project.repository.branch_names_contains(commit.id)
@tags = @project.repository.tag_names_contains(commit.id)
@diffs = @commit.diffs
@note = @project.build_commit_note(commit)
@notes_count = @project.notes.for_commit_id(commit.id).count
......@@ -31,6 +29,12 @@ class Projects::CommitController < Projects::ApplicationController
end
end
def branches
@branches = @project.repository.branch_names_contains(commit.id)
@tags = @project.repository.tag_names_contains(commit.id)
render layout: false
end
def commit
@commit ||= @project.repository.commit(params[:id])
end
......
......@@ -21,4 +21,3 @@ class Projects::GroupLinksController < Projects::ApplicationController
redirect_to project_group_links_path(project)
end
end
......@@ -35,4 +35,3 @@ class Projects::RawController < Projects::ApplicationController
end
end
end
......@@ -27,7 +27,7 @@ class SnippetsController < ApplicationController
@snippets = SnippetsFinder.new.execute(current_user, {
filter: :by_user,
user: @user,
scope: params[:scope]}).
scope: params[:scope] }).
page(params[:page]).per(20)
if @user == current_user
......
class UsersController < ApplicationController
skip_before_filter :authenticate_user!, only: [:show, :activities]
skip_before_filter :authenticate_user!
before_filter :set_user
layout :determine_layout
def show
@user = User.find_by_username!(params[:username])
unless current_user || @user.public_profile?
return authenticate_user!
end
# Projects user can view
visible_projects = ProjectsFinder.new.execute(current_user)
authorized_projects_ids = visible_projects.pluck(:id)
......@@ -25,19 +20,22 @@ class UsersController < ApplicationController
@title = @user.name
# Get user repositories and collect timestamps for commits
user_repositories = visible_projects.map(&:repository)
calendar = Gitlab::CommitsCalendar.new(user_repositories, @user)
@timestamps = calendar.timestamps
@starting_year = (Time.now - 1.year).strftime("%Y")
@starting_month = Date.today.strftime("%m").to_i
respond_to do |format|
format.html
format.atom { render layout: false }
end
end
def calendar
visible_projects = ProjectsFinder.new.execute(current_user)
calendar = Gitlab::CommitsCalendar.new(visible_projects, @user)
@timestamps = calendar.timestamps
@starting_year = calendar.starting_year
@starting_month = calendar.starting_month
render 'calendar', layout: false
end
def determine_layout
if current_user
'navless'
......@@ -45,4 +43,14 @@ class UsersController < ApplicationController
'public_users'
end
end
private
def set_user
@user = User.find_by_username!(params[:username])
unless current_user || @user.public_profile?
return authenticate_user!
end
end
end
......@@ -7,7 +7,8 @@ class NotesFinder
# Default to 0 to remain compatible with old clients
last_fetched_at = Time.at(params.fetch(:last_fetched_at, 0).to_i)
notes = case target_type
notes =
case target_type
when "commit"
project.notes.for_commit_id(target_id).not_inline.fresh
when "issue"
......
......@@ -75,9 +75,9 @@ module ApplicationHelper
options[:class] ||= ''
options[:class] << ' identicon'
bg_key = project.id % 7
style = "background-color: ##{ allowed_colors.values[bg_key] }; color: #555"
content_tag(:div, class: options[:class],
style: "background-color: ##{ allowed_colors.values[bg_key] }; color: #555") do
content_tag(:div, class: options[:class], style: style) do
project.name[0, 1].upcase
end
end
......@@ -247,15 +247,6 @@ module ApplicationHelper
Gitlab::MarkdownHelper.gitlab_markdown?(filename)
end
def spinner(text = nil, visible = false)
css_class = 'loading'
css_class << ' hide' unless visible
content_tag :div, class: css_class do
content_tag(:i, nil, class: 'fa fa-spinner fa-spin') + text
end
end
def link_to(name = nil, options = nil, html_options = nil, &block)
begin
uri = URI(options)
......@@ -324,4 +315,12 @@ module ApplicationHelper
profile_key_path(key)
end
end
def nav_sidebar_class
if nav_menu_collapsed?
"page-sidebar-collapsed"
else
"page-sidebar-expanded"
end
end
end
......@@ -65,8 +65,7 @@ module CommitsHelper
branches.sort.map do |branch|
link_to(project_tree_path(project, branch)) do
content_tag :span, class: 'label label-gray' do
content_tag(:i, nil, class: 'fa fa-code-fork') + ' ' +
branch
icon('code-fork') + ' ' + branch
end
end
end.join(" ").html_safe
......@@ -78,8 +77,7 @@ module CommitsHelper
sorted.map do |tag|
link_to(project_commits_path(project, project.repository.find_tag(tag).name)) do
content_tag :span, class: 'label label-gray' do
content_tag(:i, nil, class: 'fa fa-tag') + ' ' +
tag
icon('tag') + ' ' + tag
end
end
end.join(" ").html_safe
......@@ -114,7 +112,8 @@ module CommitsHelper
person_name = user.nil? ? source_name : user.name
person_email = user.nil? ? source_email : user.email
text = if options[:avatar]
text =
if options[:avatar]
avatar = image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "")
%Q{#{avatar} <span class="commit-#{options[:source]}-name">#{person_name}</span>}
else
......
......@@ -10,6 +10,6 @@ module CompareHelper
end
def compare_mr_path
new_project_merge_request_path(@project, merge_request: {source_branch: params[:to], target_branch: params[:from]})
new_project_merge_request_path(@project, merge_request: { source_branch: params[:to], target_branch: params[:from] })
end
end
......@@ -31,7 +31,7 @@ module EmailsHelper
end
def add_email_highlight_css
Rugments::Themes::Github.render(:scope => '.highlight')
Rugments::Themes::Github.render(scope: '.highlight')
end
def color_email_diff(diffcontent)
......
......@@ -27,18 +27,17 @@ module EventsHelper
content_tag :li, class: "filter_icon #{active}" do
link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do
content_tag(:i, nil, class: icon_for_event[key]) +
content_tag(:span, ' ' + tooltip)
icon(icon_for_event[key]) + content_tag(:span, ' ' + tooltip)
end
end
end
def icon_for_event
{
EventFilter.push => 'fa fa-upload',
EventFilter.merged => 'fa fa-check-square-o',
EventFilter.comments => 'fa fa-comments',
EventFilter.team => 'fa fa-user',
EventFilter.push => 'upload',
EventFilter.merged => 'check-square-o',
EventFilter.comments => 'comments',
EventFilter.team => 'user',
}
end
......
module IconsHelper
# Creates an icon tag given icon name(s) and possible icon modifiers.
#
# Right now this method simply delegates directly to `fa_icon` from the
# font-awesome-rails gem, but should we ever use a different icon pack in the
# future we won't have to change hundreds of method calls.
def icon(names, options = {})
fa_icon(names, options)
end
def spinner(text = nil, visible = false)
css_class = 'loading'
css_class << ' hide' unless visible
content_tag :div, class: css_class do
icon('spinner spin') + text
end
end
def boolean_to_icon(value)
if value.to_s == "true"
content_tag :i, nil, class: 'fa fa-circle cgreen'
icon('circle', class: 'cgreen')
else
content_tag :i, nil, class: 'fa fa-power-off clgray'
icon('power-off', class: 'clgray')
end
end
def public_icon
content_tag :i, nil, class: 'fa fa-globe'
icon('globe')
end
def internal_icon
content_tag :i, nil, class: 'fa fa-shield'
icon('shield')
end
def private_icon
content_tag :i, nil, class: 'fa fa-lock'
icon('lock')
end
end
......@@ -49,7 +49,7 @@ module IssuesHelper
ts << capture_haml do
haml_tag :span do
haml_concat '&middot;'
haml_concat '<i class="fa fa-edit" title="edited"></i> '
haml_concat icon('edit', title: 'edited')
haml_concat time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_edited_ago')
end
end
......
......@@ -15,12 +15,14 @@ module MergeRequestsHelper
end
def new_mr_from_push_event(event, target_project)
return :merge_request => {
return {
merge_request: {
source_project_id: event.project.id,
target_project_id: target_project.id,
source_branch: event.branch_name,
target_branch: target_project.repository.root_ref
}
}
end
def mr_css_classes(mr)
......
module NavHelper
def nav_menu_collapsed?
cookies[:collapsed_nav] == 'true'
end
end
......@@ -22,7 +22,7 @@ module NotesHelper
ts << capture_haml do
haml_tag :span do
haml_concat '&middot;'
haml_concat '<i class="fa fa-edit" title="edited"></i> '
haml_concat icon('edit', title: 'edited')
haml_concat time_ago_with_tooltip(note.updated_at, 'bottom', 'note_edited_ago')
end
end
......@@ -57,7 +57,7 @@ module NotesHelper
button_tag(class: 'btn add-diff-note js-add-diff-note-button',
data: data,
title: 'Add a comment to this line') do
content_tag :i, nil, class: 'fa fa-comment-o'
icon('comment-o')
end
end
......@@ -74,7 +74,7 @@ module NotesHelper
button_tag class: 'btn reply-btn js-discussion-reply-button',
data: data, title: 'Add a reply' do
link_text = content_tag(:i, nil, class: 'fa fa-comment')
link_text = icon('comment')
link_text << ' Reply'
end
end
......
module NotificationsHelper
def notification_icon(notification)
if notification.disabled?
content_tag :i, nil, class: 'fa fa-volume-off ns-mute'
icon('volume-off', class: 'ns-mute')
elsif notification.participating?
content_tag :i, nil, class: 'fa fa-volume-down ns-part'
icon('volume-down', class: 'ns-part')
elsif notification.watch?
content_tag :i, nil, class: 'fa fa-volume-up ns-watch'
icon('volume-up', class: 'ns-watch')
else
content_tag :i, nil, class: 'fa fa-circle-o ns-default'
icon('circle-o', class: 'ns-default')
end
end
end
......@@ -83,7 +83,7 @@ module ProjectsHelper
' Star'
end
content_tag('i', ' ', class: 'fa fa-star') + toggle_text
icon('star') + toggle_text
end
count_html = content_tag('span', class: 'count') do
......@@ -95,7 +95,7 @@ module ProjectsHelper
class: cls,
method: :post,
remote: true,
data: {type: 'json'}
data: { type: 'json' }
}
......@@ -107,7 +107,7 @@ module ProjectsHelper
end
def link_to_toggle_fork
out = content_tag(:i, '', class: 'fa fa-code-fork')
out = icon('code-fork')
out << ' Fork'
out << content_tag(:span, class: 'count') do
@project.forks_count.to_s
......@@ -262,4 +262,3 @@ module ProjectsHelper
end
end
end
......@@ -38,13 +38,8 @@ module TreeHelper
#
# type - String type of the tree item; either 'folder' or 'file'
def tree_icon(type)
icon_class = if type == 'folder'
'fa fa-folder'
else
'fa fa-file-o'
end
content_tag :i, nil, class: icon_class
icon_class = type == 'folder' ? 'folder' : 'file-o'
icon(icon_class)
end
def tree_hex_class(content)
......
......@@ -2,9 +2,12 @@ class Appearance < ActiveRecord::Base
validates :title, presence: true
validates :description, presence: true
validates :logo, file_size: { maximum: 1000.kilobytes.to_i }
validates :dark_logo, file_size: { maximum: 1000.kilobytes.to_i },
validates :dark_logo,
file_size: { maximum: 1000.kilobytes.to_i },
presence: true, if: :light_logo?
validates :light_logo, file_size: { maximum: 1000.kilobytes.to_i },
validates :light_logo,
file_size: { maximum: 1000.kilobytes.to_i },
presence: true, if: :dark_logo?
mount_uploader :logo, AttachmentUploader
......
......@@ -4,6 +4,7 @@
#
# id :integer not null, primary key
# default_projects_limit :integer
# default_branch_protection :integer
# signup_enabled :boolean
# signin_enabled :boolean
# gravatar_enabled :boolean
......@@ -14,7 +15,8 @@
#
class ApplicationSetting < ActiveRecord::Base
validates :home_page_url, allow_blank: true,
validates :home_page_url,
allow_blank: true,
format: { with: URI::regexp(%w(http https)), message: "should be a valid url" },
if: :home_page_url_column_exist
......@@ -25,6 +27,7 @@ class ApplicationSetting < ActiveRecord::Base
def self.create_from_defaults
create(
default_projects_limit: Settings.gitlab['default_projects_limit'],
default_branch_protection: Settings.gitlab['default_branch_protection'],
signup_enabled: Settings.gitlab['signup_enabled'],
signin_enabled: Settings.gitlab['signin_enabled'],
gravatar_enabled: Settings.gravatar['enabled'],
......
......@@ -88,7 +88,8 @@ class Commit
# cut off, ellipses (`&hellp;`) are prepended to the commit message.
def description
title_end = safe_message.index("\n")
@description ||= if (!title_end && safe_message.length > 100) || (title_end && title_end > 100)
@description ||=
if (!title_end && safe_message.length > 100) || (title_end && title_end > 100)
"&hellip;".html_safe << safe_message[80..-1]
else
safe_message.split("\n", 2)[1].try(:chomp)
......
......@@ -111,7 +111,7 @@ class Group < Namespace
class << self
def search(query)
where("LOWER(namespaces.name) LIKE :query", query: "%#{query.downcase}%")
where("LOWER(namespaces.name) LIKE :query or LOWER(namespaces.path) LIKE :query", query: "%#{query.downcase}%")
end
def sort(method)
......
......@@ -44,11 +44,11 @@ class WebHook < ActiveRecord::Base
}
WebHook.post(post_url,
body: data.to_json,
headers: {"Content-Type" => "application/json"},
headers: { "Content-Type" => "application/json" },
verify: false,
basic_auth: auth)
end
rescue SocketError, Errno::ECONNREFUSED, Net::OpenTimeout => e
rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e
logger.error("WebHook Error => #{e}")
false
end
......
......@@ -11,5 +11,5 @@
class Identity < ActiveRecord::Base
belongs_to :user
validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider }
end
......@@ -79,7 +79,7 @@ class MergeRequest < ActiveRecord::Base
merge_request.save
end
after_transition :locked => (any - :locked) do |merge_request, transition|
after_transition locked: (any - :locked) do |merge_request, transition|
merge_request.locked_at = nil
merge_request.save
end
......
......@@ -20,12 +20,17 @@ class Namespace < ActiveRecord::Base
belongs_to :owner, class_name: "User"
validates :owner, presence: true, unless: ->(n) { n.type == "Group" }
validates :name, presence: true, uniqueness: true,
validates :name,
presence: true, uniqueness: true,
length: { within: 0..255 },
format: { with: Gitlab::Regex.name_regex,
message: Gitlab::Regex.name_regex_message }
validates :description, length: { within: 0..255 }
validates :path, uniqueness: { case_sensitive: false }, presence: true, length: { within: 1..255 },
validates :path,
uniqueness: { case_sensitive: false },
presence: true,
length: { within: 1..255 },
exclusion: { in: Gitlab::Blacklist.path },
format: { with: Gitlab::Regex.path_regex,
message: Gitlab::Regex.path_regex_message }
......
......@@ -14,7 +14,7 @@
# merge_requests_enabled :boolean default(TRUE), not null
# wiki_enabled :boolean default(TRUE), not null
# namespace_id :integer
# issues_tracker :string(255) default('gitlab'), not null
# issues_tracker :string(255) default("gitlab"), not null
# issues_tracker_id :string(255)
# snippets_enabled :boolean default(TRUE), not null
# last_activity_at :datetime
......@@ -115,11 +115,14 @@ class Project < ActiveRecord::Base
# Validations
validates :creator, presence: true, on: :create
validates :description, length: { maximum: 2000 }, allow_blank: true
validates :name, presence: true, length: { within: 0..255 },
validates :name,
presence: true,
length: { within: 0..255 },
format: { with: Gitlab::Regex.project_name_regex,
message: Gitlab::Regex.project_regex_message }
validates :path, presence: true, length: { within: 0..255 },
exclusion: { in: Gitlab::Blacklist.path },
validates :path,
presence: true,
length: { within: 0..255 },
format: { with: Gitlab::Regex.path_regex,
message: Gitlab::Regex.path_regex_message }
validates :issues_enabled, :merge_requests_enabled,
......@@ -163,22 +166,22 @@ class Project < ActiveRecord::Base
end
event :import_finish do
transition :started => :finished
transition started: :finished
end
event :import_fail do
transition :started => :failed
transition started: :failed
end
event :import_retry do
transition :failed => :started
transition failed: :started
end
state :started
state :finished
state :failed
after_transition any => :started, :do => :add_import_job
after_transition any => :started, do: :add_import_job
end
class << self
......
class ProjectContributions
attr_reader :project, :user
def initialize(project, user)
@project, @user = project, user
end
def commits_log
repository = project.repository
if !repository.exists? || repository.empty?
return {}
end
Rails.cache.fetch(cache_key) do
repository.commits_per_day_for_user(user)
end
end
def cache_key
"#{Date.today.to_s}-commits-log-#{project.id}-#{user.email}"
end
end
......@@ -17,13 +17,19 @@ class BambooService < CiService
prop_accessor :bamboo_url, :build_key, :username, :password
validates :bamboo_url, presence: true,
format: { with: URI::regexp }, if: :activated?
validates :bamboo_url,
presence: true,
format: { with: URI::regexp },
if: :activated?
validates :build_key, presence: true, if: :activated?
validates :username, presence: true,
if: ->(service) { service.password? }, if: :activated?
validates :password, presence: true,
if: ->(service) { service.username? }, if: :activated?
validates :username,
presence: true,
if: ->(service) { service.password? },
if: :activated?
validates :password,
presence: true,
if: ->(service) { service.username? },
if: :activated?
attr_accessor :response
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
class CustomIssueTrackerService < IssueTrackerService
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
......@@ -27,8 +41,8 @@ class CustomIssueTrackerService < IssueTrackerService
{ type: 'text', name: 'title', placeholder: title },
{ type: 'text', name: 'description', placeholder: description },
{ type: 'text', name: 'project_url', placeholder: 'Project url' },
{ type: 'text', name: 'issues_url', placeholder: 'Issue url'},
{ type: 'text', name: 'new_issue_url', placeholder: 'New Issue url'}
{ type: 'text', name: 'issues_url', placeholder: 'Issue url' },
{ type: 'text', name: 'new_issue_url', placeholder: 'New Issue url' }
]
end
......
......@@ -81,7 +81,7 @@ class GitlabCiService < CiService
def fields
[
{ type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' },
{ type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3'}
{ type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3' }
]
end
end
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
class GitlabIssueTrackerService < IssueTrackerService
include Rails.application.routes.url_helpers
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
class IssueTrackerService < Service
validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated?
......@@ -30,8 +44,8 @@ class IssueTrackerService < Service
[
{ type: 'text', name: 'description', placeholder: description },
{ type: 'text', name: 'project_url', placeholder: 'Project url' },
{ type: 'text', name: 'issues_url', placeholder: 'Issue url'},
{ type: 'text', name: 'new_issue_url', placeholder: 'New Issue url'}
{ type: 'text', name: 'issues_url', placeholder: 'Issue url' },
{ type: 'text', name: 'new_issue_url', placeholder: 'New Issue url' }
]
end
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
class JiraService < IssueTrackerService
include HTTParty
......
# == Schema Information
#
# Table name: services
#
# id :integer not null, primary key
# type :string(255)
# title :string(255)
# project_id :integer not null
# created_at :datetime
# updated_at :datetime
# active :boolean default(FALSE), not null
# properties :text
#
class RedmineService < IssueTrackerService
prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url
......
......@@ -17,12 +17,15 @@ class TeamcityService < CiService
prop_accessor :teamcity_url, :build_type, :username, :password
validates :teamcity_url, presence: true,
validates :teamcity_url,
presence: true,
format: { with: URI::regexp }, if: :activated?
validates :build_type, presence: true, if: :activated?
validates :username, presence: true,
validates :username,
presence: true,
if: ->(service) { service.password? }, if: :activated?
validates :password, presence: true,
validates :password,
presence: true,
if: ->(service) { service.username? }, if: :activated?
attr_accessor :response
......
......@@ -136,7 +136,7 @@ class ProjectWiki
def commit_details(action, message = nil, title = nil)
commit_message = message || default_message(action, title)
{email: @user.email, name: @user.name, message: commit_message}
{ email: @user.email, name: @user.name, message: commit_message }
end
def default_message(action, title)
......
......@@ -30,7 +30,7 @@ class Repository
commit = Gitlab::Git::Commit.find(raw_repository, id)
commit = Commit.new(commit) if commit
commit
rescue Rugged::OdbError => ex
rescue Rugged::OdbError
nil
end
......@@ -61,25 +61,25 @@ class Repository
end
def add_branch(branch_name, ref)
Rails.cache.delete(cache_key(:branch_names))
cache.expire(:branch_names)
gitlab_shell.add_branch(path_with_namespace, branch_name, ref)
end
def add_tag(tag_name, ref, message = nil)
Rails.cache.delete(cache_key(:tag_names))
cache.expire(:tag_names)
gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message)
end
def rm_branch(branch_name)
Rails.cache.delete(cache_key(:branch_names))
cache.expire(:branch_names)
gitlab_shell.rm_branch(path_with_namespace, branch_name)
end
def rm_tag(tag_name)
Rails.cache.delete(cache_key(:tag_names))
cache.expire(:tag_names)
gitlab_shell.rm_tag(path_with_namespace, tag_name)
end
......@@ -97,19 +97,15 @@ class Repository
end
def branch_names
Rails.cache.fetch(cache_key(:branch_names)) do
raw_repository.branch_names
end
cache.fetch(:branch_names) { raw_repository.branch_names }
end
def tag_names
Rails.cache.fetch(cache_key(:tag_names)) do
raw_repository.tag_names
end
cache.fetch(:tag_names) { raw_repository.tag_names }
end
def commit_count
Rails.cache.fetch(cache_key(:commit_count)) do
cache.fetch(:commit_count) do
begin
raw_repository.commit_count(self.root_ref)
rescue
......@@ -121,26 +117,19 @@ class Repository
# Return repo size in megabytes
# Cached in redis
def size
Rails.cache.fetch(cache_key(:size)) do
raw_repository.size
end
cache.fetch(:size) { raw_repository.size }
end
def expire_cache
Rails.cache.delete(cache_key(:size))
Rails.cache.delete(cache_key(:branch_names))
Rails.cache.delete(cache_key(:tag_names))
Rails.cache.delete(cache_key(:commit_count))
Rails.cache.delete(cache_key(:graph_log))
Rails.cache.delete(cache_key(:readme))
Rails.cache.delete(cache_key(:version))
Rails.cache.delete(cache_key(:contribution_guide))
%i(size branch_names tag_names commit_count graph_log
readme version contribution_guide).each do |key|
cache.expire(key)
end
end
def graph_log
Rails.cache.fetch(cache_key(:graph_log)) do
commits = raw_repository.log(limit: 6000,
skip_merges: true,
cache.fetch(:graph_log) do
commits = raw_repository.log(limit: 6000, skip_merges: true,
ref: root_ref)
commits.map do |rugged_commit|
......@@ -176,10 +165,6 @@ class Repository
end
end
def cache_key(type)
"#{type}:#{path_with_namespace}"
end
def method_missing(m, *args, &block)
raw_repository.send(m, *args, &block)
end
......@@ -199,13 +184,11 @@ class Repository
end
def readme
Rails.cache.fetch(cache_key(:readme)) do
tree(:head).readme
end
cache.fetch(:readme) { tree(:head).readme }
end
def version
Rails.cache.fetch(cache_key(:version)) do
cache.fetch(:version) do
tree(:head).blobs.find do |file|
file.name.downcase == 'version'
end
......@@ -213,9 +196,7 @@ class Repository
end
def contribution_guide
Rails.cache.fetch(cache_key(:contribution_guide)) do
tree(:head).contribution_guide
end
cache.fetch(:contribution_guide) { tree(:head).contribution_guide }
end
def head_commit
......@@ -351,4 +332,10 @@ class Repository
[]
end
end
private
def cache
@cache ||= RepositoryCache.new(path_with_namespace)
end
end
......@@ -29,7 +29,9 @@ class Snippet < ActiveRecord::Base
validates :author, presence: true
validates :title, presence: true, length: { within: 0..255 }
validates :file_name, presence: true, length: { within: 0..255 },
validates :file_name,
presence: true,
length: { within: 0..255 },
format: { with: Gitlab::Regex.path_regex,
message: Gitlab::Regex.path_regex_message }
validates :content, presence: true
......
......@@ -113,10 +113,12 @@ class User < ActiveRecord::Base
# Validations
#
validates :name, presence: true
validates :email, presence: true, email: {strict_mode: true}, uniqueness: true
validates :email, presence: true, email: { strict_mode: true }, uniqueness: true
validates :bio, length: { maximum: 255 }, allow_blank: true
validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0}
validates :username, presence: true, uniqueness: { case_sensitive: false },
validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :username,
presence: true,
uniqueness: { case_sensitive: false },
exclusion: { in: Gitlab::Blacklist.path },
format: { with: Gitlab::Regex.username_regex,
message: Gitlab::Regex.username_regex_message }
......
......@@ -43,7 +43,7 @@ class WikiPage
@attributes[:slug]
end
alias :to_param :slug
alias_method :to_param, :slug
# The formatted title of this page.
def title
......
class GitPushService
attr_accessor :project, :user, :push_data, :push_commits
include Gitlab::CurrentSettings
include Gitlab::Access
# This method will be called after each git update
# and only if the provided user and project is present in GitLab.
......@@ -29,8 +31,12 @@ class GitPushService
if is_default_branch?(ref)
# Initial push to the default branch. Take the full history of that branch as "newly pushed".
@push_commits = project.repository.commits(newrev)
# Default branch is protected by default
project.protected_branches.create({ name: project.default_branch })
# Set protection on the default branch if configured
if (current_application_settings.default_branch_protection != PROTECTION_NONE)
developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false
project.protected_branches.create({ name: project.default_branch, developers_can_push: developers_can_push })
end
else
# Use the pushed commits that aren't reachable by the default branch
# as a heuristic. This may include more commits than are actually pushed, but
......
......@@ -5,7 +5,8 @@ module Projects
end
def execute(note_type, note_id)
participating = if note_type && note_id
participating =
if note_type && note_id
participants_in(note_type, note_id)
else
[]
......
......@@ -25,6 +25,10 @@
= f.label :default_projects_limit, class: 'control-label'
.col-sm-10
= f.number_field :default_projects_limit, class: 'form-control'
.form-group
= f.label :default_branch_protection, class: 'control-label'
.col-sm-10
= f.select :default_branch_protection, options_for_select(Gitlab::Access.protection_options, @application_setting.default_branch_protection), {}, class: 'form-control'
.form-group
= f.label :home_page_url, class: 'control-label'
.col-sm-10
......
......@@ -91,7 +91,7 @@
%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 into group', class: "btn btn-create"
= button_tag 'Add users to group', class: "btn btn-create"
.panel.panel-default
.panel-heading
%h3.panel-title
......
......@@ -10,7 +10,8 @@
- groups.each do |group|
%li.group-row
= link_to group_path(id: group.path), class: dom_class(group) do
= image_tag group_icon(group.path), class: "avatar s24"
.dash-project-avatar
= image_tag group_icon(group.path), class: "avatar s40"
%span.group-name.filter-title
= truncate(group.name, length: 35)
%span.arrow
......
= link_to project_path(project), class: dom_class(project) do
.dash-project-avatar
= project_icon(project.to_param, alt: '', class: 'avatar s40')
.dash-project-access-icon
= visibility_level_icon(project.visibility_level)
.dash-project-avatar
= project_icon(project.to_param, alt: '', class: 'avatar s24')
%span.str-truncated
%span.namespace-name
- if project.namespace
......
......@@ -12,4 +12,4 @@
%strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink"
.form-actions
= f.submit 'Add users into group', class: "btn btn-create"
= f.submit 'Add users to group', class: "btn btn-create"
......@@ -12,10 +12,10 @@
- projects.each do |project|
%li.project-row
= link_to project_path(project), class: dom_class(project) do
.dash-project-avatar
= project_icon(project.to_param, alt: '', class: 'avatar s40')
.dash-project-access-icon
= visibility_level_icon(project.visibility_level)
.dash-project-avatar
= project_icon(project.to_param, alt: '', class: 'avatar s24')
%span.str-truncated
%span.project-name
= project.name
......
.dashboard
%div
= image_tag group_icon(@group.path), class: "avatar s90"
= image_tag group_icon(@group.path), class: "avatar avatar-tile s90"
.clearfix
%h2
= @group.name
......
- if nav_menu_collapsed?
= link_to icon('angle-right'), '#', class: 'toggle-nav-collapse'
- else
= link_to icon('angle-left'), '#', class: 'toggle-nav-collapse'
- if defined?(sidebar)
.page-with-sidebar
.page-with-sidebar{ class: nav_sidebar_class }
= render "layouts/broadcast"
.sidebar-wrapper
= render(sidebar)
.collapse-nav
= render partial: 'layouts/collapse_button'
.content-wrapper
.container-fluid
.content
......
......@@ -30,5 +30,5 @@
%code
:erb
<% lines.each do |line| %>
<%= highlight(@blob.name, line, true).html_safe %>
<%= highlight(@blob.name, line.force_encoding("utf-8"), true).html_safe %>
<% end %>
......@@ -37,23 +37,8 @@
- @commit.parents.each do |parent|
= link_to parent.short_id, project_commit_path(@project, parent)
.commit-info-row
- if @branches.any?
%span
- branch = commit_default_branch(@project, @branches)
= link_to(project_tree_path(@project, branch)) do
%span.label.label-gray
%i.fa.fa-code-fork
= branch
- if @branches.any? || @tags.any?
= link_to("#", class: "js-details-expand") do
%span.label.label-gray
\...
%span.js-details-content.hide
- if @branches.any?
= commit_branches_links(@project, @branches)
- if @tags.any?
= commit_tags_links(@project, @tags)
.commit-info-row.branches
%i.fa.fa-spinner.fa-spin
.commit-box
%h3.commit-title
......@@ -61,3 +46,7 @@
- if @commit.description.present?
%pre.commit-description
= preserve(gfm(escape_once(@commit.description)))
:coffeescript
$ ->
$(".commit-info-row.branches").load("#{branches_project_commit_path(@project, @commit.id)}")
\ No newline at end of file
- if @branches.any?
%span
- branch = commit_default_branch(@project, @branches)
= link_to(project_tree_path(@project, branch)) do
%span.label.label-gray
%i.fa.fa-code-fork
= branch
- if @branches.any? || @tags.any?
= link_to("#", class: "js-details-expand") do
%span.label.label-gray
\...
%span.js-details-content.hide
- if @branches.any?
= commit_branches_links(@project, @branches)
- if @tags.any?
= commit_tags_links(@project, @tags)
\ No newline at end of file
......@@ -3,8 +3,7 @@
.project-edit-content
%div
%h3.page-title
Project settings:
%p.light Some settings, such as "Transfer Project", are hidden inside the danger area below.
Project settings
%hr
.panel-body
= form_for @project, remote: true, html: { multipart: true, class: "edit_project form-horizontal" }, authenticity_token: true do |f|
......
......@@ -10,7 +10,7 @@
You can
= link_to project_new_blob_path(@project, 'master'), class: 'btn btn-new btn-lg' do
add a file
&nbsp;or push it via command line.
&nbsp;or do a push via the command line.
%h4
%strong Command line instructions
......
......@@ -13,7 +13,7 @@
= link_to_member(@project, participant, name: false, size: 24)
.voting_notes#notes= render "projects/notes/notes_with_form"
.col-md-3.hidden-sm.hidden-xs
.col-md-3
%div
.clearfix
%span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
......
......@@ -2,23 +2,21 @@
%div.prepend-top-20
%p
Assignee:
- if can?(current_user, :modify_issue, @issue)
= project_users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @issue.assignee_id)
- elsif issue.assignee
- if issue.assignee
= link_to_member(@project, @issue.assignee)
- else
None
none
- if can?(current_user, :modify_issue, @issue)
= project_users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @issue.assignee_id)
%div.prepend-top-20
%p
Milestone:
- if issue.milestone
#{link_to @issue.milestone.title, project_milestone_path(@project, @issue.milestone)}
- else
none
- if can?(current_user, :modify_issue, @issue)
= f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'})
= hidden_field_tag :issue_context
= f.submit class: 'btn'
- elsif issue.milestone
= link_to project_milestone_path(@project, @issue.milestone) do
= @issue.milestone.title
- else
None
......@@ -3,8 +3,15 @@
:plain
$("##{dom_id(@issue)}").fadeOut();
- elsif params[:issue_context]
$('.context').html("#{escape_javascript(render partial: 'issue_context', locals: { issue: @issue })}");
$('.context').effect('highlight');
- if @issue.milestone
$('.milestone-nav-link').replaceWith("<span class='milestone-nav-link'>| <span class='light'>Milestone</span> #{escape_javascript(link_to @issue.milestone.title, project_milestone_path(@issue.project, @issue.milestone))}</span>")
- else
$('.milestone-nav-link').html('')
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
$('.edit-issue.inline-update input[type="submit"]').hide();
new ProjectUsersSelect();
new Issue();
......@@ -9,7 +9,7 @@
.col-md-9
= render "projects/merge_requests/show/participants"
= render "projects/notes/notes_with_form"
.col-md-3.hidden-sm.hidden-xs
.col-md-3
.clearfix
%span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'}
= cross_project_reference(@project, @merge_request)
......@@ -18,7 +18,7 @@
%cite.cgray
= render partial: 'projects/merge_requests/show/context', locals: { merge_request: @merge_request }
%hr
.votes-holder.hidden-sm.hidden-xs
.votes-holder
%h6 Votes
#votes= render 'votes/votes_block', votable: @merge_request
......
......@@ -2,22 +2,22 @@
%div.prepend-top-20
%p
Assignee:
- if can?(current_user, :modify_merge_request, @merge_request)
= project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @merge_request.assignee_id)
- elsif merge_request.assignee
- if @merge_request.assignee
= link_to_member(@project, @merge_request.assignee)
- else
None
none
- if can?(current_user, :modify_merge_request, @merge_request)
= project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @merge_request.assignee_id)
%div.prepend-top-20
%p
Milestone:
- if @merge_request.milestone
%span.back-to-milestone
#{link_to @merge_request.milestone.title, project_milestone_path(@project, @merge_request.milestone)}
- else
none
- if can?(current_user, :modify_merge_request, @merge_request)
= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'})
= hidden_field_tag :merge_request_context
= f.submit class: 'btn'
- elsif merge_request.milestone
= link_to merge_request.milestone.title, project_milestone_path
- else
None
- if params[:merge_request_context]
$('.context').html("#{escape_javascript(render partial: 'projects/merge_requests/show/context', locals: { issue: @issue })}");
$('.context').effect('highlight');
new ProjectUsersSelect();
$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true});
merge_request = new MergeRequest();
......@@ -19,8 +19,10 @@
%td.new_line= "..."
%td.line_content.matched= line.text
- else
%td.old_line= raw(line.type == "new" ? "&nbsp;" : line.old_pos)
%td.new_line= raw(line.type == "old" ? "&nbsp;" : line.new_pos)
%td.old_line{class: line.type == "new" ? "new" : "old"}
= raw(line.type == "new" ? "&nbsp;" : line.old_pos)
%td.new_line{class: line.type == "new" ? "new" : "old"}
= raw(line.type == "old" ? "&nbsp;" : line.new_pos)
%td.line_content{class: "noteable_line #{line.type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text)
- if line_code == note.line_code
......
.clearfix
- groups.each do |group|
= link_to group, class: 'profile-groups-avatars', title: group.name do
= image_tag group_icon(group.path), class: 'avatar avatar-inline s40'
= link_to group, class: 'profile-groups-avatars inline', title: group.name do
= image_tag group_icon(group.path), class: 'avatar avatar-tile s40'
%h4 Calendar
#cal-heatmap.calendar
:javascript
new calendar(
......
.row
.col-md-8
%h3.page-title
= image_tag avatar_icon(@user.email, 90), class: "avatar s90", alt: ''
= image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: ''
= @user.name
- if @user == current_user
.pull-right
......@@ -15,15 +15,16 @@
.clearfix
- if @groups.any?
%h4 Groups:
%h4 Groups
= render 'groups', groups: @groups
%hr
%h4 Calendar:
%div= render 'calendar'
.user-calendar
%h4.center.light
%i.fa.fa-spinner.fa-spin
%hr
%h4
User Activity:
User Activity
- if current_user
%span.rss-icon.pull-right
......@@ -36,3 +37,8 @@
= render 'profile', user: @user
- if @projects.present?
= render 'projects', projects: @projects
:coffeescript
$ ->
$(".user-calendar").load("#{user_calendar_path}")
......@@ -70,7 +70,10 @@ module Gitlab
config.middleware.use Rack::Cors do
allow do
origins '*'
resource '/api/*', headers: :any, methods: [:get, :post, :options, :put, :delete]
resource '/api/*',
headers: :any,
methods: [:get, :post, :options, :put, :delete],
expose: ['Link']
end
end
......
......@@ -12,10 +12,14 @@ class Settings < Settingslogic
def build_gitlab_shell_ssh_path_prefix
if gitlab_shell.ssh_port != 22
"ssh://#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:#{gitlab_shell.ssh_port}/"
else
if gitlab_shell.ssh_host.include? ':'
"[#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}]:"
else
"#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:"
end
end
end
def build_gitlab_url
custom_port = gitlab_on_standard_port? ? nil : ":#{gitlab.port}"
......@@ -94,6 +98,7 @@ Settings['issues_tracker'] ||= {}
#
Settings['gitlab'] ||= Settingslogic.new({})
Settings.gitlab['default_projects_limit'] ||= 10
Settings.gitlab['default_branch_protection'] ||= 2
Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil?
Settings.gitlab['default_theme'] = Gitlab::Theme::MARS if Settings.gitlab['default_theme'].nil?
Settings.gitlab['host'] ||= 'localhost'
......@@ -155,7 +160,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['upload'] ||= Settingslogic.new({'remote_directory' => nil, 'connection' => nil})
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']
Settings.backup['upload']['connection'] = Hash[Settings.backup['upload']['connection'].map { |k, v| [k.to_sym, v] }]
......
......@@ -42,7 +42,8 @@ module ActsAsTaggableOn::Taggable
elsif options.delete(:any)
# get tags, drop out if nothing returned (we need at least one)
tags = if options.delete(:wild)
tags =
if options.delete(:wild)
ActsAsTaggableOn::Tag.named_like_any(tag_list)
else
ActsAsTaggableOn::Tag.named_any(tag_list)
......
......@@ -12,22 +12,30 @@ if File.exists?(aws_file)
aws_secret_access_key: AWS_CONFIG['secret_access_key'], # required
region: AWS_CONFIG['region'], # optional, defaults to 'us-east-1'
}
config.fog_directory = AWS_CONFIG['bucket'] # required
config.fog_public = false # optional, defaults to true
config.fog_attributes = {'Cache-Control'=>'max-age=315576000'} # optional, defaults to {}
config.fog_authenticated_url_expiration = 1 << 29 # optional time (in seconds) that authenticated urls will be valid.
# required
config.fog_directory = AWS_CONFIG['bucket']
# optional, defaults to true
config.fog_public = false
# optional, defaults to {}
config.fog_attributes = { 'Cache-Control'=>'max-age=315576000' }
# optional time (in seconds) that authenticated urls will be valid.
# when fog_public is false and provider is AWS or Google, defaults to 600
config.fog_authenticated_url_expiration = 1 << 29
end
# Mocking Fog requests, based on: https://github.com/carrierwaveuploader/carrierwave/wiki/How-to%3A-Test-Fog-based-uploaders
if Rails.env.test?
Fog.mock!
connection = ::Fog::Storage.new(
:aws_access_key_id => AWS_CONFIG['access_key_id'],
:aws_secret_access_key => AWS_CONFIG['secret_access_key'],
:provider => 'AWS',
:region => AWS_CONFIG['region']
aws_access_key_id: AWS_CONFIG['access_key_id'],
aws_secret_access_key: AWS_CONFIG['secret_access_key'],
provider: 'AWS',
region: AWS_CONFIG['region']
)
connection.directories.create(:key => AWS_CONFIG['bucket'])
connection.directories.create(key: AWS_CONFIG['bucket'])
end
end
......@@ -43,10 +43,10 @@ Doorkeeper.configure do
force_ssl_in_redirect_uri false
# Provide support for an owner to be assigned to each registered application (disabled by default)
# Optional parameter :confirmation => true (default false) if you want to enforce ownership of
# Optional parameter confirmation: true (default false) if you want to enforce ownership of
# a registered application
# Note: you must also run the rails g doorkeeper:application_owner generator to provide the necessary support
enable_application_owner :confirmation => false
enable_application_owner confirmation: false
# Define access token scopes for your provider
# For more information go to
......
......@@ -3,9 +3,9 @@ require 'api/api'
Gitlab::Application.routes.draw do
use_doorkeeper do
controllers :applications => 'oauth/applications',
:authorized_applications => 'oauth/authorized_applications',
:authorizations => 'oauth/authorizations'
controllers applications: 'oauth/applications',
authorized_applications: 'oauth/authorized_applications',
authorizations: 'oauth/authorizations'
end
#
# Search
......@@ -167,10 +167,9 @@ Gitlab::Application.routes.draw do
end
end
# route for commits used by the cal-heatmap
get 'u/:username/activities' => 'users#activities', as: :user_activities,
constraints: { username: /(?:[^.]|\.(?!atom$))+/, format: /atom/ },
via: :get
get 'u/:username/calendar' => 'users#calendar', as: :user_calendar,
constraints: { username: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
get '/u/:username' => 'users#show', as: :user,
constraints: { username: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
......@@ -188,7 +187,7 @@ Gitlab::Application.routes.draw do
#
# Groups Area
#
resources :groups, constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} do
resources :groups, constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } do
member do
get :issues
get :merge_requests
......@@ -239,40 +238,44 @@ Gitlab::Application.routes.draw do
scope module: :projects do
# Blob routes:
get '/new/:id', to: 'blob#new', constraints: {id: /.+/}, as: 'new_blob'
post '/create/:id', to: 'blob#create', constraints: {id: /.+/}, as: 'create_blob'
get '/edit/:id', to: 'blob#edit', constraints: {id: /.+/}, as: 'edit_blob'
put '/update/:id', to: 'blob#update', constraints: {id: /.+/}, as: 'update_blob'
post '/preview/:id', to: 'blob#preview', constraints: {id: /.+/}, as: 'preview_blob'
get '/new/:id', to: 'blob#new', constraints: { id: /.+/ }, as: 'new_blob'
post '/create/:id', to: 'blob#create', constraints: { id: /.+/ }, as: 'create_blob'
get '/edit/:id', to: 'blob#edit', constraints: { id: /.+/ }, as: 'edit_blob'
put '/update/:id', to: 'blob#update', constraints: { id: /.+/ }, as: 'update_blob'
post '/preview/:id', to: 'blob#preview', constraints: { id: /.+/ }, as: 'preview_blob'
resources :blob, only: [:show, :destroy], constraints: { id: /.+/, format: false } do
get :diff, on: :member
end
resources :raw, only: [:show], constraints: {id: /.+/}
resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ }
resources :raw, only: [:show], constraints: { id: /.+/ }
resources :tree, only: [:show], constraints: { id: /.+/, format: /(html|js)/ }
resource :avatar, only: [:show, :destroy]
resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/}
resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
resources :commit, only: [:show], constraints: { id: /[[:alnum:]]{6,40}/ } do
get :branches, on: :member
end
resources :commits, only: [:show], constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
resources :compare, only: [:index, :create]
resources :blame, only: [:show], constraints: {id: /.+/}
resources :network, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/}
resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/} do
resources :blame, only: [:show], constraints: { id: /.+/ }
resources :network, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ }
resources :graphs, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } do
member do
get :commits
end
end
get '/compare/:from...:to' => 'compare#show', :as => 'compare',
:constraints => {from: /.+/, to: /.+/}
:constraints => { from: /.+/, to: /.+/ }
resources :snippets, constraints: {id: /\d+/} do
resources :snippets, constraints: { id: /\d+/ } do
member do
get 'raw'
end
end
resources :wikis, only: [:show, :edit, :destroy, :create], constraints: {id: /[a-zA-Z.0-9_\-\/]+/} do
resources :wikis, only: [:show, :edit, :destroy, :create], constraints: { id: /[a-zA-Z.0-9_\-\/]+/ } do
collection do
get :pages
put ':id' => 'wikis#update'
......@@ -299,7 +302,7 @@ Gitlab::Application.routes.draw do
end
end
resources :deploy_keys, constraints: {id: /\d+/} do
resources :deploy_keys, constraints: { id: /\d+/ } do
member do
put :enable
put :disable
......@@ -318,16 +321,14 @@ Gitlab::Application.routes.draw do
member do
# tree viewer logs
get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex }
get 'logs_tree/:path' => 'refs#logs_tree',
as: :logs_file,
constraints: {
get 'logs_tree/:path' => 'refs#logs_tree', as: :logs_file, constraints: {
id: Gitlab::Regex.git_reference_regex,
path: /.*/
}
end
end
resources :merge_requests, constraints: {id: /\d+/}, except: [:destroy] do
resources :merge_requests, constraints: { id: /\d+/ }, except: [:destroy] do
member do
get :diffs
post :automerge
......@@ -342,29 +343,28 @@ Gitlab::Application.routes.draw do
end
end
resources :git_hooks, constraints: {id: /\d+/}
resources :hooks, only: [:index, :create, :destroy], constraints: {id: /\d+/} do
resources :git_hooks, constraints: { id: /\d+/ }
resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do
member do
get :test
end
end
resources :team, controller: 'team_members', only: [:index]
resources :milestones, except: [:destroy], constraints: {id: /\d+/} do
resources :milestones, except: [:destroy], constraints: { id: /\d+/ } do
member do
put :sort_issues
put :sort_merge_requests
end
end
resources :labels, constraints: {id: /\d+/} do
resources :labels, constraints: { id: /\d+/ } do
collection do
post :generate
end
end
resources :issues, constraints: {id: /\d+/}, except: [:destroy] do
resources :issues, constraints: { id: /\d+/ }, except: [:destroy] do
collection do
post :bulk_update
end
......@@ -381,9 +381,9 @@ Gitlab::Application.routes.draw do
end
end
resources :group_links, only: [:index, :create, :destroy], constraints: {id: /\d+/}
resources :group_links, only: [:index, :create, :destroy], constraints: { id: /\d+/ }
resources :notes, only: [:index, :create, :destroy, :update], constraints: {id: /\d+/} do
resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do
member do
delete :delete_attachment
end
......@@ -394,7 +394,7 @@ Gitlab::Application.routes.draw do
get "/audit_events" => "audit_events#project_log"
end
get ':id' => 'namespaces#show', constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/}
get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }
root to: 'dashboard#show'
end
class AddDefaultBranchProtectionSetting < ActiveRecord::Migration
def change
add_column :application_settings, :default_branch_protection, :integer, :default => 2
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20150116234545) do
ActiveRecord::Schema.define(version: 20150125163100) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -27,6 +27,18 @@ ActiveRecord::Schema.define(version: 20150116234545) do
t.string "light_logo"
end
create_table "application_settings", force: true do |t|
t.integer "default_projects_limit"
t.boolean "signup_enabled"
t.boolean "signin_enabled"
t.boolean "gravatar_enabled"
t.text "sign_in_text"
t.datetime "created_at"
t.datetime "updated_at"
t.string "home_page_url"
t.integer "default_branch_protection", default: 2
end
create_table "audit_events", force: true do |t|
t.integer "author_id", null: false
t.string "type", null: false
......@@ -41,17 +53,6 @@ ActiveRecord::Schema.define(version: 20150116234545) do
add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree
add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree
create_table "application_settings", force: true do |t|
t.integer "default_projects_limit"
t.boolean "signup_enabled"
t.boolean "signin_enabled"
t.boolean "gravatar_enabled"
t.text "sign_in_text"
t.datetime "created_at"
t.datetime "updated_at"
t.string "home_page_url"
end
create_table "broadcast_messages", force: true do |t|
t.text "message", null: false
t.datetime "starts_at"
......@@ -378,14 +379,14 @@ ActiveRecord::Schema.define(version: 20150116234545) do
t.string "import_url"
t.integer "visibility_level", default: 0, null: false
t.boolean "archived", default: false, null: false
t.string "avatar"
t.string "import_status"
t.string "import_type"
t.string "import_source"
t.float "repository_size", default: 0.0
t.integer "star_count", default: 0, null: false
t.text "merge_requests_template"
t.integer "star_count", default: 0, null: false
t.boolean "merge_requests_rebase_enabled", default: false
t.string "avatar"
t.string "import_type"
t.string "import_source"
end
add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree
......@@ -483,6 +484,7 @@ ActiveRecord::Schema.define(version: 20150116234545) do
t.integer "notification_level", default: 1, null: false
t.datetime "password_expires_at"
t.integer "created_by_id"
t.datetime "last_credential_check_at"
t.string "avatar"
t.string "confirmation_token"
t.datetime "confirmed_at"
......@@ -490,9 +492,8 @@ ActiveRecord::Schema.define(version: 20150116234545) do
t.string "unconfirmed_email"
t.boolean "hide_no_ssh_key", default: false
t.string "website_url", default: "", null: false
t.datetime "last_credential_check_at"
t.string "github_access_token"
t.datetime "admin_email_unsubscribed_at"
t.string "github_access_token"
t.string "gitlab_access_token"
end
......
......@@ -97,7 +97,7 @@ Return values:
## Sudo
All API requests support performing an api call as if you were another user, if your private token is for an administration account. You need to pass `sudo` parameter by url or header with an id or username of the user you want to perform the operation as. If passed as header, the header name must be "SUDO" (capitals).
All API requests support performing an api call as if you were another user, if your private token is for an administration account. You need to pass `sudo` parameter by URL or header with an id or username of the user you want to perform the operation as. If passed as header, the header name must be "SUDO" (capitals).
If a non administrative `private_token` is provided then an error message will be returned with status code 403:
......@@ -142,7 +142,7 @@ When listing resources you can pass the following parameters:
- `page` (default: `1`) - page number
- `per_page` (default: `20`, max: `100`) - number of items to list per page
[Link headers](http://www.w3.org/wiki/LinkHeader) are send back with each response. These have `rel` prev/next/first/last and contain the relevant URL. Please use these instead of generating your own urls.
[Link headers](http://www.w3.org/wiki/LinkHeader) are send back with each response. These have `rel` prev/next/first/last and contain the relevant URL. Please use these instead of generating your own URLs.
## id vs iid
......
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
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