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
Boxiang Sun
gitlab-ce
Commits
376dd3a3
Commit
376dd3a3
authored
Nov 05, 2013
by
Dmitriy Zaporozhets
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'feature/create_file' of /home/git/repositories/gitlab/gitlabhq
parents
2ee04b12
3083e5e4
Changes
18
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
315 additions
and
80 deletions
+315
-80
app/contexts/base_context.rb
app/contexts/base_context.rb
+0
-1
app/contexts/files/base_context.rb
app/contexts/files/base_context.rb
+31
-0
app/contexts/files/create_context.rb
app/contexts/files/create_context.rb
+50
-0
app/contexts/files/update_context.rb
app/contexts/files/update_context.rb
+38
-0
app/controllers/projects/application_controller.rb
app/controllers/projects/application_controller.rb
+6
-0
app/controllers/projects/base_tree_controller.rb
app/controllers/projects/base_tree_controller.rb
+8
-0
app/controllers/projects/edit_tree_controller.rb
app/controllers/projects/edit_tree_controller.rb
+10
-37
app/controllers/projects/new_tree_controller.rb
app/controllers/projects/new_tree_controller.rb
+18
-0
app/controllers/projects/tree_controller.rb
app/controllers/projects/tree_controller.rb
+1
-8
app/views/layouts/nav/_project.html.haml
app/views/layouts/nav/_project.html.haml
+1
-1
app/views/projects/new_tree/show.html.haml
app/views/projects/new_tree/show.html.haml
+47
-0
app/views/projects/tree/_tree.html.haml
app/views/projects/tree/_tree.html.haml
+6
-0
config/routes.rb
config/routes.rb
+11
-9
features/project/source/browse_files.feature
features/project/source/browse_files.feature
+4
-0
features/steps/project/project_browse_files.rb
features/steps/project/project_browse_files.rb
+17
-8
lib/gitlab/satellite/files/edit_file_action.rb
lib/gitlab/satellite/files/edit_file_action.rb
+3
-16
lib/gitlab/satellite/files/file_action.rb
lib/gitlab/satellite/files/file_action.rb
+20
-0
lib/gitlab/satellite/files/new_file_action.rb
lib/gitlab/satellite/files/new_file_action.rb
+44
-0
No files found.
app/contexts/base_context.rb
View file @
376dd3a3
...
@@ -17,4 +17,3 @@ class BaseContext
...
@@ -17,4 +17,3 @@ class BaseContext
abilities
.
allowed?
(
object
,
action
,
subject
)
abilities
.
allowed?
(
object
,
action
,
subject
)
end
end
end
end
app/contexts/files/base_context.rb
0 → 100644
View file @
376dd3a3
module
Files
class
BaseContext
<
::
BaseContext
attr_reader
:ref
,
:path
def
initialize
(
project
,
user
,
params
,
ref
,
path
=
nil
)
@project
,
@current_user
,
@params
=
project
,
user
,
params
.
dup
@ref
=
ref
@path
=
path
end
private
def
error
(
message
)
{
error:
message
,
status: :error
}
end
def
success
{
error:
''
,
status: :success
}
end
def
repository
project
.
repository
end
end
end
app/contexts/files/create_context.rb
0 → 100644
View file @
376dd3a3
module
Files
class
CreateContext
<
BaseContext
def
execute
allowed
=
if
project
.
protected_branch?
(
ref
)
can?
(
current_user
,
:push_code_to_protected_branches
,
project
)
else
can?
(
current_user
,
:push_code
,
project
)
end
unless
allowed
return
error
(
"You are not allowed to create file in this branch"
)
end
unless
repository
.
branch_names
.
include?
(
ref
)
return
error
(
"You can only create files if you are on top of a branch"
)
end
file_name
=
params
[
:file_name
]
unless
file_name
=~
Gitlab
::
Regex
.
path_regex
return
error
(
"Your changes could not be commited, because file name contains not allowed characters"
)
end
file_path
=
if
path
.
blank?
file_name
else
File
.
join
(
path
,
file_name
)
end
blob
=
repository
.
blob_at
(
ref
,
file_path
)
if
blob
return
error
(
"Your changes could not be commited, because file with such name exists"
)
end
new_file_action
=
Gitlab
::
Satellite
::
NewFileAction
.
new
(
current_user
,
project
,
ref
,
path
)
created_successfully
=
new_file_action
.
commit!
(
params
[
:content
],
params
[
:commit_message
],
file_name
,
)
if
created_successfully
success
else
error
(
"Your changes could not be commited, because the file has been changed"
)
end
end
end
end
app/contexts/files/update_context.rb
0 → 100644
View file @
376dd3a3
module
Files
class
UpdateContext
<
BaseContext
def
execute
allowed
=
if
project
.
protected_branch?
(
ref
)
can?
(
current_user
,
:push_code_to_protected_branches
,
project
)
else
can?
(
current_user
,
:push_code
,
project
)
end
unless
allowed
return
error
(
"You are not allowed to push into this branch"
)
end
unless
repository
.
branch_names
.
include?
(
ref
)
return
error
(
"You can only create files if you are on top of a branch"
)
end
blob
=
repository
.
blob_at
(
ref
,
path
)
unless
blob
return
error
(
"You can only edit text files"
)
end
new_file_action
=
Gitlab
::
Satellite
::
EditFileAction
.
new
(
current_user
,
project
,
ref
,
path
)
created_successfully
=
new_file_action
.
commit!
(
params
[
:content
],
params
[
:commit_message
],
params
[
:last_commit
]
)
if
created_successfully
success
else
error
(
"Your changes could not be commited, because the file has been changed"
)
end
end
end
end
app/controllers/projects/application_controller.rb
View file @
376dd3a3
...
@@ -23,4 +23,10 @@ class Projects::ApplicationController < ApplicationController
...
@@ -23,4 +23,10 @@ class Projects::ApplicationController < ApplicationController
'public_projects'
'public_projects'
end
end
end
end
def
require_branch_head
unless
@repository
.
branch_names
.
include?
(
@ref
)
redirect_to
project_tree_path
(
@project
,
@ref
),
notice:
"This action is not allowed unless you are on top of a branch"
end
end
end
end
app/controllers/projects/base_tree_controller.rb
0 → 100644
View file @
376dd3a3
class
Projects::BaseTreeController
<
Projects
::
ApplicationController
include
ExtractsPath
before_filter
:authorize_read_project!
before_filter
:authorize_code_access!
before_filter
:require_non_empty_project
end
app/controllers/projects/edit_tree_controller.rb
View file @
376dd3a3
# Controller for edit a repository's file
class
Projects::EditTreeController
<
Projects
::
BaseTreeController
class
Projects::EditTreeController
<
Projects
::
ApplicationController
before_filter
:require_branch_head
include
ExtractsPath
before_filter
:blob
# Authorize
before_filter
:authorize_read_project!
before_filter
:authorize_code_access!
before_filter
:require_non_empty_project
before_filter
:edit_requirements
,
only:
[
:show
,
:update
]
def
show
def
show
@last_commit
=
Gitlab
::
Git
::
Commit
.
last_for_path
(
@repository
,
@ref
,
@path
).
sha
@last_commit
=
Gitlab
::
Git
::
Commit
.
last_for_path
(
@repository
,
@ref
,
@path
).
sha
end
end
def
update
def
update
edit_file_action
=
Gitlab
::
Satellite
::
EditFileAction
.
new
(
current_user
,
@project
,
@ref
,
@path
)
result
=
Files
::
UpdateContext
.
new
(
@project
,
current_user
,
params
,
@ref
,
@path
).
execute
updated_successfully
=
edit_file_action
.
commit!
(
params
[
:content
],
params
[
:commit_message
],
params
[
:last_commit
]
)
if
updated_successfully
if
result
[
:status
]
==
:success
redirect_to
project_blob_path
(
@project
,
@id
),
notice:
"Your changes have been successfully commited"
flash
[
:notice
]
=
"Your changes have been successfully commited"
redirect_to
project_blob_path
(
@project
,
@id
)
else
else
flash
[
:
notice
]
=
"Your changes could not be commited, because the file has been changed"
flash
[
:
alert
]
=
result
[
:error
]
render
:show
render
:show
end
end
end
end
private
private
def
edit_requirements
def
blob
@blob
=
@repository
.
blob_at
(
@commit
.
id
,
@path
)
@blob
||=
@repository
.
blob_at
(
@commit
.
id
,
@path
)
unless
@blob
redirect_to
project_blob_path
(
@project
,
@id
),
notice:
"You can only edit text files"
end
allowed
=
if
project
.
protected_branch?
@ref
can?
(
current_user
,
:push_code_to_protected_branches
,
project
)
else
can?
(
current_user
,
:push_code
,
project
)
end
return
access_denied!
unless
allowed
unless
@repository
.
branch_names
.
include?
(
@ref
)
redirect_to
project_blob_path
(
@project
,
@id
),
notice:
"You can only edit this file if you are on top of a branch"
end
end
end
end
end
app/controllers/projects/new_tree_controller.rb
0 → 100644
View file @
376dd3a3
class
Projects::NewTreeController
<
Projects
::
BaseTreeController
before_filter
:require_branch_head
def
show
end
def
update
result
=
Files
::
CreateContext
.
new
(
@project
,
current_user
,
params
,
@ref
,
@path
).
execute
if
result
[
:status
]
==
:success
flash
[
:notice
]
=
"Your changes have been successfully commited"
redirect_to
project_blob_path
(
@project
,
File
.
join
(
@id
,
params
[
:file_name
]))
else
flash
[
:alert
]
=
result
[
:error
]
render
:show
end
end
end
app/controllers/projects/tree_controller.rb
View file @
376dd3a3
# Controller for viewing a repository's file structure
# Controller for viewing a repository's file structure
class
Projects::TreeController
<
Projects
::
ApplicationController
class
Projects::TreeController
<
Projects
::
BaseTreeController
include
ExtractsPath
# Authorize
before_filter
:authorize_read_project!
before_filter
:authorize_code_access!
before_filter
:require_non_empty_project
def
show
def
show
return
not_found!
if
tree
.
entries
.
empty?
return
not_found!
if
tree
.
entries
.
empty?
...
...
app/views/layouts/nav/_project.html.haml
View file @
376dd3a3
...
@@ -4,7 +4,7 @@
...
@@ -4,7 +4,7 @@
%i
.icon-home
%i
.icon-home
-
if
project_nav_tab?
:files
-
if
project_nav_tab?
:files
=
nav_link
(
controller:
%w(tree blob blame edit_tree)
)
do
=
nav_link
(
controller:
%w(tree blob blame edit_tree
new_tree
)
)
do
=
link_to
'Files'
,
project_tree_path
(
@project
,
@ref
||
@repository
.
root_ref
)
=
link_to
'Files'
,
project_tree_path
(
@project
,
@ref
||
@repository
.
root_ref
)
-
if
project_nav_tab?
:commits
-
if
project_nav_tab?
:commits
...
...
app/views/projects/new_tree/show.html.haml
0 → 100644
View file @
376dd3a3
%h3
.page-title
New file
%hr
.file-editor
=
form_tag
(
project_new_tree_path
(
@project
,
@id
),
method: :put
,
class:
"form-horizontal"
)
do
.control-group.commit_message-group
=
label_tag
'file_name'
,
class:
"control-label"
do
File name
.controls
%span
.monospace
=
@path
[
-
1
]
==
"/"
?
@path
:
@path
+
"/"
=
text_field_tag
'file_name'
,
params
[
:file_name
],
placeholder:
"sample.rb"
,
required:
true
%span
on
%span
.label-branch
=
@ref
.control-group.commit_message-group
=
label_tag
'commit_message'
,
class:
"control-label"
do
Commit message
.controls
=
text_area_tag
'commit_message'
,
params
[
:commit_message
],
placeholder:
"Added new file"
,
required:
true
,
rows:
3
.file-holder
.file-title
%i
.icon-file
.file-content.code
%pre
#editor
=
params
[
:content
]
.form-actions
=
hidden_field_tag
'content'
,
''
,
id:
"file-content"
.commit-button-annotation
=
button_tag
"Commit changes"
,
class:
'btn commit-btn js-commit-button btn-create'
.message
to branch
%strong
=
@ref
=
link_to
"Cancel"
,
project_tree_path
(
@project
,
@id
),
class:
"btn btn-cancel"
,
confirm:
leave_edit_message
:javascript
ace
.
config
.
set
(
"
modePath
"
,
gon
.
relative_url_root
+
"
#{
Gitlab
::
Application
.
config
.
assets
.
prefix
}
/ace-src-noconflict
"
)
var
editor
=
ace
.
edit
(
"
editor
"
);
disableButtonIfEmptyField
(
"
#commit_message
"
,
"
.js-commit-button
"
);
$
(
"
.js-commit-button
"
).
click
(
function
(){
$
(
"
#file-content
"
).
val
(
editor
.
getValue
());
$
(
"
.file-editor form
"
).
submit
();
});
app/views/projects/tree/_tree.html.haml
View file @
376dd3a3
...
@@ -10,6 +10,12 @@
...
@@ -10,6 +10,12 @@
=
link_to
truncate
(
title
,
length:
40
),
project_tree_path
(
@project
,
path
)
=
link_to
truncate
(
title
,
length:
40
),
project_tree_path
(
@project
,
path
)
-
else
-
else
=
link_to
title
,
'#'
=
link_to
title
,
'#'
-
if
@repository
.
branch_names
.
include?
(
@ref
)
\/
%li
=
link_to
project_new_tree_path
(
@project
,
@id
),
title:
'New file'
,
id:
'new-file-link'
do
%small
%i
.icon-plus.light
%div
#tree-content-holder
.tree-content-holder
%div
#tree-content-holder
.tree-content-holder
%table
#tree-slider
{
class:
"table_#{@hex_path} tree-table"
}
%table
#tree-slider
{
class:
"table_#{@hex_path} tree-table"
}
...
...
config/routes.rb
View file @
376dd3a3
...
@@ -166,16 +166,18 @@ Gitlab::Application.routes.draw do
...
@@ -166,16 +166,18 @@ Gitlab::Application.routes.draw do
end
end
scope
module: :projects
do
scope
module: :projects
do
resources
:blob
,
only:
[
:show
],
constraints:
{
id:
/.+/
}
resources
:blob
,
only:
[
:show
],
constraints:
{
id:
/.+/
}
resources
:raw
,
only:
[
:show
],
constraints:
{
id:
/.+/
}
resources
:raw
,
only:
[
:show
],
constraints:
{
id:
/.+/
}
resources
:tree
,
only:
[
:show
],
constraints:
{
id:
/.+/
,
format:
/(html|js)/
}
resources
:tree
,
only:
[
:show
],
constraints:
{
id:
/.+/
,
format:
/(html|js)/
}
resources
:edit_tree
,
only:
[
:show
,
:update
],
constraints:
{
id:
/.+/
},
path:
'edit'
resources
:edit_tree
,
only:
[
:show
,
:update
],
constraints:
{
id:
/.+/
},
path:
'edit'
resources
:commit
,
only:
[
:show
],
constraints:
{
id:
/[[:alnum:]]{6,40}/
}
resources
:new_tree
,
only:
[
:show
,
:update
],
constraints:
{
id:
/.+/
},
path:
'new'
resources
:commits
,
only:
[
:show
],
constraints:
{
id:
/(?:[^.]|\.(?!atom$))+/
,
format:
/atom/
}
resources
:commit
,
only:
[
:show
],
constraints:
{
id:
/[[:alnum:]]{6,40}/
}
resources
:compare
,
only:
[
:index
,
:create
]
resources
:commits
,
only:
[
:show
],
constraints:
{
id:
/(?:[^.]|\.(?!atom$))+/
,
format:
/atom/
}
resources
:blame
,
only:
[
:show
],
constraints:
{
id:
/.+/
}
resources
:compare
,
only:
[
:index
,
:create
]
resources
:blame
,
only:
[
:show
],
constraints:
{
id:
/.+/
}
resources
:network
,
only:
[
:show
],
constraints:
{
id:
/(?:[^.]|\.(?!json$))+/
,
format:
/json/
}
resources
:network
,
only:
[
:show
],
constraints:
{
id:
/(?:[^.]|\.(?!json$))+/
,
format:
/json/
}
resources
:graphs
,
only:
[
:show
],
constraints:
{
id:
/(?:[^.]|\.(?!json$))+/
,
format:
/json/
}
resources
:graphs
,
only:
[
:show
],
constraints:
{
id:
/(?:[^.]|\.(?!json$))+/
,
format:
/json/
}
match
"/compare/:from...:to"
=>
"compare#show"
,
as:
"compare"
,
via:
[
:get
,
:post
],
constraints:
{
from:
/.+/
,
to:
/.+/
}
match
"/compare/:from...:to"
=>
"compare#show"
,
as:
"compare"
,
via:
[
:get
,
:post
],
constraints:
{
from:
/.+/
,
to:
/.+/
}
resources
:snippets
,
constraints:
{
id:
/\d+/
}
do
resources
:snippets
,
constraints:
{
id:
/\d+/
}
do
...
...
features/project/source/browse_files.feature
View file @
376dd3a3
...
@@ -20,6 +20,10 @@ Feature: Project Browse files
...
@@ -20,6 +20,10 @@ Feature: Project Browse files
And
I click link
"raw"
And
I click link
"raw"
Then
I should see raw file content
Then
I should see raw file content
Scenario
:
I
can create file
Given
I click on
"new file"
link in repo
Then
I can see new file page
@javascript
@javascript
Scenario
:
I
can edit file
Scenario
:
I
can edit file
Given
I click on
"Gemfile.lock"
file in repo
Given
I click on
"Gemfile.lock"
file in repo
...
...
features/steps/project/project_browse_files.rb
View file @
376dd3a3
...
@@ -3,42 +3,51 @@ class ProjectBrowseFiles < Spinach::FeatureSteps
...
@@ -3,42 +3,51 @@ class ProjectBrowseFiles < Spinach::FeatureSteps
include
SharedProject
include
SharedProject
include
SharedPaths
include
SharedPaths
Then
'I should see files from repository'
do
step
'I should see files from repository'
do
page
.
should
have_content
"app"
page
.
should
have_content
"app"
page
.
should
have_content
"history"
page
.
should
have_content
"history"
page
.
should
have_content
"Gemfile"
page
.
should
have_content
"Gemfile"
end
end
Then
'I should see files from repository for "8470d70"'
do
step
'I should see files from repository for "8470d70"'
do
current_path
.
should
==
project_tree_path
(
@project
,
"8470d70"
)
current_path
.
should
==
project_tree_path
(
@project
,
"8470d70"
)
page
.
should
have_content
"app"
page
.
should
have_content
"app"
page
.
should
have_content
"history"
page
.
should
have_content
"history"
page
.
should
have_content
"Gemfile"
page
.
should
have_content
"Gemfile"
end
end
Given
'I click on "Gemfile.lock" file in repo'
do
step
'I click on "Gemfile.lock" file in repo'
do
click_link
"Gemfile.lock"
click_link
"Gemfile.lock"
end
end
Then
'I should see it content'
do
step
'I should see it content'
do
page
.
should
have_content
"DEPENDENCIES"
page
.
should
have_content
"DEPENDENCIES"
end
end
And
'I click link "raw"'
do
step
'I click link "raw"'
do
click_link
"raw"
click_link
"raw"
end
end
Then
'I should see raw file content'
do
step
'I should see raw file content'
do
page
.
source
.
should
==
ValidCommit
::
BLOB_FILE
page
.
source
.
should
==
ValidCommit
::
BLOB_FILE
end
end
Given
'I click button "edit"'
do
step
'I click button "edit"'
do
click_link
'edit'
click_link
'edit'
end
end
Then
'I can edit code'
do
step
'I can edit code'
do
page
.
execute_script
(
'editor.setValue("GitlabFileEditor")'
)
page
.
execute_script
(
'editor.setValue("GitlabFileEditor")'
)
page
.
evaluate_script
(
'editor.getValue()'
).
should
==
"GitlabFileEditor"
page
.
evaluate_script
(
'editor.getValue()'
).
should
==
"GitlabFileEditor"
end
end
step
'I click on "new file" link in repo'
do
click_link
'new-file-link'
end
step
'I can see new file page'
do
page
.
should
have_content
"New file"
page
.
should
have_content
"File name"
page
.
should
have_content
"Commit message"
end
end
end
lib/gitlab/satellite/edit_file_action.rb
→
lib/gitlab/satellite/
files/
edit_file_action.rb
View file @
376dd3a3
require_relative
'file_action'
module
Gitlab
module
Gitlab
module
Satellite
module
Satellite
# GitLab server-side file update and commit
# GitLab server-side file update and commit
class
EditFileAction
<
Action
class
EditFileAction
<
FileAction
attr_accessor
:file_path
,
:ref
def
initialize
(
user
,
project
,
ref
,
file_path
)
super
user
,
project
,
git_timeout:
10
.
seconds
@file_path
=
file_path
@ref
=
ref
end
# Updates the files content and creates a new commit for it
# Updates the files content and creates a new commit for it
#
#
# Returns false if the ref has been updated while editing the file
# Returns false if the ref has been updated while editing the file
...
@@ -45,13 +39,6 @@ module Gitlab
...
@@ -45,13 +39,6 @@ module Gitlab
Gitlab
::
GitLogger
.
error
(
ex
.
message
)
Gitlab
::
GitLogger
.
error
(
ex
.
message
)
false
false
end
end
protected
def
can_edit?
(
last_commit
)
current_last_commit
=
Gitlab
::
Git
::
Commit
.
last_for_path
(
@project
.
repository
,
ref
,
file_path
).
sha
last_commit
==
current_last_commit
end
end
end
end
end
end
end
lib/gitlab/satellite/files/file_action.rb
0 → 100644
View file @
376dd3a3
module
Gitlab
module
Satellite
class
FileAction
<
Action
attr_accessor
:file_path
,
:ref
def
initialize
(
user
,
project
,
ref
,
file_path
)
super
user
,
project
,
git_timeout:
10
.
seconds
@file_path
=
file_path
@ref
=
ref
end
protected
def
can_edit?
(
last_commit
)
current_last_commit
=
Gitlab
::
Git
::
Commit
.
last_for_path
(
@project
.
repository
,
ref
,
file_path
).
sha
last_commit
==
current_last_commit
end
end
end
end
lib/gitlab/satellite/files/new_file_action.rb
0 → 100644
View file @
376dd3a3
require_relative
'file_action'
module
Gitlab
module
Satellite
class
NewFileAction
<
FileAction
# Updates the files content and creates a new commit for it
#
# Returns false if the ref has been updated while editing the file
# Returns false if committing the change fails
# Returns false if pushing from the satellite to Gitolite failed or was rejected
# Returns true otherwise
def
commit!
(
content
,
commit_message
,
file_name
)
in_locked_and_timed_satellite
do
|
repo
|
prepare_satellite!
(
repo
)
# create target branch in satellite at the corresponding commit from Gitolite
repo
.
git
.
checkout
({
raise:
true
,
timeout:
true
,
b:
true
},
ref
,
"origin/
#{
ref
}
"
)
# update the file in the satellite's working dir
file_path_in_satellite
=
File
.
join
(
repo
.
working_dir
,
file_path
,
file_name
)
File
.
open
(
file_path_in_satellite
,
'w'
)
{
|
f
|
f
.
write
(
content
)
}
# add new file
repo
.
add
(
file_path_in_satellite
)
# commit the changes
# will raise CommandFailed when commit fails
repo
.
git
.
commit
(
raise:
true
,
timeout:
true
,
a:
true
,
m:
commit_message
)
# push commit back to Gitolite
# will raise CommandFailed when push fails
repo
.
git
.
push
({
raise:
true
,
timeout:
true
},
:origin
,
ref
)
# everything worked
true
end
rescue
Grit
::
Git
::
CommandFailed
=>
ex
Gitlab
::
GitLogger
.
error
(
ex
.
message
)
false
end
end
end
end
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