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
7d02bcd2
Commit
7d02bcd2
authored
May 01, 2017
by
Michael Kozono
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Redirect from redirect routes to canonical routes
parent
f4a2dfb4
Changes
30
Show whitespace changes
Inline
Side-by-side
Showing
30 changed files
with
668 additions
and
87 deletions
+668
-87
app/controllers/application_controller.rb
app/controllers/application_controller.rb
+1
-1
app/controllers/concerns/routable_actions.rb
app/controllers/concerns/routable_actions.rb
+11
-0
app/controllers/groups/application_controller.rb
app/controllers/groups/application_controller.rb
+12
-9
app/controllers/groups_controller.rb
app/controllers/groups_controller.rb
+1
-1
app/controllers/projects/application_controller.rb
app/controllers/projects/application_controller.rb
+5
-9
app/controllers/users_controller.rb
app/controllers/users_controller.rb
+9
-3
app/models/concerns/routable.rb
app/models/concerns/routable.rb
+20
-4
app/models/redirect_route.rb
app/models/redirect_route.rb
+10
-0
app/models/user.rb
app/models/user.rb
+5
-0
db/migrate/20170427215854_create_redirect_routes.rb
db/migrate/20170427215854_create_redirect_routes.rb
+15
-0
db/schema.rb
db/schema.rb
+8
-0
lib/constraints/group_url_constrainer.rb
lib/constraints/group_url_constrainer.rb
+1
-1
lib/constraints/project_url_constrainer.rb
lib/constraints/project_url_constrainer.rb
+1
-1
lib/constraints/user_url_constrainer.rb
lib/constraints/user_url_constrainer.rb
+1
-1
spec/controllers/application_controller_spec.rb
spec/controllers/application_controller_spec.rb
+2
-3
spec/controllers/groups_controller_spec.rb
spec/controllers/groups_controller_spec.rb
+88
-4
spec/controllers/projects_controller_spec.rb
spec/controllers/projects_controller_spec.rb
+80
-4
spec/controllers/users_controller_spec.rb
spec/controllers/users_controller_spec.rb
+171
-10
spec/features/groups/group_settings_spec.rb
spec/features/groups/group_settings_spec.rb
+3
-0
spec/features/profiles/account_spec.rb
spec/features/profiles/account_spec.rb
+2
-3
spec/features/projects/features_visibility_spec.rb
spec/features/projects/features_visibility_spec.rb
+20
-17
spec/features/projects/project_settings_spec.rb
spec/features/projects/project_settings_spec.rb
+7
-3
spec/lib/constraints/group_url_constrainer_spec.rb
spec/lib/constraints/group_url_constrainer_spec.rb
+30
-2
spec/lib/constraints/project_url_constrainer_spec.rb
spec/lib/constraints/project_url_constrainer_spec.rb
+19
-2
spec/lib/constraints/user_url_constrainer_spec.rb
spec/lib/constraints/user_url_constrainer_spec.rb
+19
-2
spec/lib/gitlab/import_export/all_models.yml
spec/lib/gitlab/import_export/all_models.yml
+1
-0
spec/models/concerns/routable_spec.rb
spec/models/concerns/routable_spec.rb
+48
-4
spec/models/redirect_route_spec.rb
spec/models/redirect_route_spec.rb
+16
-0
spec/models/user_spec.rb
spec/models/user_spec.rb
+59
-0
spec/routing/project_routing_spec.rb
spec/routing/project_routing_spec.rb
+3
-3
No files found.
app/controllers/application_controller.rb
View file @
7d02bcd2
...
...
@@ -58,7 +58,7 @@ class ApplicationController < ActionController::Base
if
current_user
not_found
else
redirect_to
new_user_session_path
authenticate_user!
end
end
...
...
app/controllers/concerns/routable_actions.rb
0 → 100644
View file @
7d02bcd2
module
RoutableActions
extend
ActiveSupport
::
Concern
def
ensure_canonical_path
(
routable
,
requested_path
)
return
unless
request
.
get?
if
routable
.
full_path
!=
requested_path
redirect_to
request
.
original_url
.
sub
(
requested_path
,
routable
.
full_path
)
end
end
end
app/controllers/groups/application_controller.rb
View file @
7d02bcd2
class
Groups::ApplicationController
<
ApplicationController
include
RoutableActions
layout
'group'
skip_before_action
:authenticate_user!
...
...
@@ -8,18 +10,15 @@ class Groups::ApplicationController < ApplicationController
def
group
unless
@group
id
=
params
[
:group_id
]
||
params
[
:id
]
@group
=
Group
.
find_by_full_path
(
id
)
@group_merge_requests
=
MergeRequestsFinder
.
new
(
current_user
,
group_id:
@group
.
id
).
execute
given_path
=
params
[
:group_id
]
||
params
[
:id
]
@group
=
Group
.
find_by_full_path
(
given_path
,
follow_redirects:
request
.
get?
)
unless
@group
&&
can?
(
current_user
,
:read_group
,
@group
)
if
@group
&&
can?
(
current_user
,
:read_group
,
@group
)
ensure_canonical_path
(
@group
,
given_path
)
else
@group
=
nil
if
current_user
.
nil?
authenticate_user!
else
render_404
end
route_not_found
end
end
...
...
@@ -30,6 +29,10 @@ class Groups::ApplicationController < ApplicationController
@projects
||=
GroupProjectsFinder
.
new
(
group:
group
,
current_user:
current_user
).
execute
end
def
group_merge_requests
@group_merge_requests
=
MergeRequestsFinder
.
new
(
current_user
,
group_id:
@group
.
id
).
execute
end
def
authorize_admin_group!
unless
can?
(
current_user
,
:admin_group
,
group
)
return
render_404
...
...
app/controllers/groups_controller.rb
View file @
7d02bcd2
...
...
@@ -12,8 +12,8 @@ class GroupsController < Groups::ApplicationController
before_action
:authorize_admin_group!
,
only:
[
:edit
,
:update
,
:destroy
,
:projects
]
before_action
:authorize_create_group!
,
only:
[
:new
,
:create
]
# Load group projects
before_action
:group_projects
,
only:
[
:projects
,
:activity
,
:issues
,
:merge_requests
]
before_action
:group_merge_requests
,
only:
[
:merge_requests
]
before_action
:event_filter
,
only:
[
:activity
]
before_action
:user_actions
,
only:
[
:show
,
:subgroups
]
...
...
app/controllers/projects/application_controller.rb
View file @
7d02bcd2
class
Projects::ApplicationController
<
ApplicationController
include
RoutableActions
skip_before_action
:authenticate_user!
before_action
:project
before_action
:repository
...
...
@@ -24,20 +26,14 @@ class Projects::ApplicationController < ApplicationController
end
project_path
=
"
#{
namespace
}
/
#{
id
}
"
@project
=
Project
.
find_by_full_path
(
project_path
)
@project
=
Project
.
find_by_full_path
(
project_path
,
follow_redirects:
request
.
get?
)
if
can?
(
current_user
,
:read_project
,
@project
)
&&
!
@project
.
pending_delete?
if
@project
.
path_with_namespace
!=
project_path
redirect_to
request
.
original_url
.
gsub
(
project_path
,
@project
.
path_with_namespace
)
end
ensure_canonical_path
(
@project
,
project_path
)
else
@project
=
nil
if
current_user
.
nil?
authenticate_user!
else
render_404
end
route_not_found
end
end
...
...
app/controllers/users_controller.rb
View file @
7d02bcd2
class
UsersController
<
ApplicationController
include
RoutableActions
skip_before_action
:authenticate_user!
before_action
:user
,
except:
[
:exists
]
before_action
:authorize_read_user!
,
only:
[
:show
]
before_action
:authorize_read_user!
,
except:
[
:exists
]
def
show
respond_to
do
|
format
|
...
...
@@ -92,11 +94,15 @@ class UsersController < ApplicationController
private
def
authorize_read_user!
render_404
unless
can?
(
current_user
,
:read_user
,
user
)
if
can?
(
current_user
,
:read_user
,
user
)
ensure_canonical_path
(
user
.
namespace
,
params
[
:username
])
else
render_404
end
end
def
user
@user
||=
User
.
find_by_
username!
(
params
[
:username
]
)
@user
||=
User
.
find_by_
full_path
(
params
[
:username
],
follow_redirects:
true
)
end
def
contributed_projects
...
...
app/models/concerns/routable.rb
View file @
7d02bcd2
...
...
@@ -5,6 +5,7 @@ module Routable
included
do
has_one
:route
,
as: :source
,
autosave:
true
,
dependent: :destroy
has_many
:redirect_routes
,
as: :source
,
autosave:
true
,
dependent: :destroy
validates_associated
:route
validates
:route
,
presence:
true
...
...
@@ -26,16 +27,31 @@ module Routable
# Klass.find_by_full_path('gitlab-org/gitlab-ce')
#
# Returns a single object, or nil.
def
find_by_full_path
(
path
)
def
find_by_full_path
(
path
,
follow_redirects:
false
)
# On MySQL we want to ensure the ORDER BY uses a case-sensitive match so
# any literal matches come first, for this we have to use "BINARY".
# Without this there's still no guarantee in what order MySQL will return
# rows.
#
# Why do we do this?
#
# Even though we have Rails validation on Route for unique paths
# (case-insensitive), there are old projects in our DB (and possibly
# clients' DBs) that have the same path with different cases.
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/18603. Also note that
# our unique index is case-sensitive in Postgres.
binary
=
Gitlab
::
Database
.
mysql?
?
'BINARY'
:
''
order_sql
=
"(CASE WHEN
#{
binary
}
routes.path =
#{
connection
.
quote
(
path
)
}
THEN 0 ELSE 1 END)"
found
=
where_full_path_in
([
path
]).
reorder
(
order_sql
).
take
return
found
if
found
where_full_path_in
([
path
]).
reorder
(
order_sql
).
take
if
follow_redirects
if
Gitlab
::
Database
.
postgresql?
joins
(
:redirect_routes
).
find_by
(
"LOWER(redirect_routes.path) = LOWER(?)"
,
path
)
else
joins
(
:redirect_routes
).
find_by
(
path:
path
)
end
end
end
# Builds a relation to find multiple objects by their full paths.
...
...
app/models/redirect_route.rb
0 → 100644
View file @
7d02bcd2
class
RedirectRoute
<
ActiveRecord
::
Base
belongs_to
:source
,
polymorphic:
true
validates
:source
,
presence:
true
validates
:path
,
length:
{
within:
1
..
255
},
presence:
true
,
uniqueness:
{
case_sensitive:
false
}
end
app/models/user.rb
View file @
7d02bcd2
...
...
@@ -333,6 +333,11 @@ class User < ActiveRecord::Base
find_by
(
id:
Key
.
unscoped
.
select
(
:user_id
).
where
(
id:
key_id
))
end
def
find_by_full_path
(
path
,
follow_redirects:
false
)
namespace
=
Namespace
.
find_by_full_path
(
path
,
follow_redirects:
follow_redirects
)
namespace
.
owner
if
namespace
&&
namespace
.
owner
end
def
reference_prefix
'@'
end
...
...
db/migrate/20170427215854_create_redirect_routes.rb
0 → 100644
View file @
7d02bcd2
class
CreateRedirectRoutes
<
ActiveRecord
::
Migration
# Set this constant to true if this migration requires downtime.
DOWNTIME
=
false
def
change
create_table
:redirect_routes
do
|
t
|
t
.
integer
:source_id
,
null:
false
t
.
string
:source_type
,
null:
false
t
.
string
:path
,
null:
false
t
.
timestamps
null:
false
end
end
end
db/schema.rb
View file @
7d02bcd2
...
...
@@ -1040,6 +1040,14 @@ ActiveRecord::Schema.define(version: 20170504102911) do
add_index
"protected_tags"
,
[
"project_id"
],
name:
"index_protected_tags_on_project_id"
,
using: :btree
create_table
"redirect_routes"
,
force: :cascade
do
|
t
|
t
.
integer
"source_id"
,
null:
false
t
.
string
"source_type"
,
null:
false
t
.
string
"path"
,
null:
false
t
.
datetime
"created_at"
,
null:
false
t
.
datetime
"updated_at"
,
null:
false
end
create_table
"releases"
,
force: :cascade
do
|
t
|
t
.
string
"tag"
t
.
text
"description"
...
...
lib/constraints/group_url_constrainer.rb
View file @
7d02bcd2
...
...
@@ -4,6 +4,6 @@ class GroupUrlConstrainer
return
false
unless
DynamicPathValidator
.
valid?
(
id
)
Group
.
find_by_full_path
(
id
).
present?
Group
.
find_by_full_path
(
id
,
follow_redirects:
request
.
get?
).
present?
end
end
lib/constraints/project_url_constrainer.rb
View file @
7d02bcd2
...
...
@@ -6,6 +6,6 @@ class ProjectUrlConstrainer
return
false
unless
DynamicPathValidator
.
valid?
(
full_path
)
Project
.
find_by_full_path
(
full_path
).
present?
Project
.
find_by_full_path
(
full_path
,
follow_redirects:
request
.
get?
).
present?
end
end
lib/constraints/user_url_constrainer.rb
View file @
7d02bcd2
class
UserUrlConstrainer
def
matches?
(
request
)
User
.
find_by_
username
(
request
.
params
[
:username
]
).
present?
User
.
find_by_
full_path
(
request
.
params
[
:username
],
follow_redirects:
request
.
get?
).
present?
end
end
spec/controllers/application_controller_spec.rb
View file @
7d02bcd2
...
...
@@ -106,10 +106,9 @@ describe ApplicationController do
controller
.
send
(
:route_not_found
)
end
it
'does redirect to login page if not authenticated'
do
it
'does redirect to login page
via authenticate_user!
if not authenticated'
do
allow
(
controller
).
to
receive
(
:current_user
).
and_return
(
nil
)
expect
(
controller
).
to
receive
(
:redirect_to
)
expect
(
controller
).
to
receive
(
:new_user_session_path
)
expect
(
controller
).
to
receive
(
:authenticate_user!
)
controller
.
send
(
:route_not_found
)
end
end
...
...
spec/controllers/groups_controller_spec.rb
View file @
7d02bcd2
...
...
@@ -49,6 +49,24 @@ describe GroupsController do
expect
(
assigns
(
:issues
)).
to
eq
[
issue_2
,
issue_1
]
end
end
context
'when requesting the canonical path with different casing'
do
it
'redirects to the correct casing'
do
get
:issues
,
id:
group
.
to_param
.
upcase
expect
(
response
).
to
redirect_to
(
issues_group_path
(
group
.
to_param
))
end
end
context
'when requesting a redirected path'
do
let
(
:redirect_route
)
{
group
.
redirect_routes
.
create
(
path:
'old-path'
)
}
it
'redirects to the canonical path'
do
get
:issues
,
id:
redirect_route
.
path
expect
(
response
).
to
redirect_to
(
issues_group_path
(
group
.
to_param
))
end
end
end
describe
'GET #merge_requests'
do
...
...
@@ -74,6 +92,24 @@ describe GroupsController do
expect
(
assigns
(
:merge_requests
)).
to
eq
[
merge_request_2
,
merge_request_1
]
end
end
context
'when requesting the canonical path with different casing'
do
it
'redirects to the correct casing'
do
get
:merge_requests
,
id:
group
.
to_param
.
upcase
expect
(
response
).
to
redirect_to
(
merge_requests_group_path
(
group
.
to_param
))
end
end
context
'when requesting a redirected path'
do
let
(
:redirect_route
)
{
group
.
redirect_routes
.
create
(
path:
'old-path'
)
}
it
'redirects to the canonical path'
do
get
:merge_requests
,
id:
redirect_route
.
path
expect
(
response
).
to
redirect_to
(
merge_requests_group_path
(
group
.
to_param
))
end
end
end
describe
'DELETE #destroy'
do
...
...
@@ -81,7 +117,7 @@ describe GroupsController do
it
'returns 404'
do
sign_in
(
create
(
:user
))
delete
:destroy
,
id:
group
.
path
delete
:destroy
,
id:
group
.
to_param
expect
(
response
.
status
).
to
eq
(
404
)
end
...
...
@@ -94,15 +130,39 @@ describe GroupsController do
it
'schedules a group destroy'
do
Sidekiq
::
Testing
.
fake!
do
expect
{
delete
:destroy
,
id:
group
.
path
}.
to
change
(
GroupDestroyWorker
.
jobs
,
:size
).
by
(
1
)
expect
{
delete
:destroy
,
id:
group
.
to_param
}.
to
change
(
GroupDestroyWorker
.
jobs
,
:size
).
by
(
1
)
end
end
it
'redirects to the root path'
do
delete
:destroy
,
id:
group
.
path
delete
:destroy
,
id:
group
.
to_param
expect
(
response
).
to
redirect_to
(
root_path
)
end
context
'when requesting the canonical path with different casing'
do
it
'does not 404'
do
delete
:destroy
,
id:
group
.
to_param
.
upcase
expect
(
response
).
to_not
have_http_status
(
404
)
end
it
'does not redirect to the correct casing'
do
delete
:destroy
,
id:
group
.
to_param
.
upcase
expect
(
response
).
to_not
redirect_to
(
group_path
(
group
.
to_param
))
end
end
context
'when requesting a redirected path'
do
let
(
:redirect_route
)
{
group
.
redirect_routes
.
create
(
path:
'old-path'
)
}
it
'returns not found'
do
delete
:destroy
,
id:
redirect_route
.
path
expect
(
response
).
to
have_http_status
(
404
)
end
end
end
end
...
...
@@ -111,7 +171,7 @@ describe GroupsController do
sign_in
(
user
)
end
it
'updates the path succesfully'
do
it
'updates the path succes
s
fully'
do
post
:update
,
id:
group
.
to_param
,
group:
{
path:
'new_path'
}
expect
(
response
).
to
have_http_status
(
302
)
...
...
@@ -125,5 +185,29 @@ describe GroupsController do
expect
(
assigns
(
:group
).
errors
).
not_to
be_empty
expect
(
assigns
(
:group
).
path
).
not_to
eq
(
'new_path'
)
end
context
'when requesting the canonical path with different casing'
do
it
'does not 404'
do
post
:update
,
id:
group
.
to_param
.
upcase
,
group:
{
path:
'new_path'
}
expect
(
response
).
to_not
have_http_status
(
404
)
end
it
'does not redirect to the correct casing'
do
post
:update
,
id:
group
.
to_param
.
upcase
,
group:
{
path:
'new_path'
}
expect
(
response
).
to_not
redirect_to
(
group_path
(
group
.
to_param
))
end
end
context
'when requesting a redirected path'
do
let
(
:redirect_route
)
{
group
.
redirect_routes
.
create
(
path:
'old-path'
)
}
it
'returns not found'
do
post
:update
,
id:
redirect_route
.
path
,
group:
{
path:
'new_path'
}
expect
(
response
).
to
have_http_status
(
404
)
end
end
end
end
spec/controllers/projects_controller_spec.rb
View file @
7d02bcd2
...
...
@@ -218,19 +218,32 @@ describe ProjectsController do
expect
(
response
).
to
redirect_to
(
namespace_project_path
)
end
end
context
'when requesting a redirected path'
do
let!
(
:redirect_route
)
{
public_project
.
redirect_routes
.
create!
(
path:
"foo/bar"
)
}
it
'redirects to the canonical path'
do
get
:show
,
namespace_id:
'foo'
,
id:
'bar'
expect
(
response
).
to
redirect_to
(
public_project
)
end
end
end
describe
"#update"
do
render_views
let
(
:admin
)
{
create
(
:admin
)
}
let
(
:project
)
{
create
(
:project
,
:repository
)
}
let
(
:new_path
)
{
'renamed_path'
}
let
(
:project_params
)
{
{
path:
new_path
}
}
before
do
sign_in
(
admin
)
end
it
"sets the repository to the right path after a rename"
do
project
=
create
(
:project
,
:repository
)
new_path
=
'renamed_path'
project_params
=
{
path:
new_path
}
controller
.
instance_variable_set
(
:@project
,
project
)
sign_in
(
admin
)
put
:update
,
namespace_id:
project
.
namespace
,
...
...
@@ -241,6 +254,34 @@ describe ProjectsController do
expect
(
assigns
(
:repository
).
path
).
to
eq
(
project
.
repository
.
path
)
expect
(
response
).
to
have_http_status
(
302
)
end
context
'when requesting the canonical path'
do
it
"is case-insensitive"
do
controller
.
instance_variable_set
(
:@project
,
project
)
put
:update
,
namespace_id:
'FOo'
,
id:
'baR'
,
project:
project_params
expect
(
project
.
repository
.
path
).
to
include
(
new_path
)
expect
(
assigns
(
:repository
).
path
).
to
eq
(
project
.
repository
.
path
)
expect
(
response
).
to
have_http_status
(
302
)
end
end
context
'when requesting a redirected path'
do
let!
(
:redirect_route
)
{
project
.
redirect_routes
.
create!
(
path:
"foo/bar"
)
}
it
'returns not found'
do
put
:update
,
namespace_id:
'foo'
,
id:
'bar'
,
project:
project_params
expect
(
response
).
to
have_http_status
(
404
)
end
end
end
describe
"#destroy"
do
...
...
@@ -276,6 +317,31 @@ describe ProjectsController do
expect
(
merge_request
.
reload
.
state
).
to
eq
(
'closed'
)
end
end
context
'when requesting the canonical path'
do
it
"is case-insensitive"
do
controller
.
instance_variable_set
(
:@project
,
project
)
sign_in
(
admin
)
orig_id
=
project
.
id
delete
:destroy
,
namespace_id:
project
.
namespace
,
id:
project
.
path
.
upcase
expect
{
Project
.
find
(
orig_id
)
}.
to
raise_error
(
ActiveRecord
::
RecordNotFound
)
expect
(
response
).
to
have_http_status
(
302
)
expect
(
response
).
to
redirect_to
(
dashboard_projects_path
)
end
end
context
'when requesting a redirected path'
do
let!
(
:redirect_route
)
{
project
.
redirect_routes
.
create!
(
path:
"foo/bar"
)
}
it
'returns not found'
do
sign_in
(
admin
)
delete
:destroy
,
namespace_id:
'foo'
,
id:
'bar'
expect
(
response
).
to
have_http_status
(
404
)
end
end
end
describe
'PUT #new_issue_address'
do
...
...
@@ -397,6 +463,16 @@ describe ProjectsController do
expect
(
parsed_body
[
"Tags"
]).
to
include
(
"v1.0.0"
)
expect
(
parsed_body
[
"Commits"
]).
to
include
(
"123456"
)
end
context
'when requesting a redirected path'
do
let!
(
:redirect_route
)
{
public_project
.
redirect_routes
.
create!
(
path:
"foo/bar"
)
}
it
'redirects to the canonical path'
do
get
:refs
,
namespace_id:
'foo'
,
id:
'bar'
expect
(
response
).
to
redirect_to
(
refs_namespace_project_path
(
namespace_id:
public_project
.
namespace
,
id:
public_project
))
end
end
end
describe
'POST #preview_markdown'
do
...
...
spec/controllers/users_controller_spec.rb
View file @
7d02bcd2
...
...
@@ -4,15 +4,6 @@ describe UsersController do
let
(
:user
)
{
create
(
:user
)
}
describe
'GET #show'
do
it
'is case-insensitive'
do
user
=
create
(
:user
,
username:
'CamelCaseUser'
)
sign_in
(
user
)
get
:show
,
username:
user
.
username
.
downcase
expect
(
response
).
to
be_success
end
context
'with rendered views'
do
render_views
...
...
@@ -61,6 +52,38 @@ describe UsersController do
end
end
end
context
'when requesting the canonical path'
do
let
(
:user
)
{
create
(
:user
,
username:
'CamelCaseUser'
)
}
before
{
sign_in
(
user
)
}
context
'with exactly matching casing'
do
it
'responds with success'
do
get
:show
,
username:
user
.
username
expect
(
response
).
to
be_success
end
end
context
'with different casing'
do
it
'redirects to the correct casing'
do
get
:show
,
username:
user
.
username
.
downcase
expect
(
response
).
to
redirect_to
(
user
)
end
end
end
context
'when requesting a redirected path'
do
let
(
:redirect_route
)
{
user
.
namespace
.
redirect_routes
.
create
(
path:
'old-username'
)
}
it
'redirects to the canonical path'
do
get
:show
,
username:
redirect_route
.
path
expect
(
response
).
to
redirect_to
(
user
)
end
end
end
describe
'GET #calendar'
do
...
...
@@ -88,11 +111,43 @@ describe UsersController do
expect
(
assigns
(
:contributions_calendar
).
projects
.
count
).
to
eq
(
2
)
end
end
context
'when requesting the canonical path'
do
let
(
:user
)
{
create
(
:user
,
username:
'CamelCaseUser'
)
}
before
{
sign_in
(
user
)
}
context
'with exactly matching casing'
do
it
'responds with success'
do
get
:calendar
,
username:
user
.
username
expect
(
response
).
to
be_success
end
end
context
'with different casing'
do
it
'redirects to the correct casing'
do
get
:calendar
,
username:
user
.
username
.
downcase
expect
(
response
).
to
redirect_to
(
user_calendar_path
(
user
))
end
end
end
context
'when requesting a redirected path'
do
let
(
:redirect_route
)
{
user
.
namespace
.
redirect_routes
.
create
(
path:
'old-username'
)
}
it
'redirects to the canonical path'
do
get
:calendar
,
username:
redirect_route
.
path
expect
(
response
).
to
redirect_to
(
user_calendar_path
(
user
))
end
end
end
describe
'GET #calendar_activities'
do
let!
(
:project
)
{
create
(
:empty_project
)
}
let
!
(
:user
)
{
create
(
:user
)
}
let
(
:user
)
{
create
(
:user
)
}
before
do
allow_any_instance_of
(
User
).
to
receive
(
:contributed_projects_ids
).
and_return
([
project
.
id
])
...
...
@@ -110,6 +165,36 @@ describe UsersController do
get
:calendar_activities
,
username:
user
.
username
expect
(
response
).
to
render_template
(
'calendar_activities'
)
end
context
'when requesting the canonical path'
do
let
(
:user
)
{
create
(
:user
,
username:
'CamelCaseUser'
)
}
context
'with exactly matching casing'
do
it
'responds with success'
do
get
:calendar_activities
,
username:
user
.
username
expect
(
response
).
to
be_success
end
end
context
'with different casing'
do
it
'redirects to the correct casing'
do
get
:calendar_activities
,
username:
user
.
username
.
downcase
expect
(
response
).
to
redirect_to
(
user_calendar_activities_path
(
user
))
end
end
end
context
'when requesting a redirected path'
do
let
(
:redirect_route
)
{
user
.
namespace
.
redirect_routes
.
create
(
path:
'old-username'
)
}
it
'redirects to the canonical path'
do
get
:calendar_activities
,
username:
redirect_route
.
path
expect
(
response
).
to
redirect_to
(
user_calendar_activities_path
(
user
))
end
end
end
describe
'GET #snippets'
do
...
...
@@ -132,5 +217,81 @@ describe UsersController do
expect
(
JSON
.
parse
(
response
.
body
)).
to
have_key
(
'html'
)
end
end
context
'when requesting the canonical path'
do
let
(
:user
)
{
create
(
:user
,
username:
'CamelCaseUser'
)
}
context
'with exactly matching casing'
do
it
'responds with success'
do
get
:snippets
,
username:
user
.
username
expect
(
response
).
to
be_success
end
end
context
'with different casing'
do
it
'redirects to the correct casing'
do
get
:snippets
,
username:
user
.
username
.
downcase
expect
(
response
).
to
redirect_to
(
user_snippets_path
(
user
))
end
end
end
context
'when requesting a redirected path'
do
let
(
:redirect_route
)
{
user
.
namespace
.
redirect_routes
.
create
(
path:
'old-username'
)
}
it
'redirects to the canonical path'
do
get
:snippets
,
username:
redirect_route
.
path
expect
(
response
).
to
redirect_to
(
user_snippets_path
(
user
))
end
end
end
describe
'GET #exists'
do
before
do
sign_in
(
user
)
end
context
'when user exists'
do
it
'returns JSON indicating the user exists'
do
get
:exists
,
username:
user
.
username
expected_json
=
{
exists:
true
}.
to_json
expect
(
response
.
body
).
to
eq
(
expected_json
)
end
context
'when the casing is different'
do
let
(
:user
)
{
create
(
:user
,
username:
'CamelCaseUser'
)
}
it
'returns JSON indicating the user exists'
do
get
:exists
,
username:
user
.
username
.
downcase
expected_json
=
{
exists:
true
}.
to_json
expect
(
response
.
body
).
to
eq
(
expected_json
)
end
end
end
context
'when the user does not exist'
do
it
'returns JSON indicating the user does not exist'
do
get
:exists
,
username:
'foo'
expected_json
=
{
exists:
false
}.
to_json
expect
(
response
.
body
).
to
eq
(
expected_json
)
end
context
'when a user changed their username'
do
let
(
:redirect_route
)
{
user
.
namespace
.
redirect_routes
.
create
(
path:
'old-username'
)
}
it
'returns JSON indicating a user by that username does not exist'
do
get
:exists
,
username:
'old-username'
expected_json
=
{
exists:
false
}.
to_json
expect
(
response
.
body
).
to
eq
(
expected_json
)
end
end
end
end
end
spec/features/groups/group_settings_spec.rb
View file @
7d02bcd2
...
...
@@ -53,6 +53,9 @@ feature 'Edit group settings', feature: true do
given
(
:old_project_full_path
)
{
"/
#{
group
.
path
}
/
#{
project
.
path
}
"
}
given
(
:new_project_full_path
)
{
"/
#{
new_group_path
}
/
#{
project
.
path
}
"
}
before
(
:context
)
{
TestEnv
.
clean_test_path
}
after
(
:example
)
{
TestEnv
.
clean_test_path
}
scenario
'the project is accessible via the new path'
do
update_path
(
new_group_path
)
visit
new_project_full_path
...
...
spec/features/profiles/account_spec.rb
View file @
7d02bcd2
...
...
@@ -31,9 +31,8 @@ feature 'Profile > Account', feature: true do
given
(
:new_project_path
)
{
"/
#{
new_username
}
/
#{
project
.
path
}
"
}
given
(
:old_project_path
)
{
"/
#{
user
.
username
}
/
#{
project
.
path
}
"
}
after
do
TestEnv
.
clean_test_path
end
before
(
:context
)
{
TestEnv
.
clean_test_path
}
after
(
:example
)
{
TestEnv
.
clean_test_path
}
scenario
'the project is accessible via the new path'
do
update_username
(
new_username
)
...
...
spec/features/projects/features_visibility_spec.rb
View file @
7d02bcd2
...
...
@@ -68,8 +68,7 @@ describe 'Edit Project Settings', feature: true do
end
describe
'project features visibility pages'
do
before
do
@tools
=
let
(
:tools
)
{
{
builds:
namespace_project_pipelines_path
(
project
.
namespace
,
project
),
issues:
namespace_project_issues_path
(
project
.
namespace
,
project
),
...
...
@@ -77,11 +76,15 @@ describe 'Edit Project Settings', feature: true do
snippets:
namespace_project_snippets_path
(
project
.
namespace
,
project
),
merge_requests:
namespace_project_merge_requests_path
(
project
.
namespace
,
project
),
}
end
}
context
'normal user'
do
before
do
login_as
(
member
)
end
it
'renders 200 if tool is enabled'
do
@
tools
.
each
do
|
method_name
,
url
|
tools
.
each
do
|
method_name
,
url
|
project
.
project_feature
.
update_attribute
(
"
#{
method_name
}
_access_level"
,
ProjectFeature
::
ENABLED
)
visit
url
expect
(
page
.
status_code
).
to
eq
(
200
)
...
...
@@ -89,7 +92,7 @@ describe 'Edit Project Settings', feature: true do
end
it
'renders 404 if feature is disabled'
do
@
tools
.
each
do
|
method_name
,
url
|
tools
.
each
do
|
method_name
,
url
|
project
.
project_feature
.
update_attribute
(
"
#{
method_name
}
_access_level"
,
ProjectFeature
::
DISABLED
)
visit
url
expect
(
page
.
status_code
).
to
eq
(
404
)
...
...
@@ -99,21 +102,21 @@ describe 'Edit Project Settings', feature: true do
it
'renders 404 if feature is enabled only for team members'
do
project
.
team
.
truncate
@
tools
.
each
do
|
method_name
,
url
|
tools
.
each
do
|
method_name
,
url
|
project
.
project_feature
.
update_attribute
(
"
#{
method_name
}
_access_level"
,
ProjectFeature
::
PRIVATE
)
visit
url
expect
(
page
.
status_code
).
to
eq
(
404
)
end
end
it
'renders 200 if user
s
is member of group'
do
it
'renders 200 if user is member of group'
do
group
=
create
(
:group
)
project
.
group
=
group
project
.
save
group
.
add_owner
(
member
)
@
tools
.
each
do
|
method_name
,
url
|
tools
.
each
do
|
method_name
,
url
|
project
.
project_feature
.
update_attribute
(
"
#{
method_name
}
_access_level"
,
ProjectFeature
::
PRIVATE
)
visit
url
expect
(
page
.
status_code
).
to
eq
(
200
)
...
...
@@ -128,7 +131,7 @@ describe 'Edit Project Settings', feature: true do
end
it
'renders 404 if feature is disabled'
do
@
tools
.
each
do
|
method_name
,
url
|
tools
.
each
do
|
method_name
,
url
|
project
.
project_feature
.
update_attribute
(
"
#{
method_name
}
_access_level"
,
ProjectFeature
::
DISABLED
)
visit
url
expect
(
page
.
status_code
).
to
eq
(
404
)
...
...
@@ -138,7 +141,7 @@ describe 'Edit Project Settings', feature: true do
it
'renders 200 if feature is enabled only for team members'
do
project
.
team
.
truncate
@
tools
.
each
do
|
method_name
,
url
|
tools
.
each
do
|
method_name
,
url
|
project
.
project_feature
.
update_attribute
(
"
#{
method_name
}
_access_level"
,
ProjectFeature
::
PRIVATE
)
visit
url
expect
(
page
.
status_code
).
to
eq
(
200
)
...
...
spec/features/projects/project_settings_spec.rb
View file @
7d02bcd2
...
...
@@ -58,6 +58,9 @@ describe 'Edit Project Settings', feature: true do
# Not using empty project because we need a repo to exist
let
(
:project
)
{
create
(
:project
,
namespace:
user
.
namespace
,
name:
'gitlabhq'
)
}
before
(
:context
)
{
TestEnv
.
clean_test_path
}
after
(
:example
)
{
TestEnv
.
clean_test_path
}
specify
'the project is accessible via the new path'
do
rename_project
(
project
,
path:
'bar'
)
new_path
=
namespace_project_path
(
project
.
namespace
,
'bar'
)
...
...
@@ -92,9 +95,10 @@ describe 'Edit Project Settings', feature: true do
# Not using empty project because we need a repo to exist
let
(
:project
)
{
create
(
:project
,
namespace:
user
.
namespace
,
name:
'gitlabhq'
)
}
let
(
:group
)
{
create
(
:group
)
}
before
do
group
.
add_owner
(
user
)
end
before
(
:context
)
{
TestEnv
.
clean_test_path
}
before
(
:example
)
{
group
.
add_owner
(
user
)
}
after
(
:example
)
{
TestEnv
.
clean_test_path
}
specify
'the project is accessible via the new path'
do
transfer_project
(
project
,
group
)
...
...
spec/lib/constraints/group_url_constrainer_spec.rb
View file @
7d02bcd2
...
...
@@ -29,9 +29,37 @@ describe GroupUrlConstrainer, lib: true do
it
{
expect
(
subject
.
matches?
(
request
)).
to
be_falsey
}
end
context
'when the request matches a redirect route'
do
context
'for a root group'
do
let!
(
:redirect_route
)
{
group
.
redirect_routes
.
create!
(
path:
'gitlabb'
)
}
context
'and is a GET request'
do
let
(
:request
)
{
build_request
(
redirect_route
.
path
)
}
it
{
expect
(
subject
.
matches?
(
request
)).
to
be_truthy
}
end
context
'and is NOT a GET request'
do
let
(
:request
)
{
build_request
(
redirect_route
.
path
,
'POST'
)
}
it
{
expect
(
subject
.
matches?
(
request
)).
to
be_falsey
}
end
end
context
'for a nested group'
do
let!
(
:nested_group
)
{
create
(
:group
,
path:
'nested'
,
parent:
group
)
}
let!
(
:redirect_route
)
{
nested_group
.
redirect_routes
.
create!
(
path:
'gitlabb/nested'
)
}
let
(
:request
)
{
build_request
(
redirect_route
.
path
)
}
it
{
expect
(
subject
.
matches?
(
request
)).
to
be_truthy
}
end
end
end
def
build_request
(
path
)
double
(
:request
,
params:
{
id:
path
})
def
build_request
(
path
,
method
=
'GET'
)
double
(
:request
,
'get?'
:
(
method
==
'GET'
),
params:
{
id:
path
})
end
end
spec/lib/constraints/project_url_constrainer_spec.rb
View file @
7d02bcd2
...
...
@@ -24,9 +24,26 @@ describe ProjectUrlConstrainer, lib: true do
it
{
expect
(
subject
.
matches?
(
request
)).
to
be_falsey
}
end
end
context
'when the request matches a redirect route'
do
let
(
:old_project_path
)
{
'old_project_path'
}
let!
(
:redirect_route
)
{
project
.
redirect_routes
.
create!
(
path:
"
#{
namespace
.
full_path
}
/
#{
old_project_path
}
"
)
}
context
'and is a GET request'
do
let
(
:request
)
{
build_request
(
namespace
.
full_path
,
old_project_path
)
}
it
{
expect
(
subject
.
matches?
(
request
)).
to
be_truthy
}
end
context
'and is NOT a GET request'
do
let
(
:request
)
{
build_request
(
namespace
.
full_path
,
old_project_path
,
'POST'
)
}
it
{
expect
(
subject
.
matches?
(
request
)).
to
be_falsey
}
end
end
end
def
build_request
(
namespace
,
project
)
double
(
:request
,
params:
{
namespace_id:
namespace
,
id:
project
})
def
build_request
(
namespace
,
project
,
method
=
'GET'
)
double
(
:request
,
'get?'
:
(
method
==
'GET'
),
params:
{
namespace_id:
namespace
,
id:
project
})
end
end
spec/lib/constraints/user_url_constrainer_spec.rb
View file @
7d02bcd2
...
...
@@ -15,9 +15,26 @@ describe UserUrlConstrainer, lib: true do
it
{
expect
(
subject
.
matches?
(
request
)).
to
be_falsey
}
end
context
'when the request matches a redirect route'
do
let
(
:old_project_path
)
{
'old_project_path'
}
let!
(
:redirect_route
)
{
user
.
namespace
.
redirect_routes
.
create!
(
path:
'foo'
)
}
context
'and is a GET request'
do
let
(
:request
)
{
build_request
(
redirect_route
.
path
)
}
it
{
expect
(
subject
.
matches?
(
request
)).
to
be_truthy
}
end
context
'and is NOT a GET request'
do
let
(
:request
)
{
build_request
(
redirect_route
.
path
,
'POST'
)
}
it
{
expect
(
subject
.
matches?
(
request
)).
to
be_falsey
}
end
end
end
def
build_request
(
username
)
double
(
:request
,
params:
{
username:
username
})
def
build_request
(
username
,
method
=
'GET'
)
double
(
:request
,
'get?'
:
(
method
==
'GET'
),
params:
{
username:
username
})
end
end
spec/lib/gitlab/import_export/all_models.yml
View file @
7d02bcd2
...
...
@@ -225,6 +225,7 @@ project:
-
authorized_users
-
project_authorizations
-
route
-
redirect_routes
-
statistics
-
container_repositories
-
uploads
...
...
spec/models/concerns/routable_spec.rb
View file @
7d02bcd2
...
...
@@ -9,6 +9,7 @@ describe Group, 'Routable' do
describe
'Associations'
do
it
{
is_expected
.
to
have_one
(
:route
).
dependent
(
:destroy
)
}
it
{
is_expected
.
to
have_many
(
:redirect_routes
).
dependent
(
:destroy
)
}
end
describe
'Callbacks'
do
...
...
@@ -35,12 +36,55 @@ describe Group, 'Routable' do
describe
'.find_by_full_path'
do
let!
(
:nested_group
)
{
create
(
:group
,
parent:
group
)
}
context
'without any redirect routes'
do
it
{
expect
(
described_class
.
find_by_full_path
(
group
.
to_param
)).
to
eq
(
group
)
}
it
{
expect
(
described_class
.
find_by_full_path
(
group
.
to_param
.
upcase
)).
to
eq
(
group
)
}
it
{
expect
(
described_class
.
find_by_full_path
(
nested_group
.
to_param
)).
to
eq
(
nested_group
)
}
it
{
expect
(
described_class
.
find_by_full_path
(
'unknown'
)).
to
eq
(
nil
)
}
end
context
'with redirect routes'
do
let!
(
:group_redirect_route
)
{
group
.
redirect_routes
.
create!
(
path:
'bar'
)
}
let!
(
:nested_group_redirect_route
)
{
nested_group
.
redirect_routes
.
create!
(
path:
nested_group
.
path
.
sub
(
'foo'
,
'bar'
))
}
context
'without follow_redirects option'
do
context
'with the given path not matching any route'
do
it
{
expect
(
described_class
.
find_by_full_path
(
'unknown'
)).
to
eq
(
nil
)
}
end
context
'with the given path matching the canonical route'
do
it
{
expect
(
described_class
.
find_by_full_path
(
group
.
to_param
)).
to
eq
(
group
)
}
it
{
expect
(
described_class
.
find_by_full_path
(
group
.
to_param
.
upcase
)).
to
eq
(
group
)
}
it
{
expect
(
described_class
.
find_by_full_path
(
nested_group
.
to_param
)).
to
eq
(
nested_group
)
}
end
context
'with the given path matching a redirect route'
do
it
{
expect
(
described_class
.
find_by_full_path
(
group_redirect_route
.
path
)).
to
eq
(
nil
)
}
it
{
expect
(
described_class
.
find_by_full_path
(
group_redirect_route
.
path
.
upcase
)).
to
eq
(
nil
)
}
it
{
expect
(
described_class
.
find_by_full_path
(
nested_group_redirect_route
.
path
)).
to
eq
(
nil
)
}
end
end
context
'with follow_redirects option set to true'
do
context
'with the given path not matching any route'
do
it
{
expect
(
described_class
.
find_by_full_path
(
'unknown'
,
follow_redirects:
true
)).
to
eq
(
nil
)
}
end
context
'with the given path matching the canonical route'
do
it
{
expect
(
described_class
.
find_by_full_path
(
group
.
to_param
,
follow_redirects:
true
)).
to
eq
(
group
)
}
it
{
expect
(
described_class
.
find_by_full_path
(
group
.
to_param
.
upcase
,
follow_redirects:
true
)).
to
eq
(
group
)
}
it
{
expect
(
described_class
.
find_by_full_path
(
nested_group
.
to_param
,
follow_redirects:
true
)).
to
eq
(
nested_group
)
}
end
context
'with the given path matching a redirect route'
do
it
{
expect
(
described_class
.
find_by_full_path
(
group_redirect_route
.
path
,
follow_redirects:
true
)).
to
eq
(
group
)
}
it
{
expect
(
described_class
.
find_by_full_path
(
group_redirect_route
.
path
.
upcase
,
follow_redirects:
true
)).
to
eq
(
group
)
}
it
{
expect
(
described_class
.
find_by_full_path
(
nested_group_redirect_route
.
path
,
follow_redirects:
true
)).
to
eq
(
nested_group
)
}
end
end
end
end
describe
'.where_full_path_in'
do
context
'without any paths'
do
it
'returns an empty relation'
do
...
...
spec/models/redirect_route_spec.rb
0 → 100644
View file @
7d02bcd2
require
'rails_helper'
describe
RedirectRoute
,
models:
true
do
let!
(
:group
)
{
create
(
:group
,
path:
'git_lab'
,
name:
'git_lab'
)
}
let!
(
:redirect_route
)
{
group
.
redirect_routes
.
create
(
path:
'gitlabb'
)
}
describe
'relationships'
do
it
{
is_expected
.
to
belong_to
(
:source
)
}
end
describe
'validations'
do
it
{
is_expected
.
to
validate_presence_of
(
:source
)
}
it
{
is_expected
.
to
validate_presence_of
(
:path
)
}
it
{
is_expected
.
to
validate_uniqueness_of
(
:path
)
}
end
end
spec/models/user_spec.rb
View file @
7d02bcd2
...
...
@@ -849,6 +849,65 @@ describe User, models: true do
end
end
describe
'.find_by_full_path'
do
let!
(
:user
)
{
create
(
:user
)
}
context
'with a route matching the given path'
do
let!
(
:route
)
{
user
.
namespace
.
route
}
it
'returns the user'
do
expect
(
User
.
find_by_full_path
(
route
.
path
)).
to
eq
(
user
)
end
it
'is case-insensitive'
do
expect
(
User
.
find_by_full_path
(
route
.
path
.
upcase
)).
to
eq
(
user
)
expect
(
User
.
find_by_full_path
(
route
.
path
.
downcase
)).
to
eq
(
user
)
end
end
context
'with a redirect route matching the given path'
do
let!
(
:redirect_route
)
{
user
.
namespace
.
redirect_routes
.
create
(
path:
'foo'
)
}
context
'without the follow_redirects option'
do
it
'returns nil'
do
expect
(
User
.
find_by_full_path
(
redirect_route
.
path
)).
to
eq
(
nil
)
end
end
context
'with the follow_redirects option set to true'
do
it
'returns the user'
do
expect
(
User
.
find_by_full_path
(
redirect_route
.
path
,
follow_redirects:
true
)).
to
eq
(
user
)
end
it
'is case-insensitive'
do
expect
(
User
.
find_by_full_path
(
redirect_route
.
path
.
upcase
,
follow_redirects:
true
)).
to
eq
(
user
)
expect
(
User
.
find_by_full_path
(
redirect_route
.
path
.
downcase
,
follow_redirects:
true
)).
to
eq
(
user
)
end
end
end
context
'without a route or a redirect route matching the given path'
do
context
'without the follow_redirects option'
do
it
'returns nil'
do
expect
(
User
.
find_by_full_path
(
'unknown'
)).
to
eq
(
nil
)
end
end
context
'with the follow_redirects option set to true'
do
it
'returns nil'
do
expect
(
User
.
find_by_full_path
(
'unknown'
,
follow_redirects:
true
)).
to
eq
(
nil
)
end
end
end
context
'with a group route matching the given path'
do
let!
(
:group
)
{
create
(
:group
,
path:
'group_path'
)
}
it
'returns nil'
do
expect
(
User
.
find_by_full_path
(
'group_path'
)).
to
eq
(
nil
)
end
end
end
describe
'all_ssh_keys'
do
it
{
is_expected
.
to
have_many
(
:keys
).
dependent
(
:destroy
)
}
...
...
spec/routing/project_routing_spec.rb
View file @
7d02bcd2
...
...
@@ -3,7 +3,7 @@ require 'spec_helper'
describe
'project routing'
do
before
do
allow
(
Project
).
to
receive
(
:find_by_full_path
).
and_return
(
false
)
allow
(
Project
).
to
receive
(
:find_by_full_path
).
with
(
'gitlab/gitlabhq'
).
and_return
(
true
)
allow
(
Project
).
to
receive
(
:find_by_full_path
).
with
(
'gitlab/gitlabhq'
,
any_args
).
and_return
(
true
)
end
# Shared examples for a resource inside a Project
...
...
@@ -93,13 +93,13 @@ describe 'project routing' do
end
context
'name with dot'
do
before
{
allow
(
Project
).
to
receive
(
:find_by_full_path
).
with
(
'gitlab/gitlabhq.keys'
).
and_return
(
true
)
}
before
{
allow
(
Project
).
to
receive
(
:find_by_full_path
).
with
(
'gitlab/gitlabhq.keys'
,
any_args
).
and_return
(
true
)
}
it
{
expect
(
get
(
'/gitlab/gitlabhq.keys'
)).
to
route_to
(
'projects#show'
,
namespace_id:
'gitlab'
,
id:
'gitlabhq.keys'
)
}
end
context
'with nested group'
do
before
{
allow
(
Project
).
to
receive
(
:find_by_full_path
).
with
(
'gitlab/subgroup/gitlabhq'
).
and_return
(
true
)
}
before
{
allow
(
Project
).
to
receive
(
:find_by_full_path
).
with
(
'gitlab/subgroup/gitlabhq'
,
any_args
).
and_return
(
true
)
}
it
{
expect
(
get
(
'/gitlab/subgroup/gitlabhq'
)).
to
route_to
(
'projects#show'
,
namespace_id:
'gitlab/subgroup'
,
id:
'gitlabhq'
)
}
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