Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Menu
Open sidebar
heptapod
heptapod
Commits
9905ccbe0638
Unverified
Commit
8b98ca50
authored
Apr 07, 2017
by
Luke "Jared" Bennett
Browse files
Merge branch 'master' into new-resolvable-discussion
parents
7623543972cf
92ddaf38ad0e
Changes
123
Hide whitespace changes
Inline
Side-by-side
.gitlab-ci.yml
View file @
9905ccbe
...
...
@@ -347,7 +347,7 @@ migration paths:
-
master@gitlab/gitlabhq
-
master@gitlab/gitlab-ee
script
:
-
git fetch origin v8.
5.9
-
git fetch origin v8.
14.10
-
git checkout -f FETCH_HEAD
-
cp config/resque.yml.example config/resque.yml
-
sed -i 's/localhost/redis/g' config/resque.yml
...
...
app/assets/javascripts/awards_handler.js
View file @
9905ccbe
...
...
@@ -51,7 +51,7 @@ function renderCategory(name, emojiList, opts = {}) {
<h5 class="emoji-menu-title">
${
name
}
</h5>
<ul class="clearfix emoji-menu-list
${
opts
.
menuListClass
}
">
<ul class="clearfix emoji-menu-list
${
opts
.
menuListClass
||
''
}
">
${
emojiList
.
map
(
emojiName
=>
`
<li class="emoji-menu-list-item">
<button class="emoji-menu-btn text-center js-emoji-btn" type="button">
...
...
app/assets/javascripts/boards/boards_bundle.js
View file @
9905ccbe
...
...
@@ -38,6 +38,10 @@ $(() => {
Store
.
create
();
// hack to allow sidebar scripts like milestone_select manipulate the BoardsStore
gl
.
issueBoards
.
boardStoreIssueSet
=
(...
args
)
=>
Vue
.
set
(
Store
.
detail
.
issue
,
...
args
);
gl
.
issueBoards
.
boardStoreIssueDelete
=
(...
args
)
=>
Vue
.
delete
(
Store
.
detail
.
issue
,
...
args
);
gl
.
IssueBoardsApp
=
new
Vue
({
el
:
$boardApp
,
components
:
{
...
...
app/assets/javascripts/commit/pipelines/pipelines_bundle.js
View file @
9905ccbe
...
...
@@ -12,20 +12,18 @@ Vue.use(VueResource);
* Renders Pipelines table in pipelines tab in the commits show view.
*/
// export for use in merge_request_tabs.js (TODO: remove this hack)
window
.
gl
=
window
.
gl
||
{};
window
.
gl
.
CommitPipelinesTable
=
CommitPipelinesTable
;
$
(()
=>
{
window
.
gl
=
window
.
gl
||
{};
gl
.
commits
=
gl
.
commits
||
{};
gl
.
commits
.
pipelines
=
gl
.
commits
.
pipelines
||
{};
if
(
gl
.
commits
.
PipelinesTableBundle
)
{
document
.
querySelector
(
'
#commit-pipeline-table-view
'
).
removeChild
(
this
.
pipelinesTableBundle
.
$el
);
gl
.
commits
.
PipelinesTableBundle
.
$destroy
(
true
);
}
const
pipelineTableViewEl
=
document
.
querySelector
(
'
#commit-pipeline-table-view
'
);
if
(
pipelineTableViewEl
&&
pipelineTableViewEl
.
dataset
.
disableInitialization
===
undefined
)
{
gl
.
commits
.
pipelines
.
PipelinesTableBundle
=
new
CommitPipelinesTable
().
$mount
();
document
.
querySelector
(
'
#commit-
pipeline
-t
able
-v
iew
'
)
.
appendChild
(
gl
.
commits
.
pipelines
.
PipelinesTableBundle
.
$el
);
pipeline
T
able
V
iew
El
.
appendChild
(
gl
.
commits
.
pipelines
.
PipelinesTableBundle
.
$el
);
}
});
app/assets/javascripts/dispatcher.js
View file @
9905ccbe
...
...
@@ -44,6 +44,7 @@ import MiniPipelineGraph from './mini_pipeline_graph_dropdown';
import
BlobLinePermalinkUpdater
from
'
./blob/blob_line_permalink_updater
'
;
import
BlobForkSuggestion
from
'
./blob/blob_fork_suggestion
'
;
import
UserCallout
from
'
./user_callout
'
;
import
{
ProtectedTagCreate
,
ProtectedTagEditList
}
from
'
./protected_tags
'
;
const
ShortcutsBlob
=
require
(
'
./shortcuts_blob
'
);
...
...
@@ -329,8 +330,12 @@ const ShortcutsBlob = require('./shortcuts_blob');
new
Search
();
break
;
case
'
projects:repository:show
'
:
// Initialize Protected Branch Settings
new
gl
.
ProtectedBranchCreate
();
new
gl
.
ProtectedBranchEditList
();
// Initialize Protected Tag Settings
new
ProtectedTagCreate
();
new
ProtectedTagEditList
();
break
;
case
'
projects:ci_cd:show
'
:
new
gl
.
ProjectVariables
();
...
...
app/assets/javascripts/merge_request_tabs.js
View file @
9905ccbe
...
...
@@ -3,9 +3,6 @@
/* global Flash */
import
Cookies
from
'
js-cookie
'
;
import
CommitPipelinesTable
from
'
./commit/pipelines/pipelines_table
'
;
import
'
./breakpoints
'
;
import
'
./flash
'
;
...
...
@@ -102,10 +99,10 @@ import './flash';
destroyPipelinesView
()
{
if
(
this
.
commitPipelinesTable
)
{
document
.
querySelector
(
'
#commit-pipeline-table-view
'
)
.
removeChild
(
this
.
commitPipelinesTable
.
$el
);
this
.
commitPipelinesTable
.
$destroy
();
this
.
commitPipelinesTable
=
null
;
document
.
querySelector
(
'
#commit-pipeline-table-view
'
).
innerHTML
=
''
;
}
}
...
...
@@ -234,7 +231,7 @@ import './flash';
}
mountPipelinesView
()
{
this
.
commitPipelinesTable
=
new
CommitPipelinesTable
().
$mount
();
this
.
commitPipelinesTable
=
new
gl
.
CommitPipelinesTable
().
$mount
();
// $mount(el) replaces the el with the new rendered component. We need it in order to mount
// it everytime this tab is clicked - https://vuejs.org/v2/api/#vm-mount
document
.
querySelector
(
'
#commit-pipeline-table-view
'
)
...
...
app/assets/javascripts/milestone_select.js
View file @
9905ccbe
...
...
@@ -2,8 +2,6 @@
/* global Issuable */
/* global ListMilestone */
import
Vue
from
'
vue
'
;
(
function
()
{
this
.
MilestoneSelect
=
(
function
()
{
function
MilestoneSelect
(
currentProject
,
els
)
{
...
...
@@ -151,12 +149,12 @@ import Vue from 'vue';
return
$dropdown
.
closest
(
'
form
'
).
submit
();
}
else
if
(
$dropdown
.
hasClass
(
'
js-issue-board-sidebar
'
))
{
if
(
selected
.
id
!==
-
1
)
{
Vue
.
set
(
gl
.
issueBoards
.
B
oard
s
Store
.
detail
.
issue
,
'
milestone
'
,
new
ListMilestone
({
gl
.
issueBoards
.
b
oardStore
IssueSet
(
'
milestone
'
,
new
ListMilestone
({
id
:
selected
.
id
,
title
:
selected
.
name
}));
}
else
{
Vue
.
delete
(
gl
.
issueBoards
.
B
oard
s
Store
.
detail
.
issue
,
'
milestone
'
);
gl
.
issueBoards
.
b
oardStore
IssueDelete
(
'
milestone
'
);
}
$dropdown
.
trigger
(
'
loading.gl.dropdown
'
);
...
...
app/assets/javascripts/protected_tags/index.js
0 → 100644
View file @
9905ccbe
export
{
default
as
ProtectedTagCreate
}
from
'
./protected_tag_create
'
;
export
{
default
as
ProtectedTagEditList
}
from
'
./protected_tag_edit_list
'
;
app/assets/javascripts/protected_tags/protected_tag_access_dropdown.js
0 → 100644
View file @
9905ccbe
export
default
class
ProtectedTagAccessDropdown
{
constructor
(
options
)
{
this
.
options
=
options
;
this
.
initDropdown
();
}
initDropdown
()
{
const
{
onSelect
}
=
this
.
options
;
this
.
options
.
$dropdown
.
glDropdown
({
data
:
this
.
options
.
data
,
selectable
:
true
,
inputId
:
this
.
options
.
$dropdown
.
data
(
'
input-id
'
),
fieldName
:
this
.
options
.
$dropdown
.
data
(
'
field-name
'
),
toggleLabel
(
item
,
$el
)
{
if
(
$el
.
is
(
'
.is-active
'
))
{
return
item
.
text
;
}
return
'
Select
'
;
},
clicked
(
item
,
$el
,
e
)
{
e
.
preventDefault
();
onSelect
();
},
});
}
}
app/assets/javascripts/protected_tags/protected_tag_create.js
0 → 100644
View file @
9905ccbe
import
ProtectedTagAccessDropdown
from
'
./protected_tag_access_dropdown
'
;
import
ProtectedTagDropdown
from
'
./protected_tag_dropdown
'
;
export
default
class
ProtectedTagCreate
{
constructor
()
{
this
.
$form
=
$
(
'
.js-new-protected-tag
'
);
this
.
buildDropdowns
();
}
buildDropdowns
()
{
const
$allowedToCreateDropdown
=
this
.
$form
.
find
(
'
.js-allowed-to-create
'
);
// Cache callback
this
.
onSelectCallback
=
this
.
onSelect
.
bind
(
this
);
// Allowed to Create dropdown
this
.
protectedTagAccessDropdown
=
new
ProtectedTagAccessDropdown
({
$dropdown
:
$allowedToCreateDropdown
,
data
:
gon
.
create_access_levels
,
onSelect
:
this
.
onSelectCallback
,
});
// Select default
$allowedToCreateDropdown
.
data
(
'
glDropdown
'
).
selectRowAtIndex
(
0
);
// Protected tag dropdown
this
.
protectedTagDropdown
=
new
ProtectedTagDropdown
({
$dropdown
:
this
.
$form
.
find
(
'
.js-protected-tag-select
'
),
onSelect
:
this
.
onSelectCallback
,
});
}
// This will run after clicked callback
onSelect
()
{
// Enable submit button
const
$tagInput
=
this
.
$form
.
find
(
'
input[name="protected_tag[name]"]
'
);
const
$allowedToCreateInput
=
this
.
$form
.
find
(
'
#create_access_levels_attributes
'
);
this
.
$form
.
find
(
'
input[type="submit"]
'
).
attr
(
'
disabled
'
,
!
(
$tagInput
.
val
()
&&
$allowedToCreateInput
.
length
));
}
}
app/assets/javascripts/protected_tags/protected_tag_dropdown.js
0 → 100644
View file @
9905ccbe
export
default
class
ProtectedTagDropdown
{
/**
* @param {Object} options containing
* `$dropdown` target element
* `onSelect` event callback
* $dropdown must be an element created using `dropdown_tag()` rails helper
*/
constructor
(
options
)
{
this
.
onSelect
=
options
.
onSelect
;
this
.
$dropdown
=
options
.
$dropdown
;
this
.
$dropdownContainer
=
this
.
$dropdown
.
parent
();
this
.
$dropdownFooter
=
this
.
$dropdownContainer
.
find
(
'
.dropdown-footer
'
);
this
.
$protectedTag
=
this
.
$dropdownContainer
.
find
(
'
.create-new-protected-tag
'
);
this
.
buildDropdown
();
this
.
bindEvents
();
// Hide footer
this
.
toggleFooter
(
true
);
}
buildDropdown
()
{
this
.
$dropdown
.
glDropdown
({
data
:
this
.
getProtectedTags
.
bind
(
this
),
filterable
:
true
,
remote
:
false
,
search
:
{
fields
:
[
'
title
'
],
},
selectable
:
true
,
toggleLabel
(
selected
)
{
return
(
selected
&&
'
id
'
in
selected
)
?
selected
.
title
:
'
Protected Tag
'
;
},
fieldName
:
'
protected_tag[name]
'
,
text
(
protectedTag
)
{
return
_
.
escape
(
protectedTag
.
title
);
},
id
(
protectedTag
)
{
return
_
.
escape
(
protectedTag
.
id
);
},
onFilter
:
this
.
toggleCreateNewButton
.
bind
(
this
),
clicked
:
(
item
,
$el
,
e
)
=>
{
e
.
preventDefault
();
this
.
onSelect
();
},
});
}
bindEvents
()
{
this
.
$protectedTag
.
on
(
'
click
'
,
this
.
onClickCreateWildcard
.
bind
(
this
));
}
onClickCreateWildcard
(
e
)
{
this
.
$dropdown
.
data
(
'
glDropdown
'
).
remote
.
execute
();
this
.
$dropdown
.
data
(
'
glDropdown
'
).
selectRowAtIndex
();
e
.
preventDefault
();
}
getProtectedTags
(
term
,
callback
)
{
if
(
this
.
selectedTag
)
{
callback
(
gon
.
open_tags
.
concat
(
this
.
selectedTag
));
}
else
{
callback
(
gon
.
open_tags
);
}
}
toggleCreateNewButton
(
tagName
)
{
if
(
tagName
)
{
this
.
selectedTag
=
{
title
:
tagName
,
id
:
tagName
,
text
:
tagName
,
};
this
.
$dropdownContainer
.
find
(
'
.create-new-protected-tag code
'
)
.
text
(
tagName
);
}
this
.
toggleFooter
(
!
tagName
);
}
toggleFooter
(
toggleState
)
{
this
.
$dropdownFooter
.
toggleClass
(
'
hidden
'
,
toggleState
);
}
}
app/assets/javascripts/protected_tags/protected_tag_edit.js
0 → 100644
View file @
9905ccbe
/* eslint-disable no-new */
/* global Flash */
import
ProtectedTagAccessDropdown
from
'
./protected_tag_access_dropdown
'
;
export
default
class
ProtectedTagEdit
{
constructor
(
options
)
{
this
.
$wrap
=
options
.
$wrap
;
this
.
$allowedToCreateDropdownButton
=
this
.
$wrap
.
find
(
'
.js-allowed-to-create
'
);
this
.
onSelectCallback
=
this
.
onSelect
.
bind
(
this
);
this
.
buildDropdowns
();
}
buildDropdowns
()
{
// Allowed to create dropdown
this
.
protectedTagAccessDropdown
=
new
ProtectedTagAccessDropdown
({
$dropdown
:
this
.
$allowedToCreateDropdownButton
,
data
:
gon
.
create_access_levels
,
onSelect
:
this
.
onSelectCallback
,
});
}
onSelect
()
{
const
$allowedToCreateInput
=
this
.
$wrap
.
find
(
`input[name="
${
this
.
$allowedToCreateDropdownButton
.
data
(
'
fieldName
'
)}
"]`
);
// Do not update if one dropdown has not selected any option
if
(
!
$allowedToCreateInput
.
length
)
return
;
this
.
$allowedToCreateDropdownButton
.
disable
();
$
.
ajax
({
type
:
'
POST
'
,
url
:
this
.
$wrap
.
data
(
'
url
'
),
dataType
:
'
json
'
,
data
:
{
_method
:
'
PATCH
'
,
protected_tag
:
{
create_access_levels_attributes
:
[{
id
:
this
.
$allowedToCreateDropdownButton
.
data
(
'
access-level-id
'
),
access_level
:
$allowedToCreateInput
.
val
(),
}],
},
},
error
()
{
new
Flash
(
'
Failed to update tag!
'
,
null
,
$
(
'
.js-protected-tags-list
'
));
},
}).
always
(()
=>
{
this
.
$allowedToCreateDropdownButton
.
enable
();
});
}
}
app/assets/javascripts/protected_tags/protected_tag_edit_list.js
0 → 100644
View file @
9905ccbe
/* eslint-disable no-new */
import
ProtectedTagEdit
from
'
./protected_tag_edit
'
;
export
default
class
ProtectedTagEditList
{
constructor
()
{
this
.
$wrap
=
$
(
'
.protected-tags-list
'
);
this
.
initEditForm
();
}
initEditForm
()
{
this
.
$wrap
.
find
(
'
.js-protected-tag-edit-form
'
).
each
((
i
,
el
)
=>
{
new
ProtectedTagEdit
({
$wrap
:
$
(
el
),
});
});
}
}
app/assets/javascripts/subscription.js
View file @
9905ccbe
import
Vue
from
'
vue
'
;
(()
=>
{
class
Subscription
{
constructor
(
containerElm
)
{
...
...
@@ -29,8 +27,7 @@ import Vue from 'vue';
// hack to allow this to work with the issue boards Vue object
if
(
document
.
querySelector
(
'
html
'
).
classList
.
contains
(
'
issue-boards-page
'
))
{
Vue
.
set
(
gl
.
issueBoards
.
BoardsStore
.
detail
.
issue
,
gl
.
issueBoards
.
boardStoreIssueSet
(
'
subscribed
'
,
!
gl
.
issueBoards
.
BoardsStore
.
detail
.
issue
.
subscribed
,
);
...
...
app/assets/javascripts/users_select.js
View file @
9905ccbe
...
...
@@ -2,8 +2,6 @@
/* global Issuable */
/* global ListUser */
import
Vue
from
'
vue
'
;
(
function
()
{
var
bind
=
function
(
fn
,
me
)
{
return
function
()
{
return
fn
.
apply
(
me
,
arguments
);
};
},
slice
=
[].
slice
;
...
...
@@ -74,7 +72,7 @@ import Vue from 'vue';
e
.
preventDefault
();
if
(
$dropdown
.
hasClass
(
'
js-issue-board-sidebar
'
))
{
Vue
.
set
(
gl
.
issueBoards
.
B
oard
s
Store
.
detail
.
issue
,
'
assignee
'
,
new
ListUser
({
gl
.
issueBoards
.
b
oardStore
IssueSet
(
'
assignee
'
,
new
ListUser
({
id
:
_this
.
currentUser
.
id
,
username
:
_this
.
currentUser
.
username
,
name
:
_this
.
currentUser
.
name
,
...
...
@@ -225,14 +223,14 @@ import Vue from 'vue';
return
$dropdown
.
closest
(
'
form
'
).
submit
();
}
else
if
(
$dropdown
.
hasClass
(
'
js-issue-board-sidebar
'
))
{
if
(
user
.
id
)
{
Vue
.
set
(
gl
.
issueBoards
.
B
oard
s
Store
.
detail
.
issue
,
'
assignee
'
,
new
ListUser
({
gl
.
issueBoards
.
b
oardStore
IssueSet
(
'
assignee
'
,
new
ListUser
({
id
:
user
.
id
,
username
:
user
.
username
,
name
:
user
.
name
,
avatar_url
:
user
.
avatar_url
}));
}
else
{
Vue
.
delete
(
gl
.
issueBoards
.
B
oard
s
Store
.
detail
.
issue
,
'
assignee
'
);
gl
.
issueBoards
.
b
oardStore
IssueDelete
(
'
assignee
'
);
}
updateIssueBoardsIssue
();
...
...
app/assets/stylesheets/pages/projects.scss
View file @
9905ccbe
...
...
@@ -744,7 +744,8 @@ pre.light-well {
text-align
:
left
;
}
.protected-branches-list
{
.protected-branches-list
,
.protected-tags-list
{
margin-bottom
:
30px
;
a
{
...
...
@@ -776,6 +777,17 @@ pre.light-well {
}
}
.protected-tags-list
{
.dropdown-menu-toggle
{
width
:
100%
;
max-width
:
300px
;
}
.flash-container
{
padding
:
0
;
}
}
.custom-notifications-form
{
.is-loading
{
.custom-notification-event-loading
{
...
...
app/controllers/projects/builds_controller.rb
View file @
9905ccbe
...
...
@@ -19,6 +19,11 @@ def index
else
@builds
end
@builds
=
@builds
.
includes
([
{
pipeline: :project
},
:project
,
:tags
])
@builds
=
@builds
.
page
(
params
[
:page
]).
per
(
30
)
end
...
...
app/controllers/projects/protected_branches_controller.rb
View file @
9905ccbe
class
Projects::ProtectedBranchesController
<
Projects
::
ApplicationController
include
RepositorySettingsRedirect
# Authorize
before_action
:require_non_empty_project
before_action
:authorize_admin_project!
before_action
:load_protected_branch
,
only:
[
:show
,
:update
,
:destroy
]
class
Projects::ProtectedBranchesController
<
Projects
::
ProtectedRefsController
protected
layout
"project_settings"
def
index
redirect_to_repository_settings
(
@project
)
end
def
create
@protected_branch
=
::
ProtectedBranches
::
CreateService
.
new
(
@project
,
current_user
,
protected_branch_params
).
execute
unless
@protected_branch
.
persisted?
flash
[
:alert
]
=
@protected_branches
.
errors
.
full_messages
.
join
(
', '
).
html_safe
end
redirect_to_repository_settings
(
@project
)
end
def
show
@matching_branches
=
@protected_branch
.
matching
(
@project
.
repository
.
branches
)
def
project_refs
@project
.
repository
.
branches
end
def
update
@protected_branch
=
::
ProtectedBranches
::
UpdateService
.
new
(
@project
,
current_user
,
protected_branch_params
).
execute
(
@protected_branch
)
if
@protected_branch
.
valid?
respond_to
do
|
format
|
format
.
json
{
render
json:
@protected_branch
,
status: :ok
}
end
else
respond_to
do
|
format
|
format
.
json
{
render
json:
@protected_branch
.
errors
,
status: :unprocessable_entity
}
end
end
def
create_service_class
::
ProtectedBranches
::
CreateService
end
def
destroy
@protected_branch
.
destroy
respond_to
do
|
format
|
format
.
html
{
redirect_to_repository_settings
(
@project
)
}
format
.
js
{
head
:ok
}
end
def
update_service_class
::
ProtectedBranches
::
UpdateService
end
private
def
load_protected_branch
@protected_branch
=
@project
.
protected_branches
.
find
(
params
[
:id
])
def
load_protected_ref
@protected_ref
=
@project
.
protected_branches
.
find
(
params
[
:id
])
end
def
protected_
branch
_params
def
protected_
ref
_params
params
.
require
(
:protected_branch
).
permit
(
:name
,
merge_access_levels_attributes:
[
:access_level
,
:id
],
push_access_levels_attributes:
[
:access_level
,
:id
])
...
...
app/controllers/projects/protected_refs_controller.rb
0 → 100644
View file @
9905ccbe
class
Projects::ProtectedRefsController
<
Projects
::
ApplicationController
include
RepositorySettingsRedirect
# Authorize
before_action
:require_non_empty_project
before_action
:authorize_admin_project!
before_action
:load_protected_ref
,
only:
[
:show
,
:update
,
:destroy
]
layout
"project_settings"
def
index
redirect_to_repository_settings
(
@project
)
end
def
create
protected_ref
=
create_service_class
.
new
(
@project
,
current_user
,
protected_ref_params
).
execute
unless
protected_ref
.
persisted?
flash
[
:alert
]
=
protected_ref
.
errors
.
full_messages
.
join
(
', '
).
html_safe
end
redirect_to_repository_settings
(
@project
)
end
def
show
@matching_refs
=
@protected_ref
.
matching
(
project_refs
)
end
def
update
@protected_ref
=
update_service_class
.
new
(
@project
,
current_user
,
protected_ref_params
).
execute
(
@protected_ref
)
if
@protected_ref
.
valid?
render
json:
@protected_ref
,
status: :ok
else
render
json:
@protected_ref
.
errors
,
status: :unprocessable_entity
end
end
def
destroy
@protected_ref
.
destroy
respond_to
do
|
format
|
format
.
html
{
redirect_to_repository_settings
(
@project
)
}
format
.
js
{
head
:ok
}
end
end
end
app/controllers/projects/protected_tags_controller.rb
0 → 100644
View file @
9905ccbe
class
Projects::ProtectedTagsController
<
Projects
::
ProtectedRefsController
protected