Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Tatuya Kamada
gitlab-ce
Commits
7649497f
Commit
7649497f
authored
Aug 30, 2016
by
Stan Hu
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'master' of gitlab.com:gitlab-org/gitlab-ce
parents
341541d3
4d042afe
Changes
35
Show whitespace changes
Inline
Side-by-side
Showing
35 changed files
with
354 additions
and
118 deletions
+354
-118
CHANGELOG
CHANGELOG
+4
-0
app/assets/javascripts/boards/boards_bundle.js.es6
app/assets/javascripts/boards/boards_bundle.js.es6
+7
-0
app/assets/javascripts/boards/components/board.js.es6
app/assets/javascripts/boards/components/board.js.es6
+0
-15
app/assets/javascripts/boards/models/list.js.es6
app/assets/javascripts/boards/models/list.js.es6
+0
-4
app/assets/javascripts/boards/stores/boards_store.js.es6
app/assets/javascripts/boards/stores/boards_store.js.es6
+2
-1
app/assets/stylesheets/pages/boards.scss
app/assets/stylesheets/pages/boards.scss
+10
-35
app/assets/stylesheets/pages/pipelines.scss
app/assets/stylesheets/pages/pipelines.scss
+7
-0
app/assets/stylesheets/pages/projects.scss
app/assets/stylesheets/pages/projects.scss
+8
-0
app/controllers/concerns/toggle_award_emoji.rb
app/controllers/concerns/toggle_award_emoji.rb
+7
-3
app/controllers/projects_controller.rb
app/controllers/projects_controller.rb
+1
-1
app/helpers/lfs_helper.rb
app/helpers/lfs_helper.rb
+4
-0
app/helpers/nav_helper.rb
app/helpers/nav_helper.rb
+2
-0
app/helpers/projects_helper.rb
app/helpers/projects_helper.rb
+12
-0
app/models/concerns/awardable.rb
app/models/concerns/awardable.rb
+12
-0
app/models/concerns/issuable.rb
app/models/concerns/issuable.rb
+4
-0
app/models/note.rb
app/models/note.rb
+4
-0
app/models/project.rb
app/models/project.rb
+7
-0
app/views/admin/projects/show.html.haml
app/views/admin/projects/show.html.haml
+6
-0
app/views/projects/boards/components/_board.html.haml
app/views/projects/boards/components/_board.html.haml
+0
-5
app/views/projects/edit.html.haml
app/views/projects/edit.html.haml
+10
-0
app/views/shared/icons/_icon_status_created.svg
app/views/shared/icons/_icon_status_created.svg
+1
-1
app/views/shared/issuable/_filter.html.haml
app/views/shared/issuable/_filter.html.haml
+12
-9
db/migrate/20160823213309_add_lfs_enabled_to_projects.rb
db/migrate/20160823213309_add_lfs_enabled_to_projects.rb
+29
-0
db/migrate/20160830232601_change_lock_version_not_null.rb
db/migrate/20160830232601_change_lock_version_not_null.rb
+13
-0
db/schema.rb
db/schema.rb
+2
-1
doc/api/projects.md
doc/api/projects.md
+3
-0
doc/workflow/project_features.md
doc/workflow/project_features.md
+9
-1
lib/api/award_emoji.rb
lib/api/award_emoji.rb
+5
-1
lib/api/entities.rb
lib/api/entities.rb
+1
-1
lib/api/projects.rb
lib/api/projects.rb
+9
-3
spec/features/boards/boards_spec.rb
spec/features/boards/boards_spec.rb
+39
-26
spec/features/issues/award_emoji_spec.rb
spec/features/issues/award_emoji_spec.rb
+0
-1
spec/javascripts/boards/list_spec.js.es6
spec/javascripts/boards/list_spec.js.es6
+0
-9
spec/requests/api/award_emoji_spec.rb
spec/requests/api/award_emoji_spec.rb
+17
-1
spec/requests/lfs_http_spec.rb
spec/requests/lfs_http_spec.rb
+107
-0
No files found.
CHANGELOG
View file @
7649497f
...
@@ -21,10 +21,12 @@ v 8.12.0 (unreleased)
...
@@ -21,10 +21,12 @@ v 8.12.0 (unreleased)
- Add Sentry logging to API calls
- Add Sentry logging to API calls
- Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
- Automatically expand hidden discussions when accessed by a permalink !5585 (Mike Greiling)
- Remove unused mixins (ClemMakesApps)
- Remove unused mixins (ClemMakesApps)
- Add search to all issue board lists
- Fix groups sort dropdown alignment (ClemMakesApps)
- Fix groups sort dropdown alignment (ClemMakesApps)
- Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps)
- Add horizontal scrolling to all sub-navs on mobile viewports (ClemMakesApps)
- Fix markdown help references (ClemMakesApps)
- Fix markdown help references (ClemMakesApps)
- Add last commit time to repo view (ClemMakesApps)
- Add last commit time to repo view (ClemMakesApps)
- Added project specific enable/disable setting for LFS !5997
- Added tests for diff notes
- Added tests for diff notes
- Add a button to download latest successful artifacts for branches and tags !5142
- Add a button to download latest successful artifacts for branches and tags !5142
- Add delimiter to project stars and forks count (ClemMakesApps)
- Add delimiter to project stars and forks count (ClemMakesApps)
...
@@ -78,6 +80,8 @@ v 8.11.2
...
@@ -78,6 +80,8 @@ v 8.11.2
v 8.11.1
v 8.11.1
- Pulled due to packaging error.
- Pulled due to packaging error.
v 8.11.0 (unreleased)
- Fix pipelines tab layout regression (brycepj)
v 8.11.0
v 8.11.0
- Use test coverage value from the latest successful pipeline in badge. !5862
- Use test coverage value from the latest successful pipeline in badge. !5862
- Add test coverage report badge. !5708
- Add test coverage report badge. !5708
...
...
app/assets/javascripts/boards/boards_bundle.js.es6
View file @
7649497f
...
@@ -54,4 +54,11 @@ $(() => {
...
@@ -54,4 +54,11 @@ $(() => {
});
});
}
}
});
});
gl.IssueBoardsSearch = new Vue({
el: '#js-boards-seach',
data: {
filters: Store.state.filters
}
});
});
});
app/assets/javascripts/boards/components/board.js.es6
View file @
7649497f
...
@@ -21,15 +21,10 @@
...
@@ -21,15 +21,10 @@
},
},
data () {
data () {
return {
return {
query: '',
filters: Store.state.filters
filters: Store.state.filters
};
};
},
},
watch: {
watch: {
query () {
this.list.filters = this.getFilterData();
this.list.getIssues(true);
},
filters: {
filters: {
handler () {
handler () {
this.list.page = 1;
this.list.page = 1;
...
@@ -38,16 +33,6 @@
...
@@ -38,16 +33,6 @@
deep: true
deep: true
}
}
},
},
methods: {
getFilterData () {
const filters = this.filters;
let queryData = { search: this.query };
Object.keys(filters).forEach((key) => { queryData[key] = filters[key]; });
return queryData;
}
},
ready () {
ready () {
const options = gl.issueBoards.getBoardSortableDefaultOptions({
const options = gl.issueBoards.getBoardSortableDefaultOptions({
disabled: this.disabled,
disabled: this.disabled,
...
...
app/assets/javascripts/boards/models/list.js.es6
View file @
7649497f
...
@@ -58,10 +58,6 @@ class List {
...
@@ -58,10 +58,6 @@ class List {
}
}
}
}
canSearch () {
return this.type === 'backlog';
}
getIssues (emptyIssues = true) {
getIssues (emptyIssues = true) {
const filters = this.filters;
const filters = this.filters;
let data = { page: this.page };
let data = { page: this.page };
...
...
app/assets/javascripts/boards/stores/boards_store.js.es6
View file @
7649497f
...
@@ -15,7 +15,8 @@
...
@@ -15,7 +15,8 @@
author_id: gl.utils.getParameterValues('author_id')[0],
author_id: gl.utils.getParameterValues('author_id')[0],
assignee_id: gl.utils.getParameterValues('assignee_id')[0],
assignee_id: gl.utils.getParameterValues('assignee_id')[0],
milestone_title: gl.utils.getParameterValues('milestone_title')[0],
milestone_title: gl.utils.getParameterValues('milestone_title')[0],
label_name: gl.utils.getParameterValues('label_name[]')
label_name: gl.utils.getParameterValues('label_name[]'),
search: ''
};
};
},
},
addList (listObj) {
addList (listObj) {
...
...
app/assets/stylesheets/pages/boards.scss
View file @
7649497f
...
@@ -160,40 +160,6 @@
...
@@ -160,40 +160,6 @@
border-bottom
:
1px
solid
$border-color
;
border-bottom
:
1px
solid
$border-color
;
}
}
.board-search-container
{
position
:
relative
;
background-color
:
#fff
;
.form-control
{
padding-right
:
30px
;
}
}
.board-search-icon
,
.board-search-clear-btn
{
position
:
absolute
;
right
:
$gl-padding
+
10px
;
top
:
50%
;
margin-top
:
-7px
;
font-size
:
14px
;
}
.board-search-icon
{
color
:
$gl-placeholder-color
;
}
.board-search-clear-btn
{
padding
:
0
;
line-height
:
1
;
background
:
transparent
;
border
:
0
;
outline
:
0
;
&
:hover
{
color
:
$gl-link-color
;
}
}
.board-delete
{
.board-delete
{
margin-right
:
10px
;
margin-right
:
10px
;
padding
:
0
;
padding
:
0
;
...
@@ -304,3 +270,12 @@
...
@@ -304,3 +270,12 @@
margin-right
:
8px
;
margin-right
:
8px
;
font-weight
:
500
;
font-weight
:
500
;
}
}
.issue-boards-search
{
width
:
335px
;
.form-control
{
display
:
inline-block
;
width
:
210px
;
}
}
app/assets/stylesheets/pages/pipelines.scss
View file @
7649497f
...
@@ -477,3 +477,10 @@
...
@@ -477,3 +477,10 @@
width
:
60px
;
width
:
60px
;
}
}
}
}
.ci-status-icon-created
{
svg
{
fill
:
$table-text-gray
;
}
}
app/assets/stylesheets/pages/projects.scss
View file @
7649497f
...
@@ -311,6 +311,14 @@ a.deploy-project-label {
...
@@ -311,6 +311,14 @@ a.deploy-project-label {
color
:
$gl-success
;
color
:
$gl-success
;
}
}
.lfs-enabled
{
color
:
$gl-success
;
}
.lfs-disabled
{
color
:
$gl-warning
;
}
.breadcrumb.repo-breadcrumb
{
.breadcrumb.repo-breadcrumb
{
padding
:
0
;
padding
:
0
;
background
:
transparent
;
background
:
transparent
;
...
...
app/controllers/concerns/toggle_award_emoji.rb
View file @
7649497f
...
@@ -8,10 +8,14 @@ module ToggleAwardEmoji
...
@@ -8,10 +8,14 @@ module ToggleAwardEmoji
def
toggle_award_emoji
def
toggle_award_emoji
name
=
params
.
require
(
:name
)
name
=
params
.
require
(
:name
)
if
awardable
.
user_can_award?
(
current_user
,
name
)
awardable
.
toggle_award_emoji
(
name
,
current_user
)
awardable
.
toggle_award_emoji
(
name
,
current_user
)
TodoService
.
new
.
new_award_emoji
(
to_todoable
(
awardable
),
current_user
)
TodoService
.
new
.
new_award_emoji
(
to_todoable
(
awardable
),
current_user
)
render
json:
{
ok:
true
}
render
json:
{
ok:
true
}
else
render
json:
{
ok:
false
}
end
end
end
private
private
...
...
app/controllers/projects_controller.rb
View file @
7649497f
...
@@ -309,7 +309,7 @@ class ProjectsController < Projects::ApplicationController
...
@@ -309,7 +309,7 @@ class ProjectsController < Projects::ApplicationController
:issues_tracker_id
,
:default_branch
,
:issues_tracker_id
,
:default_branch
,
:wiki_enabled
,
:visibility_level
,
:import_url
,
:last_activity_at
,
:namespace_id
,
:avatar
,
:wiki_enabled
,
:visibility_level
,
:import_url
,
:last_activity_at
,
:namespace_id
,
:avatar
,
:builds_enabled
,
:build_allow_git_fetch
,
:build_timeout_in_minutes
,
:build_coverage_regex
,
:builds_enabled
,
:build_allow_git_fetch
,
:build_timeout_in_minutes
,
:build_coverage_regex
,
:public_builds
,
:only_allow_merge_if_build_succeeds
,
:request_access_enabled
:public_builds
,
:only_allow_merge_if_build_succeeds
,
:request_access_enabled
,
:lfs_enabled
)
)
end
end
...
...
app/helpers/lfs_helper.rb
View file @
7649497f
...
@@ -23,10 +23,14 @@ module LfsHelper
...
@@ -23,10 +23,14 @@ module LfsHelper
end
end
def
lfs_download_access?
def
lfs_download_access?
return
false
unless
project
.
lfs_enabled?
project
.
public?
||
ci?
||
(
user
&&
user
.
can?
(
:download_code
,
project
))
project
.
public?
||
ci?
||
(
user
&&
user
.
can?
(
:download_code
,
project
))
end
end
def
lfs_upload_access?
def
lfs_upload_access?
return
false
unless
project
.
lfs_enabled?
user
&&
user
.
can?
(
:push_code
,
project
)
user
&&
user
.
can?
(
:push_code
,
project
)
end
end
...
...
app/helpers/nav_helper.rb
View file @
7649497f
...
@@ -25,6 +25,8 @@ module NavHelper
...
@@ -25,6 +25,8 @@ module NavHelper
current_path?
(
'merge_requests#commits'
)
||
current_path?
(
'merge_requests#commits'
)
||
current_path?
(
'merge_requests#builds'
)
||
current_path?
(
'merge_requests#builds'
)
||
current_path?
(
'merge_requests#conflicts'
)
||
current_path?
(
'merge_requests#conflicts'
)
||
current_path?
(
'merge_requests#pipelines'
)
||
current_path?
(
'issues#show'
)
current_path?
(
'issues#show'
)
if
cookies
[
:collapsed_gutter
]
==
'true'
if
cookies
[
:collapsed_gutter
]
==
'true'
"page-gutter right-sidebar-collapsed"
"page-gutter right-sidebar-collapsed"
...
...
app/helpers/projects_helper.rb
View file @
7649497f
...
@@ -187,6 +187,18 @@ module ProjectsHelper
...
@@ -187,6 +187,18 @@ module ProjectsHelper
nav_tabs
.
flatten
nav_tabs
.
flatten
end
end
def
project_lfs_status
(
project
)
if
project
.
lfs_enabled?
content_tag
(
:span
,
class:
'lfs-enabled'
)
do
'Enabled'
end
else
content_tag
(
:span
,
class:
'lfs-disabled'
)
do
'Disabled'
end
end
end
def
git_user_name
def
git_user_name
if
current_user
if
current_user
current_user
.
name
current_user
.
name
...
...
app/models/concerns/awardable.rb
View file @
7649497f
...
@@ -59,6 +59,18 @@ module Awardable
...
@@ -59,6 +59,18 @@ module Awardable
true
true
end
end
def
awardable_votes?
(
name
)
AwardEmoji
::
UPVOTE_NAME
==
name
||
AwardEmoji
::
DOWNVOTE_NAME
==
name
end
def
user_can_award?
(
current_user
,
name
)
if
user_authored?
(
current_user
)
!
awardable_votes?
(
normalize_name
(
name
))
else
true
end
end
def
awarded_emoji?
(
emoji_name
,
current_user
)
def
awarded_emoji?
(
emoji_name
,
current_user
)
award_emoji
.
where
(
name:
emoji_name
,
user:
current_user
).
exists?
award_emoji
.
where
(
name:
emoji_name
,
user:
current_user
).
exists?
end
end
...
...
app/models/concerns/issuable.rb
View file @
7649497f
...
@@ -196,6 +196,10 @@ module Issuable
...
@@ -196,6 +196,10 @@ module Issuable
end
end
end
end
def
user_authored?
(
user
)
user
==
author
end
def
subscribed_without_subscriptions?
(
user
)
def
subscribed_without_subscriptions?
(
user
)
participants
(
user
).
include?
(
user
)
participants
(
user
).
include?
(
user
)
end
end
...
...
app/models/note.rb
View file @
7649497f
...
@@ -223,6 +223,10 @@ class Note < ActiveRecord::Base
...
@@ -223,6 +223,10 @@ class Note < ActiveRecord::Base
end
end
end
end
def
user_authored?
(
user
)
user
==
author
end
def
award_emoji?
def
award_emoji?
can_be_award_emoji?
&&
contains_emoji_only?
can_be_award_emoji?
&&
contains_emoji_only?
end
end
...
...
app/models/project.rb
View file @
7649497f
...
@@ -390,6 +390,13 @@ class Project < ActiveRecord::Base
...
@@ -390,6 +390,13 @@ class Project < ActiveRecord::Base
end
end
end
end
def
lfs_enabled?
return
false
unless
Gitlab
.
config
.
lfs
.
enabled
return
Gitlab
.
config
.
lfs
.
enabled
if
self
[
:lfs_enabled
].
nil?
self
[
:lfs_enabled
]
end
def
repository_storage_path
def
repository_storage_path
Gitlab
.
config
.
repositories
.
storages
[
repository_storage
]
Gitlab
.
config
.
repositories
.
storages
[
repository_storage
]
end
end
...
...
app/views/admin/projects/show.html.haml
View file @
7649497f
...
@@ -73,6 +73,12 @@
...
@@ -73,6 +73,12 @@
%span
.light
last commit:
%span
.light
last commit:
%strong
%strong
=
last_commit
(
@project
)
=
last_commit
(
@project
)
%li
%span
.light
Git LFS status:
%strong
=
project_lfs_status
(
@project
)
=
link_to
icon
(
'question-circle'
),
help_page_path
(
'workflow/lfs/manage_large_binaries_with_git_lfs'
)
-
else
-
else
%li
%li
%span
.light
repository:
%span
.light
repository:
...
...
app/views/projects/boards/components/_board.html.haml
View file @
7649497f
...
@@ -21,11 +21,6 @@
...
@@ -21,11 +21,6 @@
%button
.board-delete.has-tooltip.pull-right
{
type:
"button"
,
title:
"Delete list"
,
"aria-label"
=>
"Delete list"
,
data:
{
placement:
"bottom"
},
"@click.stop"
=>
"deleteBoard"
}
%button
.board-delete.has-tooltip.pull-right
{
type:
"button"
,
title:
"Delete list"
,
"aria-label"
=>
"Delete list"
,
data:
{
placement:
"bottom"
},
"@click.stop"
=>
"deleteBoard"
}
=
icon
(
"trash"
)
=
icon
(
"trash"
)
=
icon
(
"spinner spin"
,
class:
"board-header-loading-spinner pull-right"
,
"v-show"
=>
"list.loadingMore"
)
=
icon
(
"spinner spin"
,
class:
"board-header-loading-spinner pull-right"
,
"v-show"
=>
"list.loadingMore"
)
.board-inner-container.board-search-container
{
"v-if"
=>
"list.canSearch()"
}
%input
.form-control
{
type:
"text"
,
placeholder:
"Search issues"
,
"v-model"
=>
"query"
,
"debounce"
=>
"250"
}
=
icon
(
"search"
,
class:
"board-search-icon"
,
"v-show"
=>
"!query"
)
%button
.board-search-clear-btn
{
type:
"button"
,
role:
"button"
,
"aria-label"
=>
"Clear search"
,
"@click"
=>
"query = ''"
,
"v-show"
=>
"query"
}
=
icon
(
"times"
,
class:
"board-search-clear"
)
%board-list
{
"inline-template"
=>
true
,
%board-list
{
"inline-template"
=>
true
,
"v-if"
=>
"list.type !== 'blank'"
,
"v-if"
=>
"list.type !== 'blank'"
,
":list"
=>
"list"
,
":list"
=>
"list"
,
...
...
app/views/projects/edit.html.haml
View file @
7649497f
...
@@ -80,6 +80,16 @@
...
@@ -80,6 +80,16 @@
%strong
Snippets
%strong
Snippets
%br
%br
%span
.descr
Share code pastes with others out of git repository
%span
.descr
Share code pastes with others out of git repository
-
if
Gitlab
.
config
.
lfs
.
enabled
&&
current_user
.
admin?
.form-group
.checkbox
=
f
.
label
:lfs_enabled
do
=
f
.
check_box
:lfs_enabled
,
checked:
@project
.
lfs_enabled?
%strong
LFS
%br
%span
.descr
Git Large File Storage
=
link_to
icon
(
'question-circle'
),
help_page_path
(
'workflow/lfs/manage_large_binaries_with_git_lfs'
)
-
if
Gitlab
.
config
.
registry
.
enabled
-
if
Gitlab
.
config
.
registry
.
enabled
.form-group
.form-group
.checkbox
.checkbox
...
...
app/views/shared/icons/_icon_status_created.svg
View file @
7649497f
<svg
xmlns=
"http://www.w3.org/2000/svg"
viewBox=
"0 0 14 14"
enable-background=
"new 0 0 14 14"
><path
d=
"M12.5,7 C12.5,4 10,1.5 7,1.5 C4,1.5 1.5,4 1.5,7 C1.5,10 4,12.5 7,12.5 C10,12.5 12.5,10 12.5,7 L12.5,7 Z M0,7 C0,3.1 3.1,0 7,0 C10.9,0 14,3.1 14,7 C14,10.9 10.9,14 7,14 C3.1,14 0,10.9 0,7 L0,7 Z"
/><circle
cx=
"7"
cy=
"7"
r=
"3.25"
/></svg>
<svg
xmlns=
"http://www.w3.org/2000/svg"
width=
"14"
height=
"14"
viewBox=
"0 0 14 14"
enable-background=
"new 0 0 14 14"
><path
d=
"M12.5,7 C12.5,4 10,1.5 7,1.5 C4,1.5 1.5,4 1.5,7 C1.5,10 4,12.5 7,12.5 C10,12.5 12.5,10 12.5,7 L12.5,7 Z M0,7 C0,3.1 3.1,0 7,0 C10.9,0 14,3.1 14,7 C14,10.9 10.9,14 7,14 C3.1,14 0,10.9 0,7 L0,7 Z"
/><circle
cx=
"7"
cy=
"7"
r=
"3.25"
/></svg>
app/views/shared/issuable/_filter.html.haml
View file @
7649497f
...
@@ -27,8 +27,11 @@
...
@@ -27,8 +27,11 @@
=
render
"shared/issuable/label_dropdown"
=
render
"shared/issuable/label_dropdown"
.pull-right
.pull-right
-
if
controller
.
controller_name
==
'boards'
&&
can?
(
current_user
,
:admin_list
,
@project
)
-
if
controller
.
controller_name
==
'boards'
.dropdown
#js-boards-seach
.issue-boards-search
%input
.pull-left.form-control
{
type:
"search"
,
placeholder:
"Filter by name..."
,
"v-model"
=>
"filters.search"
,
"debounce"
=>
"250"
}
-
if
can?
(
current_user
,
:admin_list
,
@project
)
.dropdown.pull-right
%button
.btn.btn-create.js-new-board-list
{
type:
"button"
,
data:
{
toggle:
"dropdown"
,
labels:
labels_filter_path
,
project_id:
@project
.
try
(
:id
)
}
}
%button
.btn.btn-create.js-new-board-list
{
type:
"button"
,
data:
{
toggle:
"dropdown"
,
labels:
labels_filter_path
,
project_id:
@project
.
try
(
:id
)
}
}
Create new list
Create new list
.dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
.dropdown-menu.dropdown-menu-paging.dropdown-menu-align-right.dropdown-menu-issues-board-new.dropdown-menu-selectable
...
...
db/migrate/20160823213309_add_lfs_enabled_to_projects.rb
0 → 100644
View file @
7649497f
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class
AddLfsEnabledToProjects
<
ActiveRecord
::
Migration
include
Gitlab
::
Database
::
MigrationHelpers
# Set this constant to true if this migration requires downtime.
DOWNTIME
=
false
# When a migration requires downtime you **must** uncomment the following
# constant and define a short and easy to understand explanation as to why the
# migration requires downtime.
# DOWNTIME_REASON = ''
# When using the methods "add_concurrent_index" or "add_column_with_default"
# you must disable the use of transactions as these methods can not run in an
# existing transaction. When using "add_concurrent_index" make sure that this
# method is the _only_ method called in the migration, any other changes
# should go in a separate migration. This ensures that upon failure _only_ the
# index creation fails and can be retried or reverted easily.
#
# To disable transactions uncomment the following line and remove these
# comments:
# disable_ddl_transaction!
def
change
add_column
:projects
,
:lfs_enabled
,
:boolean
end
end
db/migrate/20160830232601_change_lock_version_not_null.rb
0 → 100644
View file @
7649497f
class
ChangeLockVersionNotNull
<
ActiveRecord
::
Migration
include
Gitlab
::
Database
::
MigrationHelpers
DOWNTIME
=
false
def
up
change_column_null
:issues
,
:lock_version
,
true
change_column_null
:merge_requests
,
:lock_version
,
true
end
def
down
end
end
db/schema.rb
View file @
7649497f
...
@@ -11,7 +11,7 @@
...
@@ -11,7 +11,7 @@
#
#
# It's strongly recommended that you check this file into your version control system.
# It's strongly recommended that you check this file into your version control system.
ActiveRecord
::
Schema
.
define
(
version:
201608
27011312
)
do
ActiveRecord
::
Schema
.
define
(
version:
201608
30232601
)
do
# These are extensions that must be enabled in order to support this database
# These are extensions that must be enabled in order to support this database
enable_extension
"plpgsql"
enable_extension
"plpgsql"
...
@@ -825,6 +825,7 @@ ActiveRecord::Schema.define(version: 20160827011312) do
...
@@ -825,6 +825,7 @@ ActiveRecord::Schema.define(version: 20160827011312) do
t
.
string
"repository_storage"
,
default:
"default"
,
null:
false
t
.
string
"repository_storage"
,
default:
"default"
,
null:
false
t
.
boolean
"request_access_enabled"
,
default:
true
,
null:
false
t
.
boolean
"request_access_enabled"
,
default:
true
,
null:
false
t
.
boolean
"has_external_wiki"
t
.
boolean
"has_external_wiki"
t
.
boolean
"lfs_enabled"
end
end
add_index
"projects"
,
[
"ci_id"
],
name:
"index_projects_on_ci_id"
,
using: :btree
add_index
"projects"
,
[
"ci_id"
],
name:
"index_projects_on_ci_id"
,
using: :btree
...
...
doc/api/projects.md
View file @
7649497f
...
@@ -452,6 +452,7 @@ Parameters:
...
@@ -452,6 +452,7 @@ Parameters:
-
`import_url`
(optional)
-
`import_url`
(optional)
-
`public_builds`
(optional)
-
`public_builds`
(optional)
-
`only_allow_merge_if_build_succeeds`
(optional)
-
`only_allow_merge_if_build_succeeds`
(optional)
-
`lfs_enabled`
(optional)
### Create project for user
### Create project for user
...
@@ -478,6 +479,7 @@ Parameters:
...
@@ -478,6 +479,7 @@ Parameters:
-
`import_url`
(optional)
-
`import_url`
(optional)
-
`public_builds`
(optional)
-
`public_builds`
(optional)
-
`only_allow_merge_if_build_succeeds`
(optional)
-
`only_allow_merge_if_build_succeeds`
(optional)
-
`lfs_enabled`
(optional)
### Edit project
### Edit project
...
@@ -505,6 +507,7 @@ Parameters:
...
@@ -505,6 +507,7 @@ Parameters:
-
`visibility_level`
(optional)
-
`visibility_level`
(optional)
-
`public_builds`
(optional)
-
`public_builds`
(optional)
-
`only_allow_merge_if_build_succeeds`
(optional)
-
`only_allow_merge_if_build_succeeds`
(optional)
-
`lfs_enabled`
(optional)
On success, method returns 200 with the updated project. If parameters are
On success, method returns 200 with the updated project. If parameters are
invalid, 400 is returned.
invalid, 400 is returned.
...
...
doc/workflow/project_features.md
View file @
7649497f
...
@@ -32,4 +32,12 @@ Snippets are little bits of code or text.
...
@@ -32,4 +32,12 @@ Snippets are little bits of code or text.
This is a nice place to put code or text that is used semi-regularly within the project, but does not belong in source control.
This is a nice place to put code or text that is used semi-regularly within the project, but does not belong in source control.
For example, a specific config file that is used by > the team that is only valid for the people that work on the code.
For example, a specific config file that is used by the team that is only valid for the people that work on the code.
## Git LFS
>**Note:** Project-specific LFS setting was added on 8.12 and is available only to admins.
Git Large File Storage allows you to easily manage large binary files with Git.
With this setting admins can better control which projects are allowed to use
LFS.
lib/api/award_emoji.rb
View file @
7649497f
...
@@ -54,7 +54,7 @@ module API
...
@@ -54,7 +54,7 @@ module API
post
endpoint
do
post
endpoint
do
required_attributes!
[
:name
]
required_attributes!
[
:name
]
not_found!
(
'Award Emoji'
)
unless
can_read_awardable?
not_found!
(
'Award Emoji'
)
unless
can_read_awardable?
&&
can_award_awardable?
award
=
awardable
.
create_award_emoji
(
params
[
:name
],
current_user
)
award
=
awardable
.
create_award_emoji
(
params
[
:name
],
current_user
)
...
@@ -92,6 +92,10 @@ module API
...
@@ -92,6 +92,10 @@ module API
can?
(
current_user
,
ability
,
awardable
)
can?
(
current_user
,
ability
,
awardable
)
end
end
def
can_award_awardable?
awardable
.
user_can_award?
(
current_user
,
params
[
:name
])
end
def
awardable
def
awardable
@awardable
||=
@awardable
||=
begin
begin
...
...
lib/api/entities.rb
View file @
7649497f
...
@@ -78,7 +78,7 @@ module API
...
@@ -78,7 +78,7 @@ module API
expose
:path
,
:path_with_namespace
expose
:path
,
:path_with_namespace
expose
:issues_enabled
,
:merge_requests_enabled
,
:wiki_enabled
,
:builds_enabled
,
:snippets_enabled
,
:container_registry_enabled
expose
:issues_enabled
,
:merge_requests_enabled
,
:wiki_enabled
,
:builds_enabled
,
:snippets_enabled
,
:container_registry_enabled
expose
:created_at
,
:last_activity_at
expose
:created_at
,
:last_activity_at
expose
:shared_runners_enabled
expose
:shared_runners_enabled
,
:lfs_enabled
expose
:creator_id
expose
:creator_id
expose
:namespace
expose
:namespace
expose
:forked_from_project
,
using:
Entities
::
BasicProjectDetails
,
if:
lambda
{
|
project
,
options
|
project
.
forked?
}
expose
:forked_from_project
,
using:
Entities
::
BasicProjectDetails
,
if:
lambda
{
|
project
,
options
|
project
.
forked?
}
...
...
lib/api/projects.rb
View file @
7649497f
...
@@ -105,6 +105,7 @@ module API
...
@@ -105,6 +105,7 @@ module API
# visibility_level (optional) - 0 by default
# visibility_level (optional) - 0 by default
# import_url (optional)
# import_url (optional)
# public_builds (optional)
# public_builds (optional)
# lfs_enabled (optional)
# Example Request
# Example Request
# POST /projects
# POST /projects
post
do
post
do
...
@@ -124,7 +125,8 @@ module API
...
@@ -124,7 +125,8 @@ module API
:visibility_level
,
:visibility_level
,
:import_url
,
:import_url
,
:public_builds
,
:public_builds
,
:only_allow_merge_if_build_succeeds
]
:only_allow_merge_if_build_succeeds
,
:lfs_enabled
]
attrs
=
map_public_to_visibility_level
(
attrs
)
attrs
=
map_public_to_visibility_level
(
attrs
)
@project
=
::
Projects
::
CreateService
.
new
(
current_user
,
attrs
).
execute
@project
=
::
Projects
::
CreateService
.
new
(
current_user
,
attrs
).
execute
if
@project
.
saved?
if
@project
.
saved?
...
@@ -156,6 +158,7 @@ module API
...
@@ -156,6 +158,7 @@ module API
# visibility_level (optional)
# visibility_level (optional)
# import_url (optional)
# import_url (optional)
# public_builds (optional)
# public_builds (optional)
# lfs_enabled (optional)
# Example Request
# Example Request
# POST /projects/user/:user_id
# POST /projects/user/:user_id
post
"user/:user_id"
do
post
"user/:user_id"
do
...
@@ -174,7 +177,8 @@ module API
...
@@ -174,7 +177,8 @@ module API
:visibility_level
,
:visibility_level
,
:import_url
,
:import_url
,
:public_builds
,
:public_builds
,
:only_allow_merge_if_build_succeeds
]
:only_allow_merge_if_build_succeeds
,
:lfs_enabled
]
attrs
=
map_public_to_visibility_level
(
attrs
)
attrs
=
map_public_to_visibility_level
(
attrs
)
@project
=
::
Projects
::
CreateService
.
new
(
user
,
attrs
).
execute
@project
=
::
Projects
::
CreateService
.
new
(
user
,
attrs
).
execute
if
@project
.
saved?
if
@project
.
saved?
...
@@ -220,6 +224,7 @@ module API
...
@@ -220,6 +224,7 @@ module API
# public (optional) - if true same as setting visibility_level = 20
# public (optional) - if true same as setting visibility_level = 20
# visibility_level (optional) - visibility level of a project
# visibility_level (optional) - visibility level of a project
# public_builds (optional)
# public_builds (optional)
# lfs_enabled (optional)
# Example Request
# Example Request
# PUT /projects/:id
# PUT /projects/:id
put
':id'
do
put
':id'
do
...
@@ -237,7 +242,8 @@ module API
...
@@ -237,7 +242,8 @@ module API
:public
,
:public
,
:visibility_level
,
:visibility_level
,
:public_builds
,
:public_builds
,
:only_allow_merge_if_build_succeeds
]
:only_allow_merge_if_build_succeeds
,
:lfs_enabled
]
attrs
=
map_public_to_visibility_level
(
attrs
)
attrs
=
map_public_to_visibility_level
(
attrs
)
authorize_admin_project
authorize_admin_project
authorize!
:rename_project
,
user_project
if
attrs
[
:name
].
present?
authorize!
:rename_project
,
user_project
if
attrs
[
:name
].
present?
...
...
spec/features/boards/boards_spec.rb
View file @
7649497f
...
@@ -110,6 +110,45 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -110,6 +110,45 @@ describe 'Issue Boards', feature: true, js: true do
end
end
end
end
it
'search backlog list'
do
page
.
within
(
'#js-boards-seach'
)
do
find
(
'.form-control'
).
set
(
issue1
.
title
)
end
wait_for_vue_resource
expect
(
find
(
'.board:nth-child(1)'
)).
to
have_selector
(
'.card'
,
count:
1
)
expect
(
find
(
'.board:nth-child(2)'
)).
to
have_selector
(
'.card'
,
count:
0
)
expect
(
find
(
'.board:nth-child(3)'
)).
to
have_selector
(
'.card'
,
count:
0
)
expect
(
find
(
'.board:nth-child(4)'
)).
to
have_selector
(
'.card'
,
count:
0
)
end
it
'search done list'
do
page
.
within
(
'#js-boards-seach'
)
do
find
(
'.form-control'
).
set
(
issue8
.
title
)
end
wait_for_vue_resource
expect
(
find
(
'.board:nth-child(1)'
)).
to
have_selector
(
'.card'
,
count:
0
)
expect
(
find
(
'.board:nth-child(2)'
)).
to
have_selector
(
'.card'
,
count:
0
)
expect
(
find
(
'.board:nth-child(3)'
)).
to
have_selector
(
'.card'
,
count:
0
)
expect
(
find
(
'.board:nth-child(4)'
)).
to
have_selector
(
'.card'
,
count:
1
)
end
it
'search list'
do
page
.
within
(
'#js-boards-seach'
)
do
find
(
'.form-control'
).
set
(
issue5
.
title
)
end
wait_for_vue_resource
expect
(
find
(
'.board:nth-child(1)'
)).
to
have_selector
(
'.card'
,
count:
0
)
expect
(
find
(
'.board:nth-child(2)'
)).
to
have_selector
(
'.card'
,
count:
1
)
expect
(
find
(
'.board:nth-child(3)'
)).
to
have_selector
(
'.card'
,
count:
0
)
expect
(
find
(
'.board:nth-child(4)'
)).
to
have_selector
(
'.card'
,
count:
0
)
end
it
'allows user to delete board'
do
it
'allows user to delete board'
do
page
.
within
(
find
(
'.board:nth-child(2)'
))
do
page
.
within
(
find
(
'.board:nth-child(2)'
))
do
find
(
'.board-delete'
).
click
find
(
'.board-delete'
).
click
...
@@ -162,32 +201,6 @@ describe 'Issue Boards', feature: true, js: true do
...
@@ -162,32 +201,6 @@ describe 'Issue Boards', feature: true, js: true do
end
end
end
end
it
'is searchable'
do
page
.
within
(
find
(
'.board'
,
match: :first
))
do
find
(
'.form-control'
).
set
issue1
.
title
wait_for_vue_resource
(
spinner:
false
)
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
end
end
it
'clears search'
do
page
.
within
(
find
(
'.board'
,
match: :first
))
do
find
(
'.form-control'
).
set
issue1
.
title
expect
(
page
).
to
have_selector
(
'.card'
,
count:
1
)
find
(
'.board-search-clear-btn'
).
click
end
wait_for_vue_resource
page
.
within
(
find
(
'.board'
,
match: :first
))
do
expect
(
page
).
to
have_selector
(
'.card'
,
count:
6
)
end
end
it
'moves issue from backlog into list'
do
it
'moves issue from backlog into list'
do
drag_to
(
list_to_index:
1
)
drag_to
(
list_to_index:
1
)
...
...
spec/features/issues/award_emoji_spec.rb
View file @
7649497f
...
@@ -12,7 +12,6 @@ describe 'Awards Emoji', feature: true do
...
@@ -12,7 +12,6 @@ describe 'Awards Emoji', feature: true do
describe
'Click award emoji from issue#show'
do
describe
'Click award emoji from issue#show'
do
let!
(
:issue
)
do
let!
(
:issue
)
do
create
(
:issue
,
create
(
:issue
,
author:
@user
,
assignee:
@user
,
assignee:
@user
,
project:
project
)
project:
project
)
end
end
...
...
spec/javascripts/boards/list_spec.js.es6
View file @
7649497f
...
@@ -60,15 +60,6 @@ describe('List model', () => {
...
@@ -60,15 +60,6 @@ describe('List model', () => {
}, 0);
}, 0);
});
});
it('can\'t search when not backlog', () => {
expect(list.canSearch()).toBe(false);
});
it('can search when backlog', () => {
list.type = 'backlog';
expect(list.canSearch()).toBe(true);
});
it('gets issue from list', (done) => {
it('gets issue from list', (done) => {
setTimeout(() => {
setTimeout(() => {
const issue = list.findIssue(1);
const issue = list.findIssue(1);
...
...
spec/requests/api/award_emoji_spec.rb
View file @
7649497f
...
@@ -4,7 +4,7 @@ describe API::API, api: true do
...
@@ -4,7 +4,7 @@ describe API::API, api: true do
include
ApiHelpers
include
ApiHelpers
let
(
:user
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
let!
(
:project
)
{
create
(
:project
)
}
let!
(
:project
)
{
create
(
:project
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
,
author:
user
)
}
let
(
:issue
)
{
create
(
:issue
,
project:
project
)
}
let!
(
:award_emoji
)
{
create
(
:award_emoji
,
awardable:
issue
,
user:
user
)
}
let!
(
:award_emoji
)
{
create
(
:award_emoji
,
awardable:
issue
,
user:
user
)
}
let!
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
,
target_project:
project
)
}
let!
(
:merge_request
)
{
create
(
:merge_request
,
source_project:
project
,
target_project:
project
)
}
let!
(
:downvote
)
{
create
(
:award_emoji
,
:downvote
,
awardable:
merge_request
,
user:
user
)
}
let!
(
:downvote
)
{
create
(
:award_emoji
,
:downvote
,
awardable:
merge_request
,
user:
user
)
}
...
@@ -115,6 +115,8 @@ describe API::API, api: true do
...
@@ -115,6 +115,8 @@ describe API::API, api: true do
end
end
describe
"POST /projects/:id/awardable/:awardable_id/award_emoji"
do
describe
"POST /projects/:id/awardable/:awardable_id/award_emoji"
do
let
(
:issue2
)
{
create
(
:issue
,
project:
project
,
author:
user
)
}
context
"on an issue"
do
context
"on an issue"
do
it
"creates a new award emoji"
do
it
"creates a new award emoji"
do
post
api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue
.
id
}
/award_emoji"
,
user
),
name:
'blowfish'
post
api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue
.
id
}
/award_emoji"
,
user
),
name:
'blowfish'
...
@@ -136,6 +138,12 @@ describe API::API, api: true do
...
@@ -136,6 +138,12 @@ describe API::API, api: true do
expect
(
response
).
to
have_http_status
(
401
)
expect
(
response
).
to
have_http_status
(
401
)
end
end
it
"returns a 404 error if the user authored issue"
do
post
api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue2
.
id
}
/award_emoji"
,
user
),
name:
'thumbsup'
expect
(
response
).
to
have_http_status
(
404
)
end
it
"normalizes +1 as thumbsup award"
do
it
"normalizes +1 as thumbsup award"
do
post
api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue
.
id
}
/award_emoji"
,
user
),
name:
'+1'
post
api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue
.
id
}
/award_emoji"
,
user
),
name:
'+1'
...
@@ -155,6 +163,8 @@ describe API::API, api: true do
...
@@ -155,6 +163,8 @@ describe API::API, api: true do
end
end
describe
"POST /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji"
do
describe
"POST /projects/:id/awardable/:awardable_id/notes/:note_id/award_emoji"
do
let
(
:note2
)
{
create
(
:note
,
project:
project
,
noteable:
issue
,
author:
user
)
}
it
'creates a new award emoji'
do
it
'creates a new award emoji'
do
expect
do
expect
do
post
api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue
.
id
}
/notes/
#{
note
.
id
}
/award_emoji"
,
user
),
name:
'rocket'
post
api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue
.
id
}
/notes/
#{
note
.
id
}
/award_emoji"
,
user
),
name:
'rocket'
...
@@ -164,6 +174,12 @@ describe API::API, api: true do
...
@@ -164,6 +174,12 @@ describe API::API, api: true do
expect
(
json_response
[
'user'
][
'username'
]).
to
eq
(
user
.
username
)
expect
(
json_response
[
'user'
][
'username'
]).
to
eq
(
user
.
username
)
end
end
it
"it returns 404 error when user authored note"
do
post
api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue
.
id
}
/notes/
#{
note2
.
id
}
/award_emoji"
,
user
),
name:
'thumbsup'
expect
(
response
).
to
have_http_status
(
404
)
end
it
"normalizes +1 as thumbsup award"
do
it
"normalizes +1 as thumbsup award"
do
post
api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue
.
id
}
/notes/
#{
note
.
id
}
/award_emoji"
,
user
),
name:
'+1'
post
api
(
"/projects/
#{
project
.
id
}
/issues/
#{
issue
.
id
}
/notes/
#{
note
.
id
}
/award_emoji"
,
user
),
name:
'+1'
...
...
spec/requests/lfs_http_spec.rb
View file @
7649497f
...
@@ -44,6 +44,113 @@ describe 'Git LFS API and storage' do
...
@@ -44,6 +44,113 @@ describe 'Git LFS API and storage' do
end
end
end
end
context
'project specific LFS settings'
do
let
(
:project
)
{
create
(
:empty_project
)
}
let
(
:body
)
do
{
'objects'
=>
[
{
'oid'
=>
'91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897'
,
'size'
=>
1575078
},
{
'oid'
=>
sample_oid
,
'size'
=>
sample_size
}
],
'operation'
=>
'upload'
}
end
let
(
:authorization
)
{
authorize_user
}
context
'with LFS disabled globally'
do
before
do
project
.
team
<<
[
user
,
:master
]
allow
(
Gitlab
.
config
.
lfs
).
to
receive
(
:enabled
).
and_return
(
false
)
end
describe
'LFS disabled in project'
do
before
do
project
.
update_attribute
(
:lfs_enabled
,
false
)
end
it
'responds with a 501 message on upload'
do
post_lfs_json
"
#{
project
.
http_url_to_repo
}
/info/lfs/objects/batch"
,
body
,
headers
expect
(
response
).
to
have_http_status
(
501
)
end
it
'responds with a 501 message on download'
do
get
"
#{
project
.
http_url_to_repo
}
/gitlab-lfs/objects/
#{
sample_oid
}
"
,
nil
,
headers
expect
(
response
).
to
have_http_status
(
501
)
end
end
describe
'LFS enabled in project'
do
before
do
project
.
update_attribute
(
:lfs_enabled
,
true
)
end
it
'responds with a 501 message on upload'
do
post_lfs_json
"
#{
project
.
http_url_to_repo
}
/info/lfs/objects/batch"
,
body
,
headers
expect
(
response
).
to
have_http_status
(
501
)
end
it
'responds with a 501 message on download'
do
get
"
#{
project
.
http_url_to_repo
}
/gitlab-lfs/objects/
#{
sample_oid
}
"
,
nil
,
headers
expect
(
response
).
to
have_http_status
(
501
)
end
end
end
context
'with LFS enabled globally'
do
before
do
project
.
team
<<
[
user
,
:master
]
enable_lfs
end
describe
'LFS disabled in project'
do
before
do
project
.
update_attribute
(
:lfs_enabled
,
false
)
end
it
'responds with a 403 message on upload'
do
post_lfs_json
"
#{
project
.
http_url_to_repo
}
/info/lfs/objects/batch"
,
body
,
headers
expect
(
response
).
to
have_http_status
(
403
)
expect
(
json_response
).
to
include
(
'message'
=>
'Access forbidden. Check your access level.'
)
end
it
'responds with a 403 message on download'
do
get
"
#{
project
.
http_url_to_repo
}
/gitlab-lfs/objects/
#{
sample_oid
}
"
,
nil
,
headers
expect
(
response
).
to
have_http_status
(
403
)
expect
(
json_response
).
to
include
(
'message'
=>
'Access forbidden. Check your access level.'
)
end
end
describe
'LFS enabled in project'
do
before
do
project
.
update_attribute
(
:lfs_enabled
,
true
)
end
it
'responds with a 200 message on upload'
do
post_lfs_json
"
#{
project
.
http_url_to_repo
}
/info/lfs/objects/batch"
,
body
,
headers
expect
(
response
).
to
have_http_status
(
200
)
expect
(
json_response
[
'objects'
].
first
[
'size'
]).
to
eq
(
1575078
)
end
it
'responds with a 200 message on download'
do
get
"
#{
project
.
http_url_to_repo
}
/gitlab-lfs/objects/
#{
sample_oid
}
"
,
nil
,
headers
expect
(
response
).
to
have_http_status
(
200
)
end
end
end
end
describe
'deprecated API'
do
describe
'deprecated API'
do
let
(
:project
)
{
create
(
:empty_project
)
}
let
(
:project
)
{
create
(
:empty_project
)
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment